我们知道,如果申请一块儿连续的存储来储存数据的话,一旦遇到插入和删除操作,这个时候后续所有数据都需要向前或者向后移动,这种时间上的开销甚至可能达到O(N)。
所以我们想到用不连续的存储,即用链表来存储,每个链表节点中,含有两部分,第一部分是值部分,第二部分是指针部分,通常记作NEXT指针,NEXT指针指向下一个节点的的位置。如果其是最后一个节点,NEXT则指向NULL。
我们来看一下leetcode默认的链表表示方法:
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(nullptr) {}
};
这是一种结构体,类型名为ListNode,其内包含一个值 val,和一个ListNode类型的指针NEXT,指向下一个节点。再往下就是其构造函数。
节点的插入与删除
出现的问题
解决方法:
1.哑节点(虚拟节点)/表头:
我们在第一个位置之前(即位置0)多留一个节点,使其指向当前链表的头节点。这样即使我们删除了整个链表,表头这个节点仍然存在。
2.尽量处理当前节点的下一个节点,而非当前节点本身
这个想法和我们做dp动态规划的时候指针标识相似,我们回忆一下,我们做dp的时候,大多时候dp【0】都是一个没有实际用处却需要初始化的对象,因为我们理想的从第一个数开始处理,但是创建的数组或者vector却是从0开始的。所以很多人在写dp的时候,会如下这样写,每次处理上一个节点。
for(int i=1...)
{
dp[i-1]....
}
同样的,我们在进行链表遍历的时候,每次可以对i+1进行操作。
链表翻转最主要的内容就是改变指针指向,
注意
如果他本身就是张空表,我们直接返回即可。
非递归写法:
我们需要两个指针,一个指向当前节点prev,一个指向下一个节点next。
每次先获取当前节点的next,把他记录下来(为什么要先做这一步,你可以想象一下, 如果我们成功的把某一根箭头反转了,那么原本下一个位置依靠于当前节点的next,如果我们改变了)
主要步骤:初始化,改向,右移,初始化
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
//head表示当前节点,pre表示上一个节点,next表示下一个节点
if(!head) return head;//head为表头的指针,指向第一个节点,如果其为null(即0),那么这个表只有一个表头(空表),我们返回即可
ListNode *pre,*next;
pre=nullptr;//为什么我们要初始化pre,因为第一个节点的pre翻转过后就是最后一个节点的指向,即nullptr
while(head)
{
next=head->next;//next先把下一个地址存下来,避免改向后丢失
head->next=pre;//更改当前节点的next指针指向前一个元素(或者空)
//现在改向已经完成,接下来右移即可,即接下来需要初始化下一个节点的pre和下一个节点自己在哪
pre=head;//下一个节点的pre就是当前的节点head
head=next;//注意不能先改head,如果先改head,下一个节点必然找不到当前节点next了
}
//注意,我们此时已经翻转了链表,需要返回的是表头的指针。即最后一次循环的pre
return pre;
}
};
递归写法
递归写法在函数参数中得先告诉其pre为多少
ListNode* reverseList(ListNode *head,ListNode *pre=nullptr)
{
//递归写法先考虑循环结束时条件,即head指向了空
if(!head) return pre;
//每次记录下个节点,改向
ListNode *next=head->next;
head->next=pre;
return reverseList(next,head);//告诉下一个节点pre是当前的head,自己的位置在当前的next
如果只用一个参数,比较难理解一些:(理解下面这种解法对递归的理解会深刻一些)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
if(head==nullptr ||head->next==nullptr)//如果head本身是空或者是尾部节点,返回
{
return head;
}
ListNode* newhead=reverseList(head->next);//一直递归到5返回,即在第4层的时候,得到了第5层返回回来的新的头节点,注意当前是第4层
head->next->next=head;
head->next=nullptr;
return newhead;
}
};
创建两个指针dummy和node,两个开始都指向表头,最后dummy在表头,node指在表尾。
思路时分两个阶段,
第一阶段l1和l2任意一个都没走完。
第二阶段走完了其中一个。
第一阶段只需要比较每次l1和l2的大小,然后谁小就让node-next指向谁,随后node和小的都后移。知道某一条走完了。
这时比如说l1走完了,那么l1此时必定指向空,此时我们只需要让node->next指向非空的那一个就行,l1?判定为0,所以我们选l2.
非递归写法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
ListNode *dummy=new ListNode(),*node=dummy;//dummy作为表头,node作为最后一个节点
while(l1 &&l2)//先并完某一条
{
if(l1->val <= l2->val)
{
node->next=l1;
l1=l1->next;
}
else
{
node->next=l2;
l2=l2->next;
}
node=node->next;
}
node->next=l1 ? l1:l2;//剩下的指向哪个就接着指哪个
return dummy->next;
}
};
递归写法
递归总是先判断结束条件,上面我们也讨论过了,谁空了就指向另一个。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
if(!l1) return l2;
else if(!l2) return l1;
if(l1->val > l2->val)//l1>l2,即从l2指向l1
{
l2->next=mergeTwoLists(l1,l2->next);
return l2;
}
//else
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}
};
每次记录123节点为q1 q2 q3
递归写法:
我们首先确定递归的终止条件,就是q1已经为空或者q1已经到了4(即q1->next为空)
每次让q2->next=q1 即完成了2指向1,到最后返回q2就是头节点即要的结果。
那么q1指向谁呢
q1指向递归返回的下一个调整了顺序的头节点,即指向4(所以q1->next=swapxx()这个括号里应该是下一个q1,即此时的q3)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head || !head->next) return head;
ListNode *q1=head,*q2=q1->next,*q3=q2->next;
q2->next=q1;
q1->next=swapPairs(q3);
return q2;
}
};
非递归写法:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head)
{
if(!head || !head->next) return head;
ListNode *dummy=new ListNode();
dummy->next=head;
ListNode *t=dummy;
while(t->next && t->next->next)//注意这里为什么要加t->next->next,因为如果他的个数是奇数,比如最后多了一个5,也不需要交换就直接结束了
{
ListNode *q1=t->next,*q2=q1->next;
t->next=q2;
q1->next=q2->next;
q2->next=q1;
t=q1;
}
return dummy->next;
}
};
我们想象一下,如果两个链表长度相同,那么他们以相同的速度循环移动(即到了尾部就回到对方的头节点,一定要回到对方的头节点,否则他们的间距不会改变),必定能在交点碰见。
如果长度不同呢?
我们来看一下示例一:链1的长度为2,链2的长度为3,链3(假设右半部分记为链3,其并不是一个单独的链表)的长度为3.
共移动8次,由此我们推出:
记左上链长A,左下链长B,右链长C
必定在A+B+C次移动之后能相遇。(注意特殊情况如果有一方为空就一定相遇不了了)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
if(!headA ||!headB) return NULL;
ListNode *a=headA,*b=headB;
while(a!=b)
{
a=a? a->next:headB;
b=b? b->next:headA;
}
return a;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* insertionSortList(ListNode* head)
{
if(!head ||!head->next) return head;
ListNode *dummy=new ListNode();
dummy->next=head;
ListNode *cur=head->next,*last=head;
while(cur)
{
if(cur->val>=last->val)
{
last=last->next;
}
else
{
ListNode *pp=dummy;
while(cur->val>pp->next->val)
{
pp=pp->next;
}
//将cur插入到pp和pp->next之间
last->next=cur->next;
cur->next=pp->next;
pp->next=cur;
}
cur=last->next;
}
return dummy->next;
}
};
注意最好把链断开。避免无限递归。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head)
{
if(!head || !head->next) return head;//为空或者只有一个
auto slow=head,fast=head;
while(fast->next &&fast->next->next)
{
slow=slow->next;
fast=fast->next->next;
}
//把链断开
fast=slow->next;
slow->next=nullptr;
return mergelist(sortList(head),sortList(fast));
}
ListNode* mergelist(ListNode* head1,ListNode* head2)//合并两个有序链表
{
ListNode* dummy=new ListNode();
ListNode* temp=dummy,*temp1=head1,*temp2=head2;
while(temp1 &&temp2)
{
if(temp1->val<temp2->val)
{
temp->next=temp1;
temp1=temp1->next;
}
else
{
temp->next=temp2;
temp2=temp2->next;
}
temp=temp->next;
}
temp->next=temp1? temp1:temp2;
return dummy->next;
}
};
我们的主要思想很简单,先统计双方长度len1 len2,然后补全短的一方
注意示例三想告诉我们,最后的长度len,并不一定等于两者中的一个,因为进位的话会多一位。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//保存两个表头
ListNode *dummy1=l1,*dummy2=l2;
//先统计每个链表的长度
int len1=getlen(l1),len2=getlen(l2);
int len=len1-len2;
//补0
if(len>0)//len1长,在l2后面补0
{
while(l2->next)
{
l2=l2->next;
}
for(int i=1;i<=len;++i)//补len1-len2个
{
l2->next=new ListNode(0);
l2=l2->next;
}
}
else if(len<0)//len2长,在l1后面补
{
while(l1->next)
{
l1=l1->next;
}
for(int i=1;i<=len2-len1;++i)//补len2-len1个
{
l1->next=new ListNode(0);
l1=l1->next;
}
}
ListNode *first=dummy1,*last;
int last_add=0;
while(dummy1 &&dummy2)
{
int sum=dummy1->val +dummy2->val+last_add;
dummy1->val=(sum%10);
last_add=sum>9? 1:0;
last=dummy1;
dummy1=dummy1->next;
dummy2=dummy2->next;
}
//如果最后溢出了还有进位
if(last_add)
{
last->next=new ListNode(1);
last=last->next;
}
return first;
}
int getlen(ListNode *l)
{
int len=0;
while(l)
{
++len;
l=l->next;
}
return len;
}
};
解法一:把链表赋值给数组
过于low,不说了
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(!head) return false;
vector<int> num;
while(head)
{
num.push_back(head->val);
head=head->next;
}
int n=num.size(),l=0,r=n-1;
bool ans=true;
while(ans &&l<r)
{
if(num[l]==num[r])
{
++l,--r;
}
else
ans=false;
}
return ans;
}
};
解法二:翻转后半部分然后判断
需要如下几个辅助函数:
1.判断中点在哪
2.翻转后半部分链表
3.比较两个链表
怎么找中点在哪?你可能想先算出他的长度l,然后再找到一半。但是这样遍历了两次链表。我们在想有没有遍历一次就能找到中点的方法。
快慢指针
定义fast 和slow两个指针,fast指针每次走两步,而slow指针每次走一步。我们来简单验证一下这个猜想对不对。
假设节点有偶数个,比如6个
f 1 3 5
s 1 2 3
此时fast->next->next为空就得终止,终止时slow指向的就是中点。
假设节点有奇数个,比如5个
f 1 3 5
s 1 2 3
此时fast->next为空就得终止
综上while循环的条件得是
while(fast->next && fast->next->next)
{
fast=fast->next->next;
slow=slow->next;
}
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(!head) return false;
ListNode *mid=findmid(head);//第一步,找出中点
ListNode *rhead=reverselistnode(mid->next);//第二步,以mid的下一个节点为头节点翻转后续节点
//第三步,比较两个链表
while(head && rhead)
{
if(head->val!=rhead->val)
return false;
head=head->next;
rhead=rhead->next;
}
return true;
}
ListNode* findmid(ListNode *head)
{
ListNode *fast=head,*slow=fast;
while(fast->next &&fast->next->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
ListNode *reverselistnode(ListNode *head)
{
ListNode *pre=nullptr,*next;
while(head)
{
next=head->next;
head->next=pre;
pre=head;
head=next;
}
return pre;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n)
{
//定义双指针相隔n-1,每次以步长1移动。
ListNode *fast,*slow,*dummy=new ListNode();
dummy->next=head;
if(!head->next)//如果只有一个节点,又因为我至少要删除一个节点,所以返回空指针
{
delete head;
return nullptr;
}
//右移fast指针,初始化其位置
fast=head,slow=dummy;
int k=n-1;
while(k!=0)
{
fast=fast->next;
--k;
}
//双方均以步长为1右移
while(fast &&fast->next)
{
fast=fast->next;
slow=slow->next;
}
//删除slow的下一个节点
ListNode *need_re=slow->next;
slow->next=slow->next->next;
delete need_re;
return dummy->next;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(!head || !head->next) return head;
ListNode *q=head;
while(q && q->next)
{
if(q->val == q->next->val)
{
ListNode *n=q->next;
q->next=n->next;
delete n;
}
else
q=q->next;
}
return head;
}
};
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* oddEvenList(ListNode* head)
{
if(!head ||!head->next ||!head->next->next) return head;
ListNode *q=head,*p=head->next,*s=p;
bool flag=true;
while(p&&p->next)
{
if(flag)//奇数
{
q->next=p->next;
q=q->next;
flag=false;
}
else//偶数
{
p->next=q->next;
p=p->next;
flag=true;
}
}
q->next=s;
return head;
}
};
思路一:
既然对于每个小list,我们每次都需要找出开头最小的元素。
可以使用小根堆/优先队列( priority_queue
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists)
{
if(!lists.size()) return nullptr;
priority_queue<int,vector<int>,greater<int> > minHeap;//小根堆
for(ListNode *x:lists)//把所有节点的val都加入小根堆
{
while(x)
{
minHeap.push(x->val);
x=x->next;
}
}
ListNode *dummy = new ListNode(),*x=dummy;
while(!minHeap.empty())
{
int val = minHeap.top();
minHeap.pop();
x->next=new ListNode(val);
x= x->next;
}
return dummy->next;
}
};