Author: Authur Whywait, 一个努力学习知识的孩子。好吧,其实是老男孩 \ (0^ ◇^0)/
想看博主的其他所有leetcode卡片学习笔记链接?传送门点这儿文章目录
- 复习
- 对照
- 程序练习:
- 1. 合并两个有序列表
- 2. 两数相加
- 3. 扁平化多级双向链表
- 4. 复制带随机指针的链表
- 5. 旋转链表
回顾单链表和双链表的表现
相同:
- 它们都无法在常量时间内随机访问数据。
- 它们都能够在 O(1) 时间内在给定结点之后或列表开头添加一个新结点。
- 它们都能够在 O(1) 时间内删除第一个结点。
不同 - 体现在删除给定结点(包括最后一个结点)
- 在单链表中,它无法获取给定结点的前一个结点,因此在删除给定结点之前我们
必须花费 O(N) 时间来找出前一结点
。- 在双链表中,这会更容易,因为我们可以
使用“prev”引用字段获取前一个结点
。因此我们可以在 O(1) 时间内删除给定结点。
下面是链表和其他数据结构(包括数组,队列和栈)之间的时间复杂度比较:
如果你需要经常添加或删除结点,链表可能是一个不错的选择。
如果你需要经常按索引访问元素,数组可能是比链表更好的选择。
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
思路:比较两个链表的表头,将其中较小的一个丢到新的链表里,直至遍历两个链表。
遇到过的报错:runtime error: member access within misaligned address(点击链接,可以查看错误原因)
遇到的第二个问题:
前提:head->next == NULL
错误操作:h = head->next; h = h->next;
错误解释:一个空的东西,哪来的next
? (╯‵□′)╯︵┻━┻
下面是代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
struct ListNode * head = (struct ListNode *)malloc(sizeof(struct ListNode));
head->val=0;
head->next=NULL;
struct ListNode * h = head, * h1 = l1, * h2 = l2;
while(h1 || h2){
if(h1 && h2){
struct ListNode * temp = (struct ListNode *)malloc(sizeof(struct ListNode));
temp->val = h1->val < h2->val ? h1->val : h2->val;
temp->next = NULL;
h->next = temp;
h = h->next;
if(h->val==h1->val) h1 = h1->next;
else h2 = h2->next;
}
else if(h1 && !h2){
struct ListNode * temp = (struct ListNode *)malloc(sizeof(struct ListNode));
temp->val = h1->val;
temp->next = NULL;
h->next = temp;
h = h->next;
h1 = h1->next;
}
else{
struct ListNode * temp = (struct ListNode *)malloc(sizeof(struct ListNode));
temp->val = h2->val;
temp->next = NULL;
h->next = temp;
h = h->next;
h2 = h2->next;
}
}
return head->next;
}
想清楚情况类型,分情况讨论,这种问题就能轻易解决啦~(当然不要像我一样犯低级错误 (╯‵□′)╯︵┻━┻ )
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
思路:回想数组里面的两数相加,是不是就觉得so简单了呢~
这题其实就是关乎进位 - 满十进一
代码直接丢在下头
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
struct ListNode *l3 = (struct ListNode*)malloc(sizeof(struct ListNode));
l3->val = 0; l3->next = NULL;
struct ListNode* h1 = l1, * h2 =l2, * h3 = l3;
int flag = 0;
while(h1 != NULL && h2 != NULL){
if(flag){
h3->val = 1;
flag = 0;
}
h3->val += (h1->val + h2->val);
if(h3->val >= 10){
h3->val = h3->val % 10;
flag = 1;
}
h1 = h1->next;
h2 = h2->next;
if((h1 || h2) && !h3->next){
struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
tempNode->val = 0;
tempNode->next = NULL;
h3->next = tempNode;
h3 = h3->next;
}
}
if (!h1 && !h2 && flag){
if(!h3->next){
struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
tempNode->val = 1;
tempNode->next = NULL;
h3->next = tempNode;
}
}
if(h1){
while(h1){
h3->val = flag + h1->val;
flag = 0;
if(h3->val >= 10){
h3->val = h3->val % 10;
flag = 1;
}
h1 = h1->next;
if(!h3->next && h1){
struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
tempNode->val = 0;
tempNode->next = NULL;
h3->next = tempNode;
}
if(h1) h3 = h3->next;
}
if(flag && !h3->next){
if(!h3->next){
struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
tempNode->val = 1;
tempNode->next = NULL;
h3->next = tempNode;
}
}
}
else if(h2){
while(h2){
h3->val = flag + h2->val;
flag = 0;
if(h3->val >= 10){
h3->val = h3->val % 10;
flag = 1;
}
h2 = h2->next;
if(!h3->next && h2){
struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
tempNode->val = 0;
tempNode->next = NULL;
h3->next = tempNode;
}
if(h2) h3 = h3->next;
}
if(flag && !h3->next){
if(!h3->next){
struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
tempNode->val = 1;
tempNode->next = NULL;
h3->next = tempNode;
}
}
}
else ;//puts("something is wrong!");
return l3;
}
其实学到这里的时候,我发现,这个程序是我在leetcode写的第二个程序(当初花了近乎一天的时间写出这个程序之后,我发现,单单做题是没用的,还需要吸收相关知识,不管是算法还是数据结构)。所以,这道题之后,我开始学习leetcode里面的卡片,并在CSDN上努力吸收知识。回想起来,我与刚进入leetcode的我,从自我感觉的角度来说,差别还是有的,不说解题的速度,更多的是在于面对难解问题时候内心的无所惧。继续加油!
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。
输入:head = [1,2,null,3]
输出:[1,3,2]
解释:
输入的多级列表如下图所示:
1—2---NULL
|
3—NULL
方法:把到处分叉的双向链表捋直了。觉得理不清就画个图,事半功倍
代码写的C++, 因为这道题leetcode没有给出C的语言环境(╯‵□′)╯︵┻━┻
/*
// Definition for a Node.
class Node {
public:
int val;
Node* prev;
Node* next;
Node* child;
};
*/
class Solution {
public:
Node* flatten(Node* head) {
if(!head) return nullptr;
Node *cur=head;
while(cur){
Node* nxt = cur->next;
if(cur->child){
Node * child1=cur->child;
cur->next=child1;
child1->prev=cur;
cur->child=nullptr;
Node *tail=child1;
while(tail&&tail->next) tail=tail->next;
tail->next=nxt;
if(nxt) nxt->prev=tail;
}
cur = cur->next;
}
return head;
}
};
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
要求返回这个链表的 深拷贝
。
我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index]
表示:
val
:一个表示 Node.val
的整数。random_index
:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。深拷贝是什么鬼?难道还有浅拷贝?于是我陷入了深深的沉思··· ok, fine. 我其实是去查资料了(这种东西单单靠脑子肿么想得出来)。深拷贝与浅拷贝传送门在这儿。(看到这里的你一定黑人问号脸:传送门搁那儿呢?谁又能想到传送门是这儿后面的那个句号呢?我真是个小机灵鬼(●ˇ∀ˇ●))
"沉思"结束,我马上一顿操作
主要步骤分为两步:
- 构建一个新的链表,除了其中每个结点的随机指针
h1->random==NULL
都为NULL
,和原链表并无其他差别;- 新链表构建好之后,我们利用计数器index,依次遍历新链表,对每个结点的随机指针进行赋值。
/**
* Definition for a Node.
* struct Node {
* int val;
* struct TreeNode *next;
* struct TreeNode *random;
* };
*/
struct Node* copyRandomList(struct Node* head) {
if(!head) return NULL;
/*使用next将整个链表构建起来*/
struct Node *head1 = (struct Node *)malloc(sizeof(struct Node));
head1->val = head->val;
head1->next = NULL;
head1->random = NULL;
struct Node* h = head, * h1 = head1;
while(h->next){
h = h->next;
struct Node * temp = (struct Node *)malloc(sizeof(struct Node));
temp->val = h->val;
temp->next = NULL;
temp->random = NULL;
h1->next = temp;
h1 = h1->next;
}
/*处理那个随机指针*/
h = head; h1 = head1;
while(h){
if(!h) break;
int index=0;
struct Node * cur=head, * cur1=head1;
if(h->random){
while(cur && cur!=h->random){
index++;
cur = cur->next;
}
while(index){
index--;
cur1 = cur1->next;
}
h1->random = cur1;
}
else h1->random = NULL;
h = h->next;
h1 = h1->next;
}
return head1;
}
PS:此题所用到的深拷贝,只是深拷贝里面比较简单的一种。过些日子要写一篇blog,专门总结整理一下深拷贝的相关知识点,包括深拷贝与浅拷贝的区别以及深拷贝的程序实现等内容。ヾ(≧ ▽ ≦)ゝ
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
如果你是随着我的整个leetcode卡片学习系列一路下来,那你一定是知道我曾经遇到过一道题——旋转数组,这道题的解法颇多,详情看链接。(这次传送门不在句号上,惊喜不?( ¯(∞)¯ ) )
言归正传,我们可以用 旋转数组 里面的思想来处理这道题目么?
自然是可以的!
不管是暴力换头术,辅助链表,还是其他方法,都可以解决这类问题。
但是对于链表而言,用环来做,不香吗?
方法:
- 最后一个节点的next指针指向头节点
- 找到相应的位置,断掉环,返回新的头节点。
实际操作小技巧:使用%取余,减少不必要的循环次数
struct ListNode* rotateRight(struct ListNode* head, int k){
/*如果为空,直接返回NULL*/
if(!head) return NULL;
/*得到节点个数size*/
int size=1;
struct ListNode* cur=head;
while(cur->next){
cur = cur->next;
size++;
}
/*如果右移位数k为size的整数倍,则直接返回原链表,right为头节点右移的位数*/
int right = size - (k%size);
if(size==right) return head;
/*尾首相连,得到一个环,找到新的头节点的位置以及新的最后一个节点的位置*/
cur->next = head;
struct ListNode* newtail=cur;
while(right){
newtail = newtail->next;
right--;
}
/*断环*/
head = newtail->next;
newtail->next = NULL;
/*返回新的头节点*/
return head;
}
都看到这里了,确定不点个赞再走? (╯‵□′)╯︵┻━┻