今天是链表章节最后一天,加油💪
24. 两两交换链表中的节点
题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:
输入:head = []
输出:[]
示例 3:
输入:head = [1]
输出:[1]
思路:本题需要借助几个临时节点完成交换,想要两辆交换节点,必须有临时节点保存第三个节点。假设前三个节点A、B、C,要交换AB节点的位置,需要新建一个虚拟头节点,并用指针curr
指向虚拟头节点,用临时节点temp
记录节点A,临时节点1temp1
记录节点C,交换代码就是curr->next = curr->next->next; curr->next->next = temp; curr->next->next->next = temp1;
也就是head->next = B; B->next = A; A->next = C;
(交换代码的含义,不能直接用这个,注意链表节点交换时,要提前保存next指针指向的下一个节点,不然容易丢失下一个节点)
C++版本
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *head_ = new ListNode(0, head);
ListNode *curr = head_;
while (curr->next != 0 && curr->next->next != 0) { // 顺序不能颠倒,不然会出现空指针异常
ListNode *temp = curr->next;
ListNode *temp1 = curr->next->next->next;
// 主要交换代码
curr->next = curr->next->next;
curr->next->next = temp;
curr->next->next->next = temp1;
curr = curr->next->next;
}
return head_->next;
}
};
这题不算难,就是要注意一些细节的问题。
19.删除链表的倒数第N个节点
题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
思路:注意是删除倒数第n个节点,不要看错题目要求了!这题我写了两种方法,一种是直接法,首先遍历一遍链表算出链表长度以及倒数第n个节点属于正数第几个节点,然后再遍历一次找到要删除的节点的前一个节点,最后进行删除操作;另一种是利用快慢指针,双指针的方法实在是太妙了,令快慢指针都指向虚拟头节点,快指针先走n步,然后快慢指针再同时往前走,当快指针走到链表的最后一个节点时,慢指针正好在倒数第n+1个节点,便可进行删除操作。
C++版直接方法
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) { //删除倒数第n个
ListNode *head_ = new ListNode(0, head); // 创建一个虚拟头节点
ListNode *curr = head_;
ListNode *temp = head_;
int l = 0; int t; //
while (temp != NULL) { // 计算链表长度(从1开始)
temp = temp->next;
l++;
t = l - n; // 计算循环次数
}
while (--t) {
curr = curr->next; // 遍历到需要删掉的节点的上一个节点
}
curr->next = curr->next->next;
return head_->next;
}
};
C++版双指针
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *head_ = new ListNode(0, head);
ListNode *fast = head_;
ListNode *slow = head_;
while (n-- && fast != NULL) {
fast = fast->next; // fast先走n步
}
while (fast->next != NULL) {
fast = fast->next; // 当fast走到最后一个节点时
slow = slow->next; // slow走到倒数第n个节点的前一个节点?
}
slow->next = slow->next->next;
return head_->next;
}
};
一定要掌握双指针方法的核心思想,并学会灵活应用!太好用了!
面试题 02.07. 链表相交
题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at ‘8’
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
思路:注意不是数值相等算相交,是节点一样才算相交,这题我一开始没有什么思路,看了Carl哥的视频茅塞顿开!首先要明确,从相交的节点开始,后面的节点都是一样的,直到结束。这可不是一句废话,这是解题的关键。所以将两个链表末尾对齐,从后半部分找重合的节点即可。
C++版本
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *head_A = new ListNode(0, headA);
ListNode *head_B = new ListNode(0, headB);
ListNode *curr_A = head_A;
ListNode *curr_B = head_B;
int len_A = 0; int len_B = 0;
while (curr_A->next != 0) { // 计算链表A的长度
len_A++;
curr_A = curr_A->next;
}
while (curr_B->next != 0) { // 计算链表B的长度
len_B++;
curr_B = curr_B->next;
}
curr_A = head_A;
curr_B = head_B;
if (len_B > len_A) { // 保证最长的是链表A
swap (curr_A, curr_B);
swap (len_A, len_B);
}
int t = len_A - len_B; // 将两链表末尾对齐,curr_A需要移动的次数
while (t--) {
curr_A = curr_A->next;
}
while (curr_A !=NULL) {
if (curr_A == curr_B) {
return curr_A; // 一个一个的返回重合的节点
}
curr_A = curr_A->next;
curr_B = curr_B->next;
}
return NULL;
}
};
这里的一个小tips是,将长链表放在curr_A
,将短链表放在curr_B
,方便了后续的操作。
142.环形链表II
题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
思路:不得不感叹双指针是真好用。本题需要一定的数学证明,详情请见代码随想录。本题第一步判断是否有环,第二步找环的入口。第一步:快指针一次走两步,慢指针一次走一步,当两指针相遇说明一定有环。第二步:令快指针起点为相遇点,慢指针起点为头节点,两指针每次都只走一步,下次相遇一定是在环的入口。具体证明见上述链接。
C++版本
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
while (fast != 0 && fast->next != 0) { // 快指针走两步,慢指针走一步
fast = fast->next->next;
slow = slow->next;
if (fast == slow) { // 快慢指针相遇,不能直接break,不然无法判断无环的情况
ListNode *index_f = fast; // 重新赋值index_f指针指向相遇点
ListNode *index_h = head; // 另一个index_h指针指向头节点
while (index_f != index_h) { // 再次相遇的点就是环的入口
index_f = index_f->next;
index_h = index_h->next;
}
return index_f;
}
}
return NULL; // 遍历结束快慢指针都没有相遇过,就没有环
}
};
其实本题也可以采用 19.删除链表的倒数第N个节点 类似的思路,在找到相遇点后,让快指针绕环走一圈,计算出环的长度L
;再将快慢指针重新指向头节点,这次让快指针先走L
步,慢指针再和快指针一起移动,每次移动一步;因为快指针比慢指针多走了一个环的距离,所以下一次相遇一定是在环的入口!
这是训练营小伙伴 芒果冰冰 的解法,大家都很棒!像大家学习,加油💪