24.两两交换链表中的节点
题目:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
思路
这道题目正常模拟就可以了。
你要想相邻两个节点交换以后整个链表的指针指向关系是什么样的
建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且注意要操作的先后顺序
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
看这个可能就更直观一些了:
概述:设置虚拟头结点后正常模拟,以 head = [1,2,3,4] 为例,交换一对节点分为三步进行:
操作前链表为:dummyHead -> 1 -> 2 -> 3 -> 4 -> null
第一步直接越过下一个节点1,将当前节点 dummyHead 的 next 指向节点2;
第二步再将节点2的 next 指向节点1;
第三步将节点1的 next 指向节点3,与原链表重新连接起来。
以上三步便可将一对节点交换,操作后链表变为:dummyHead -> 2 -> 1 -> 3 -> 4 -> null
之后遍历链表重复进行交换即可。
C代码如下:
struct ListNode* swapPairs(struct ListNode* head) {
//设置虚拟头结点,便于后续操作
struct ListNode* dummyHead=malloc(sizeof(struct ListNode));
dummyHead->next=head;
//创建curr指针用于遍历链表
struct ListNode* curr =dummyHead;
while(curr->next && curr->next->next){
//记录临时节点
struct ListNode* tmp1=curr->next;
struct ListNode* tmp2=curr->next->next->next;
//开始交换节点(三个步骤)
curr->next=curr->next->next;//第一步
curr->next->next=tmp1;//第二步
tmp1->next=tmp2;//第三步
//curr指针移动两位,准备下一对交换
curr=curr->next->next;
}
return dummyHead->next;
}
19.删除链表的倒数第N个节点
题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
思路
要注意,要删除第N个节点,那么我们当前遍历的指针一定要指向第N个节点的前一个节点。
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
分为如下几步:
- 定义fast指针和slow指针,初始值为虚拟头结点,如图:
fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
fast和slow同时移动,直到fast指向末尾,如图:
删除slow指向的下一个节点,如图:
C代码如下:
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
//创建虚拟头结点
struct ListNode* dummyHead=malloc(sizeof(struct ListNode));
dummyHead->next=head;
//创建双指针
struct ListNode* fast=dummyHead;
struct ListNode* slow=dummyHead;
//fast先走n+1步
while(n--){
fast=fast->next;
}
fast=fast->next; //fast再提前走一步,因为需要让slow指向删除节点的上一个节点
//fast和slow同时移动直到fast指向NULL
while(fast){
fast=fast->next;
slow=slow->next;
}
//删除slow所指向的节点并释放结点空间
struct ListNode* tmp=slow->next;
slow->next=slow->next->next;
free(tmp);
//注意返回的是dummy->next而不是head
return dummyHead->next;
}
难点:想明白fast指针到底一开始先走n步还是n+1步?
面试题02.07.链表相交
题目:给你两个单链表的头节点 headA
和headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回null
。
大家注意数值相同,不代表指针相同。
思路
简单来说,就是求两个链表交点节点的指针。 这里要注意,交点不是数值相等,而是指针相等。
概述:先分别计算出两个链表的长度并计算长度差gap,之后让较长的链表向前移动 gap 步,这样两个链表就实现了尾部对齐(即操作两个链表的指针在同一起点上)。最后遍历两个链表判断是否有交点。
C代码如下:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
//使用两个指针用于遍历两个链表
struct ListNode* currA=headA;
struct ListNode* currB=headB;
//统计两个链表的长度
int lenA=0,lenB=0;
while(currA){
lenA++;
currA=currA->next;
}
while(currB){
lenB++;
currB=currB->next;
}
//统计完长度让currA,currB回到起始点
currA = headA;
currB = headB;
//让currA指向较长的链表
if(lenB>lenA){
struct ListNode* temp=currA;
currA=currB;
currB=temp;
}
//计算两个链表的长度差
int gap=lenA > lenB ? lenA-lenB : lenB-lenA;
//让两个链表尾部对齐
while(gap--){
currA=currA->next;
}
//遍历两个链表,若遇到相同说明相交,返回相交节点
while(currA){
if(currA==currB){
return currA;
}
currA=currA->next;
currB=currB->next;
}
//若无相交则返回空指针
return NULL;
}
142.环形链表II
算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口
题目: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
思路
这道题目,不仅考察对链表的操作,而且还需要一些数学运算。
主要考察两个知识点:
- 判断链表是否有环
- 如果有环,如何找到这个环的入口
判断链表是否有环
可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
为什么fast一次走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢
首先第一点:因为fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。
为什么fast指针和slow指针一定会相遇呢?
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
动画如下:
https://code-thinking.cdn.bcebos.com/gifs/141.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8.gif
如果有环,如何找到这个环的入口
此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。
1.当 slow 入环后,对 slow 来说,fast 是在追赶它,而且每次都会靠近一个节点,因此一定可以追到。
2.由于 slow 每次只走一步,且 fast 每次都会追到一步,而 fast 距离 slow(相对 fast 追 slow 来说,fast 在 slow 之后)最多不会超过环的所有结点数,因此 fast 一定会在 slow 走完环的一圈之前追到slow。
结论: 从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点,那么当这两个指针相遇的时候就是环形入口的节点。
也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。
让index1和index2同时移动,每次移动一个节点,那么他们相遇的地方就是 环形入口的节点。
动画如下:
C代码如下:
struct ListNode *detectCycle(struct ListNode *head) {
//定义快慢指针
struct ListNode* fast=head;
struct ListNode* slow=head;
//先找相遇交点,再找环形入口
while(fast && fast->next){
fast=fast->next->next;
slow=slow->next;
//快慢指针相遇 ,此时让两个指针分别从head和相遇点出发直至相遇即为环形入口
if(fast==slow)
{
while(slow!=head){
slow=slow->next;
head=head->next;
}
return head;//返回环的入口
}
}
return NULL;
}
写在最后:今天的几道题很锻炼脑子,感想就是做算法题,脑子常用常新。
如果某道题一时想不明白,你可以先记住这道题的关键解题点.例如这道环形链表题里如何找环形入口的结论。