第四天,第一个周末到啦,虽然只有4天,但也算坚持一周了ヾ(◍°∇°◍)ノ゙💪,编程语言:C++
目录
24.两两交换链表中的节点
文档讲解:代码随想录两两交换链表中的节点
视频讲解:手撕两两交换链表中的节点
题目:
初看: 第一想法是采取昨天的双指针方法,两个指针分别指向要交换的节点,接着先让第一个节点的next等于第二个节点的next,再让第二个节点的next等于第一个节点。这样就实现了第二个节点指向第一个节点,第一个节点指向第三个节点的交换。但细想发现,这样的话实际上第二个节点没有彻底取代第一个节点,也就是前一个节点没有指向新的“第一个节点”。因此还需要对前节点进行处理。也就是一次得处理三个节点。
学习:通过文档视频讲解发现,本题与之前的双指针方式不同,由于要同时处理三个节点,所以首先要明确当前节点cur是第几个节点。显然由于是单链表,只有next指针,因此当前节点cur为前一节点(要交换的两个节点的前一节点)能够直接处理后两个节点。同时为了避免节点的丢失,需要采用两个中间指针保存两个next数据。
代码:
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
dummyHead->next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
ListNode* cur = dummyHead;
while(cur->next != nullptr && cur->next->next != nullptr) {
ListNode* tmp = cur->next; // 记录临时节点
ListNode* tmp1 = cur->next->next->next; // 记录临时节点
cur->next = cur->next->next; // 步骤一
cur->next->next = tmp; // 步骤二
cur->next->next->next = tmp1; // 步骤三
cur = cur->next->next; // cur移动两位,准备下一轮交换
}
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
};
改进代码:在进行代码编写的过程中,我发现如果将步骤三与步骤二交换同样不会导致节点的丢失,同时还能够减少一个中间节点的保存。
//时间复杂度:O(n)
//空间复杂度:O(1)
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* temp = cur->next;
cur->next = cur->next->next; //第一步骤
temp->next = cur->next->next; //第三步骤
cur->next->next = temp; //第二步骤
cur = temp;
}
ListNode* result = dummyhead->next;
delete dummyhead;
dummyhead = nullptr;
return result;
}
};
收获:
- 链表的处理,最重要的是要确定一次要处理的节点有几个,且当前节点cur是哪个节点,只有确定好这两点,才能够有条不紊的进行下去。
- 同时在处理链表时,还需要时刻注意节点的丢失,合理的保存中间节点。
19.删除链表的倒数第N个节点
文档讲解: 代码随想录删除链表的倒数第N个节点
视频讲解:手撕删除链表的倒数第N个节点
题目:
初看: 第一个想法是先遍历链表,确定链表的个数size,然后再次遍历直到遍历到第size-n个节点,则下一个节点就是要删除的节点,之后进行节点的删除释放即可。(例如总共有10个节点,要删除的节点是倒数第2个节点,则遍历到第(10-2=8)个节点即可(遍历到要删除的节点的前一个节点)。该方法的时间复杂度为O(L),L为链表长度。
学习:通过文档视频学习到一个新的方法,使用双指针能够一次遍历找到要删除的目标节点。具体为设置一对快慢指针,初始均指向虚拟头节点(使得头节点能够同一处理),由于要删除倒数第n个节点,则可以让快指针先往前走n个节点。之后再让快慢指针同时往后走。直到快指针的下一个指针为空时停下,此时快指针指向了最后一个节点,而慢指针指向要删除的节点的前一个节点。(例如总共有5个节点,要删除倒数第2个节点,快指针遍历n个节点,到达了第2个节点,之后快慢指针同时往前走,快指针走到第三步时,到达第5个节点也就是最后一个节点,此时慢指针到达第三个节点,要删除的倒数第二个节点(第四个节点)的前一个节点。)
代码:
//时间复杂度O(n)
//空间复杂度O(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->next != nullptr) { //快指针先走n步
fast = fast->next;
}
while (fast->next != nullptr) { //快慢指针同时前进
slow = slow->next;
fast = fast->next;
}
ListNode* temp = slow->next;
slow->next = slow->next->next; //进行指针删除
delete temp;
temp = nullptr;
return dummyhead->next;
}
};
收获:
- 学习到双指针的另一个用法:确定链表倒数第n个节点。 这种方法,我认为重点在于确定快指针的终点,以及慢指针此时的位置。
面试题02.07.链表相交
文档讲解:代码随想录面试题 02.07. 链表相交
题目:
示例:
初看: 第一想法由于有两个链表,因此肯定需要有两个指针分别指向两个链表的节点。难点在于如何判断两个指针指向的节点是否为交点,以及如何保证两个节点能够在交点相遇。基于这两点有个想法,可以先分别遍历两个链表,确定两个链表的长度。两个链表如果相交的话,则后半段一定是相同的,因此确定好链表长度之后,可以让长的链表指针先走两个链表的差值,此时两个指针就走到了同一起跑线上,之后两个指针同时向后遍历,直到指向的是同一个指针则找到了交点,如果没有则不存在交点。
代码:
//时间复杂度:O(n+m) 两个链表的长度之和
//空间复杂度:O(1)
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
int nA = 0, nB = 0;
ListNode* curA = headA;
ListNode* curB = headB;
while (curA != NULL) { //求A链表长度
nA++;
curA = curA->next;
}
while (curB != NULL) { //求B链表长度
nB++;
curB = curB->next;
}
curA = headA;
curB = headB;
if (nA >= nB) { //判断哪个链表为长链表
int n = nA - nB;
while (n--) {
curA = curA->next;
}
while (curA != curB && curA != NULL && curB != NULL) {
curA = curA->next;
curB = curB->next;
}
}
else {
int n = nB - nA;
while (n--) {
curB = curB->next;
}
while (curA != curB && curA != NULL && curB != NULL) {
curA = curA->next;
curB = curB->next;
}
}
return curA; //找到交点则curA指向交点,未找到交点则curA此时也指向了NULL
}
};
收获:
- 本题主要就是在于逻辑分析和模拟过程,我认为重点在于去尝试进行模拟,很多题可能会有很简便的方式,但模拟法同样是重要的解决办法之一。
142.环形链表II
文档讲解:代码随想录环形链表II
视频讲解:手撕环形链表
题目:
示例:
初看: 第一想法是与前一道链表相交是否相同,能否通过双指针的方式找到相交节点。发现并不能,首先如果链表内有环的话,一次遍历无法确定链表内元素个数,也无法确定交点。无法解出。
学习:学习文档视频后发现本题可采用快慢指针的方式找到环的所在,即快指针每次走两步,慢指针每次走一步。如果有环的话,那快指针必定先进入环,且在环内循环移动,慢指针后进入环。又因为快指针相对于慢指针实际上只多移动一格,类似于跑步,由于只多移动一格,则快指针对慢指针进行超圈的时候,快慢指针一定会相遇,此时循环结束。同时如果没有环,则快指针会先到达NULL,循环结束,返回NULL。
找到环所在后,可以将整个链表分为三段:
而由于fast每次走两步,slow每次走一步,因此fast走过的节点数是slow走过的节点数的两倍。即x+y+n(y+z) = 2(x+y),则可以得到x=n(y+z)-y。由于n至少为1(fast至少转了一圈与slow相遇),则可以取一个y+z,上式变为x=z+(n-1)(y+z)。由于y+z就为环的长度,指针在内部每转一圈则走y+z步,则可以发现如果让一个节点index在fast与slow的相遇位置,一个节点在头节点,两个节点同时向后遍历,则节点ptr在旋转(n-1)圈后,再走z个节点到达环形入口点,此时头节点也到达环形入口点,也就是这两个节点一定会在环形入口点相遇。
代码:
//时间复杂度O(n)
//空间复杂度O(1)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
while (fast != NULL && fast->next != NULL) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
ListNode* ptr = head; //头节点开始移动
while (ptr != slow) { //solw节点作为index节点
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
}
return NULL;
}
};
收获:
- 通过本题了解到了一种找到链表内是否有环,环的交点在哪的方法。采用快慢指针相对移动一格来找到环内所在,并且只能是一格,如果超过1格则可能会出现套圈,无法相遇的情况。
- 分析链表环的时候,通过例图分析更加直观,也考验了数学逻辑能力。
总结:
第四天刷题,环形链表II未能解出,数学逻辑能力还不够,继续刷题💪