力扣题目链接
文章链接:24.两两交换链表中的结点
视频链接:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点
状态:第一次提交报错RE,主要原因在于循环条件没把握好。对空指针尽兴了操作。
循环条件必须是逻辑&&,而不是||,因为循环体内涉及到了node.next.next.next。
画一个图,很容易能发现,cur结点必须在待交换的两个结点之前,并且我们还需要一个temp结点保存待交换两个结点之后的一个结点。如果我们想交换相邻两个结点,还需要第二个temp1来保存其中一个结点位置来完成交换。
交换逻辑如下:cur在虚拟头结点位置,首先让cur指向结点2,然后让2指向结点1,最后让结点1指向结点3。
综上所属,我们需要设计变量:虚拟头结点、当前结点、暂存结点1、暂存结点2.
然后就是遍历结点时的循环条件一定要注意,cur结点的下一个结点和下下个结点都不能为空,不然就会访问一个空指针的成员,肯定会报错。
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移动两位,准备下一轮交换
}
return dummyHead->next;
}
};
力扣题目链接
文章链接:19.删除链表的倒数第N个结点
视频链接:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点
状态:毫无思路,只想到了先遍历一次链表,拿到size,用size-1的暴力解决办法;那么一趟扫描如何找到倒数第N个结点呢?答案就是双指针大法!构造速度差
本题我觉得最重要的就是一趟扫描的基础上找到倒数第N个结点。
我们设置快慢两个指针,快指针比慢指针领先n个,如果等到快指针指向链尾的NULL,就说明慢指针已经指向了倒数第N个结点。
想要删除某个节点,我们的当前结点一定要在待删除的结点之前,这样才能顺利完成删除(画图即可理解)。所以有两种处理办法,一种是再设置一个cur结点,让它始终在slow前面,另外一种就是让fast再比slow多跑一步,让slow结点指向删除结点的上一个结点。这样的话对fast的操作就比较讲究,在遍历fast的时候,注意while中的条件,再一个就是让fast多跑一步。
关于是否虚拟结点就要看我们是否要对只有头结点的情况进行操作,本题中完全有可能删除的结点就是头结点,所以为了代码的统一性,我们需要设置虚拟头结点
释放虚拟头结点
ListNode* ans = dummy->next;
delete dummy;
return ans
释放被删除结点内存
// 保存要删除的节点,以便后续释放内存
ListNode* toDelete = slow->next;
// 删除节点
slow->next = slow->next->next;
// 释放被删除节点的内存
delete toDelete;
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* slow = dummyHead;
ListNode* fast = dummyHead;
// 让fast指针先移动n步
while(n-- && fast != NULL) {
fast = fast->next;
}
// 让fast再提前走一步,因为需要让slow指向删除节点的上一个节点
fast = fast->next;
// 移动fast和slow,直到fast指向链表末尾
while (fast != NULL) {
fast = fast->next;
slow = slow->next;
}
// 保存要删除的节点,以便后续释放内存
ListNode* toDelete = slow->next;
// 删除节点
slow->next = slow->next->next;
// 释放被删除节点的内存
delete toDelete;
// 保存返回结果,并释放虚拟头结点的内存
ListNode* result = dummyHead->next;
delete dummyHead;
return result;
}
};
力扣题目链接
文章链接:面试题 02.07. 链表相交
状态:完成,其实本题和上一题一样,唯一的不同就在于次题确实要遍历出整个链表长度。
链表相交处并不是值相等,而是地址相等。
本题最大的难度就是链表长度不一样,无法找到相交处的主要原因就是,遍历速度不一样。
综上,我们把他们遍历速度变成一致的不就好了?所以假设链表A的长度lengthA,链表B的长度lengthB。如果A比B长的话,sub = lengthA - lengthB。我们让A想跑sub步,这样不就使得A和B的长度一致了吗?
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int lenA = 0, lenB = 0;
while (curA != NULL) { // 求链表A的长度
lenA++;
curA = curA->next;
}
while (curB != NULL) { // 求链表B的长度
lenB++;
curB = curB->next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
swap (lenA, lenB);
swap (curA, curB);
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap--) {
curA = curA->next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != NULL) {
if (curA == curB) {
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
力扣题目链接
文章链接:142.环形链表II
视频链接:把环形链表讲清楚!| LeetCode:142.环形链表II
状态:想到了用快慢指针的办法,但是思路还是有问题,一定要分开讨论什么时候有环,在找到环了基础上如何推理出环的入口,同时需要用一点纯数学的思路
设计两个指针相遇,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。
在文章链接中展现了完整的推导过程。142.环形链表II
其中有一段数学推导,最主要的就是证明:
如果有环,那么fast和slow肯定在环内相遇,我们再设计两个指针,一个index1从相遇点开始遍历,一个index2从头结点开始,他们速度一样,并且根据推导,二者肯定会在环口相遇,这样就通过index2找到了环的入口处。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
// 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
if (slow == fast) {
ListNode* index1 = fast;
ListNode* index2 = head;
while (index1 != index2) {
index1 = index1->next;
index2 = index2->next;
}
return index2; // 返回环的入口
}
}
return NULL;
}
};