写在前面
更新情况记录:
最近更新时间 | 更新次数 |
---|---|
参考博客与书籍以及链接:
(非常感谢这些博主们的文章,将我的一些疑问得到解决。)
参考博客链接或书籍名称 |
---|
深入理解快慢指针算法 – 链表环路检测 - 知乎 (zhihu.com) |
代码随想录 |
总目录: |
说明:本篇文章知识脉络并非采用线性顺序,有所不会请看目录,根据知识点来查看。
未完待续,最核心的知识已经写完了。
线性表题目系列,预计两章:单链表、顺序表。
题目来源:牛客跟力扣(待更新)。
零、知识图谱
一、单链表必会题
必会题来源于牛客力扣
1.移除链表元素
题目
题解
(1)可以在单链表元素之前设置一个哨兵头,这样的话删除链表的头结点跟其他结点的方法一致。
(2)先找到要删除结点的前一个结点,然后前一个结点的next更新为要删除结点的next,释放要被删除的结点。
(3)来看看如何设置一个虚拟头(哨兵头)。依然还是在这个链表中,移除元素1。
(4)图解
实现注意点
(1)不过oj题中基本不带头,题目也没有说明链表带头所以我们需要自己创建一个哨兵头。
struct ListNode * guard=(struct ListNode*)malloc(sizeof(struct ListNode));
需要注意的是,最后哨兵头需要被释放,头结点更新,头结点指向哨兵结点的下一个。
head=guard->next;
free(guard);
代码
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode* cur=head;
//创造哨兵节点
struct ListNode* guard=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* tail=guard;
while(cur)
{
if(cur->val!=val)
{
//后一个节点更新
tail->next=cur;
tail=tail->next;
//前一个节点更新
cur=cur->next;
}
else
{
struct ListNode* del=cur;
cur=cur->next;
free(del);
}
}
if(tail)
tail->next=NULL;
head=guard->next;
free(guard);
return head;
}
2.反转一个单链表
题目
题解(方法一)
(1)尾插变头插,取结点头插到新链表
(2)代码细节过程图解
方法一代码
struct ListNode* cur=head;
struct ListNode* newhead=NULL;
while(cur)
{
struct ListNode* next=cur->next;
cur->next=newhead;
newhead=cur;
cur=next;
}
return newhead;
题解(方法二)
(1)翻转方向
(2)代码实现细节图解
方法二代码
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* n1,*n2,*n3;
//防止链表为空n3赋值放在循环里面
n1=NULL,n2=head,n3=NULL;
while(n2)
{
n3=n2->next;
n2->next=n1;
//迭代
n1=n2;
n2=n3;
}
return n1;
}
3.链表的中间结点
题目
题解
(1)通过快慢指针,可以只遍历一遍链表就能得出正确答案
(2)快指针一次走两步,慢指针一次走一步,快指针走完链表,慢指针刚好在一半处。
(3)图解
代码
struct ListNode* middleNode(struct ListNode* head){
//快慢指针
struct ListNode* f=head,*s=head;
//注意两个判断条件
while(f&&f->next)
{
s=s->next;
f=f->next->next;
}
return s;
}
4. 链表中倒数第k个结点
题目
[链表中倒数第k个结点_牛客题霸_牛客网 ](链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com))
题解
(1)方法还是快慢指针
(2)fast先走一步,fast跟slow再同时走,最后slow停在的地方就是倒数第一个。
类推,fast先走k步,fast跟slow再同时走,最后slow停在的地方就是倒数第几个。
(3)图解:
注意
(1)为什么fast先走的循环中使用k–,使用–k可以吗?
代码while(k–),k–的含义:每次先用k,然后再自减。每次循环k的变化(以上图为例):3,2,1。当k==0的时候不执行。
当while(–k)时,–k的含义:每次用k的时候先自减。k的变化:2,1。导致就执行两次。
(2)链表可能为空需要判断。
代码
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
struct ListNode *fast,*slow;
fast=slow=pListHead;
//fast先走n步
while(k--)
{
//当链表为空的时候,返回空值
if(!fast)
{
return fast;
}
fast=fast->next;
}
//fast跟slow一直走到fast为空的时候
while(fast)
{
fast=fast->next;
slow=slow->next;
}
return slow;
}
5.合并两个有序链表
题目
题解
(1)写一个哨兵头,比大小,小的只管尾插,不需要考虑头插。
(2)图解
代码
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
struct ListNode* guard =(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* cur1,*cur2,*newhead,*cur3;
guard->next=NULL;
cur1=list1,cur2=list2,newhead=cur3=guard;
//小的尾插
while(cur1&&cur2)
{
if(cur1->val>=cur2->val)
{
cur3->next=cur2;
cur3=cur3->next;
cur2=cur2->next;
}
else
{
cur3->next=cur1;
cur3=cur3->next;
cur1=cur1->next;
}
}
//剩余的尾插
if(cur1)
{
while(cur1)
{
cur3->next=cur1;
cur3=cur3->next;
cur1=cur1->next;
}
}
else
{
while(cur2)
{
cur3->next=cur2;
cur3=cur3->next;
cur2=cur2->next;
}
}
//释放哨兵结点,更新newhead的位置
newhead=guard->next;
free(guard);
return newhead;
}
6.给定值x为基准将链表分割
编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结
点之前
题目
[链表分割](链表分割_牛客题霸_牛客网 (nowcoder.com))
题解
(1)创建两个哨兵结点,一个哨兵结点(暂且叫它lessGuard)后面插入的是小于x值的结点,另一个哨兵结点(叫它greaterGuard)后面插入的是大于x值的结点。
(2)过程图解:
注意
(1)greaterGuard指向的那条链表最后一个结点的next为什么要处理?
如果greaterGuard指向的那条链表的最后一个结点在原链表中不是最后一个结点的话,那么就会指向lessGuard指向的链表中的最后一个结点,如图所示:
两个链表合并的话就会出现下面这张图的情况,即形成一个环:
代码
ListNode* partition(ListNode* pHead, int x) {
// write code here
//lessGuard:小于x的结点的链表的哨兵头,lessTail:用于操作链表
//greaterGuard:大于x的结点的链表的哨兵头,greaterTail:用于操作链表
//tail用于遍历原链表
struct ListNode *lessGuard,*lessTail,*greaterGuard,*greaterTail,*tail;
lessGuard=(struct ListNode*)malloc(sizeof(struct ListNode));
greaterGuard=(struct ListNode*)malloc(sizeof(struct ListNode));
lessTail=lessGuard;
greaterTail=greaterGuard;
tail=pHead;
//结点根据大小尾插到相应的链表后面
while(tail)
{
if(tail->val<x)
{
lessTail->next=tail;
tail=tail->next;
lessTail=lessTail->next;
}
else
{
greaterTail->next=tail;
tail=tail->next;
greaterTail=greaterTail->next;
}
}
//最后两个链表合并
lessTail->next=greaterGuard->next;
//greaterTail->next后面应该是NULL
//否则可能形成环
greaterTail->next=NULL;
pHead=lessGuard->next;
//释放掉两个哨兵结点
free(greaterGuard);
free(lessGuard);
return pHead;
}
7. 链表的回文结构
题目
[链表的回文结构](链表的回文结构_牛客题霸_牛客网 (nowcoder.com))
题解
(1)可以先用快慢指针,找出链表的中间点,然后将后半段链表反转,最后两个链表进行比较。
(2)这题可以看成是第2题与第3题的结合。
(3)图解
代码
bool chkPalindrome(ListNode* A) {
// write code here
struct ListNode* f,*s;
f=s=A;
while(f&&f->next)
{
f=f->next->next;
s=s->next;
}
struct ListNode *cur=s, *n = NULL;
while(cur)
{
struct ListNode* next=cur->next;
cur->next=n;
n=cur;
cur=next;
}
struct ListNode *cur1=A;
while(n&&cur1)
{
if(n->val!=cur1->val)
{
return false;
}
n=n->next;
cur1=cur1->next;
}
return true;
}
8.相交链表
输入两个链表,找出它们的第一个公共结点。
题目
题解
(1)a.先可以遍历一遍,如果两个链表尾结点的地址相同就说明两个链表相交。反之,则不想交。
b.通过a步骤的遍历,分别求出两个链表的长度,然后 长度长的链表先走差距步(两个链表的长度差)。走完之后,同时走,第一个地址相等的结点就是相交结点。
(2)图解:
代码
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//1.分别遍历
struct ListNode* curA,*curB;
curA=headA,curB=headB;
//遍历判断条件是结点下一个不为空
//防止空结点
if(headA==NULL||headB==NULL)
{
return NULL;
}
//len要从1开始
int lenA=1,lenB=1;
while(curA->next)
{
lenA++;
curA=curA->next;
}
while(curB->next)
{
lenB++;
curB=curB->next;
}
//如果不相等就返回空
if(curA!=curB)
{
return NULL;
}
//2.长的走差距步
//默认长链表是A,短链表是B(可以少写判断)
struct ListNode* longList = headA,*shortList = headB;
if(lenB>lenA)
{
longList=headB;
shortList=headA;
}
//差距步总不能是负的吧
int gap = abs(lenA-lenB);
while(gap--)
{
longList=longList->next;
}
//3.开始同时走
while(longList!=shortList)
{
longList=longList->next;
shortList=shortList->next;
}
return longList;
}
9.判断链表中是否有环
给定一个链表,判断链表中是否有环。
题目
题解
(1)快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表其实位置开始运行,
如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。
(2)注意:这题切不能遍历,一遍历就死循环。
(3)图解:
注意
关于这题的问题请看这篇文章:深入理解快慢指针算法 – 链表环路检测 - 知乎 (zhihu.com)
代码
bool hasCycle(struct ListNode *head) {
struct ListNode* fast,*slow;
fast=slow=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
if(fast==slow)
return true;
}
return false;
}
10. 返回链表开始入环的第一个结点
给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL
题目
题解:
(1)可以用公式证明:
a.fast走的距离是2*slow走的距离。
b.假设进环前的长度为L,假设环的长度为C。
假设入口点到相遇点距离为X。
假设slow进环前,fast在环里面转了N圈,N>=1。
slow走的距离是L+X。
fast走的距离为L+N*C+X。
c.因此 2 * slow = L + N * C + X
化简一下: L=(N-1) * C + C - X。
代码
struct ListNode *detectCycle(struct ListNode *head) {
//公式法
//1.找到相遇点
struct ListNode* fast,*slow;
struct ListNode* A=head,*B=NULL;
fast=slow=head;
if(fast==NULL)
{
return NULL;
}
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
B=fast;
break;
}
}
//如果B还是NULL,说明不是环
if(B==NULL)
{
return NULL;
}
//2.A与B走的长度相同
while(A!=B)
{
A=A->next;
B=B->next;
}
return B;
}
11.复制带随机指针的链表
给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点
或空结点。要求返回这个链表的深度拷贝。
题目
题解
1.遍历链表,在每一个结点后面复制本结点,然后复制结点尾插在本结点的后面。
2.更新random,复制结点的random在本结点的后一个(即这个结点的复制结点)。
3.最后将链表的复制结点拿出来形成一个新链表,将原本的链表还原。
4.图解:
代码
struct Node* copyRandomList(struct Node* head) {
//1.拷贝原结点,链接在原结点的后面
// if(head==NULL)
// return NULL;
struct Node* cur=head;
struct Node* next=NULL;
while(cur)
{
next=cur->next;
struct Node* copy=(struct Node*)malloc(sizeof(struct Node));
copy->val=cur->val;
cur->next=copy;
copy->next=next;
cur=next;
}
//2.更新每个拷贝节点的random
cur = head;
while(cur)
{
struct Node* copy=cur->next;
//分两种情况
//1.random结点为空
//2.random结点不为空
if(!cur->random)
{
copy->random=NULL;
}
else
{
copy->random=cur->random->next;
}
//迭代
cur=cur->next->next;
}
//3.恢复原链表
//搞一个链表头
struct Node* guard=(struct Node*)malloc(sizeof(struct Node));
guard->next=NULL;
struct Node* newhead=guard;
cur=head;
while(cur)
{
struct Node* copy=cur->next;
next=copy->next;
//还原老链表
cur->next=next;
cur=next;
//更新新结点
newhead->next=copy;
newhead=copy;
newhead->next=NULL;
}
newhead=guard->next;
free(guard);
return newhead;
}
{
copy->random=NULL;
}
else
{
copy->random=cur->random->next;
}
//迭代
cur=cur->next->next;
}
//3.恢复原链表
//搞一个链表头
struct Node* guard=(struct Node*)malloc(sizeof(struct Node));
guard->next=NULL;
struct Node* newhead=guard;
cur=head;
while(cur)
{
struct Node* copy=cur->next;
next=copy->next;
//还原老链表
cur->next=next;
cur=next;
//更新新结点
newhead->next=copy;
newhead=copy;
newhead->next=NULL;
}
newhead=guard->next;
free(guard);
return newhead;
}
本节完。