代码随想录算法训练营第4天|24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II。
学习文档链接: 代码随想录
一、24. 两两交换链表中的节点
链接: 两两交换链表中的节点
该题思路:
自己初始想法【错误想法】:没有考虑使用dummyHead,导致在后面返回头节点的时候找不到头节点,可能是受到上个题目反转链表的影响(因为上个题在最后返回的指针pre是头指针,这个题目不使用虚拟头节点的话找不到最后的头节点),在考虑的时候认为不需要使用虚拟头节点,所以没有解出来。。。
代码如下:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next!=nullptr &&cur->next->next!=nullptr){
ListNode* tmp1 = cur->next;
ListNode* tmp2 = cur->next->next->next;
cur->next = cur->next->next; //不需要存cur->next->next,因为第一次指向的时候能找到cur->next->next。
cur->next->next = tmp1;
cur->next->next->next = tmp2;
cur = cur->next->next;
}
ListNode* result = dummyHead->next;
return result;
}
};
二、19.删除链表的倒数第 N 个结点
链接: 删除链表的倒数第 N 个结点
思路:
由于无法直接从后面遍历链表,同时链表长度也未知,那么要在只遍历一遍链表的情况下实现该题,只能使用双指针slow和fast,先让fast前进n步,然后fast和slow同时前进,直到fast走到末端,此时slow所指向的元素正是倒数底n个节点。
由于删除链表操作需操作前一节点,所以直接使用虚拟头节点最方便。
代码如下:
/**
*2024-05-13 想到了删除第k个(k=链表长度-n+1),但没有后续想法了
**/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* fast = dummyHead;
ListNode* slow = dummyHead;
while(n--){
fast = fast->next;
}
while(fast->next){
fast = fast->next;
slow = slow->next;
}
ListNode* tmp = slow->next;
slow->next = slow->next->next;
delete tmp;
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
};
三、面试题 02.07. 链表相交
链接: 链表相交
初始想法:
在看到此题目时,意识到可能需要使用"先走差距步"方法来遍历,但是错误得认为在两个链表的交点后面的链表可能分叉,其实是不可能分叉的,因为一旦指针相同之后,后续指针只能指向一个节点,所以相交后的链表节点一定是相等的。
代码如下:
/**2024-05-13
* 有一个关键点是:如果两个链表相交,那么从交点以后的链表一定是相同的,因为多个指针可以指向同一个节点,但一个指针不能指向多个节点,所以一旦有交点,那么交点后面的一定相同。
*基于上面的关键点,我们就可以通过两条链表末端对齐来找交点。
**/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int sub = 0;
int lenA = 0;
int lenB = 0;
while(curA){
lenA++;
curA = curA->next;
}
while(curB){
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
if(lenA>lenB){
sub = lenA-lenB;
while(sub--){
curA = curA->next;
}
}
else{
sub = lenB-lenA;
while(sub--){
curB = curB->next;
}
}
while(curA!=nullptr&&curB!=nullptr){
if(curA==curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return nullptr;
}
};
四、环形链表II
链接: 环形链表II
思路:
解决两个问题:
1、检测链表有无环:使用快慢指针,fast走两步,slow走一步,如果fast能赶上slow,那么一定是有环的,否则就没环。
2、有环如何找入环节点:经过公式推导发现,从头节点和fast与slow相遇的节点分别出发两个指针index1,index2,index1走到入环点时,index2一定也在入环点,无论index2在环内转了几圈。
代码如下:
/**
*2024-05-13
*两个问题:
*1、检测链表有无环:使用快慢指针,fast走两步,slow走一步,如果fast能赶上slow,那么一定是有环的,否则就没环。
*2、有环如何找入环节点:经过公式推导发现,从头节点和fast与slow相遇的节点分别出发两个指针index1,index2,index1走到入环点时,index2一定也在入环点,无论index2在环内转了几圈。
**/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
ListNode* middle = nullptr;
//检测是否有环
while(fast&&fast->next){//我在此还加了fast->next->next,程序运行正常,但是随想录代码中并没有判断这个,具体不太清楚,可能原因是(因为fast->next->next=0时直接退出循环)。
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
middle = fast;
break;
}
}
if(!middle){
return nullptr;
}
//有环找入环节点
ListNode* index1 = head;
ListNode* index2 = fast;
while(index1&&index2){
if(index1 == index2){
return index1;
}
index1 = index1->next;
index2 = index2->next;
}
return index1;
}
};
总结
1、在操作链表时候,一定要注意哪些节点在重新连接之后就找不到了,要将其存起来用来继续遍历或者删除时用。 2、在链表操作中,”先走差距步“的遍历方法一定要掌握 3、判断链表是否有环通过快慢指针实现,快指针走两步,慢指针走一步,快能追上慢则一定存在环,否则不可能存在 4、判断入环口:分别从head和相遇点出发两个指针,直到两个相遇,那么该相遇点必是入环口