刷题记录Day4-链表(两两交换链表中的节点、删除链表的倒数第N个节点、链表相交、环形链表)
文章目录
前言
题目来源:leetcode
刷题顺序:代码随想录
刷题工具:VSCode+leetcode插件
补充:会结合LeetCode 101: A LeetCode Grinding Guide (C++ Version)相似题目一起做。
一、两两交换链表中的节点
1. 24两两交换链表中的节点
题目:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
代码:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* dummyhead = new ListNode(0,head);
ListNode* cur = dummyhead;
while(cur->next !=nullptr && cur->next->next !=nullptr){
//例如:dh->1->2->3->4
//tmp1用来保存1,tmp2用来保存3
ListNode* tmp1 = cur->next;
ListNode* tmp2 = cur->next->next->next;
cur->next =cur->next->next;
cur->next->next = tmp1;
cur->next->next->next = tmp2;
cur = cur->next->next; //每次移动两格
}
return dummyhead->next;
}
};
二、删除链表的倒数第N个节点
1. 19删除链表的倒数第N个节点
题目:
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
代码:
方法一,最开始写的版本,一次ac
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0,head);
ListNode* cnt = dummyhead;
int sz = 0;
while(cnt->next != nullptr){
++sz;
cnt = cnt->next;
}
ListNode* cur = dummyhead;
for(int i =0; i<sz-n; ++i){
cur = cur->next;
}
ListNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
tmp = nullptr;
return dummyhead->next;
}
};
方法二:
进阶问题是一次遍历,所以想到第二个双指针的办法。
没看题解就想出来而且两次就ac了!牛牛牛!而且只用写一个while循环!
需要注意第一次为什么没ac,因为cur需要移动到要删除节点的前一个节点,所以要把n+1,这样cur会少移动一次从而到达需要删除节点的前一个位置!
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyhead = new ListNode(0,head);
ListNode* cnt = dummyhead;
ListNode* cur = dummyhead;
int sz = 0;
while (cnt->next != nullptr){
cnt = cnt->next;
++sz;
if(sz == n+1){
cur = cur->next;
--sz;
}
}
ListNode* tmp =cur->next;
cur->next = cur->next->next;
delete tmp;
return dummyhead->next;
}
};
三、 链表相交
1. 160链表相交
题目:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
示例:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
代码:
方法一:暴力解法。蠢的很,但我没想到其他解法。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* dummyheadA = new ListNode();
dummyheadA->next=headA;
ListNode* dummyheadB = new ListNode();
dummyheadB->next=headB;
ListNode* curA = dummyheadA;
while (curA->next != NULL){
ListNode* curB = dummyheadB;
while (curB->next != NULL){
if(curB->next == curA->next){
return curB->next;
}
curB = curB->next;
}
curA = curA->next;
}
return NULL;
}
};
方法二:
双指针,没想到还可以这样。看题解看到的。
让两个指针走完各自的路后再走对方的路。
两者的路程是a+c+b = b+c+a,所以如果有交点肯定会在交点相遇。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
while (curA != curB){
curA = curA->next;
curB = curB->next;
if(curA == NULL && curB == NULL){
return NULL;
}
if(curA == NULL){
curA = headB;
}
if(curB == NULL){
curB = headA;
}
}
return curA;
}
};
方法三:
卡哥的方法,长-短得到一段距离,让长的链表先移动这段距离,然后两个指针同时移动。这个方法也不错。就是算链表长度要把两个链表都先遍历一遍。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* curA = headA;
ListNode* curB = headB;
int sizeA=0, sizeB = 0;
while (curA != NULL){
++sizeA;
curA = curA->next;
}
while (curB != NULL){
++sizeB;
curB = curB->next;
}
curA = headA;
curB = headB;
if(sizeB>sizeA){
swap(sizeA,sizeB);
swap(curA,curB);
}
int gap = sizeA-sizeB;
while(gap--){
curA = curA->next;
}
while(curA != NULL){
if(curA == curB){
return curA;
}
curA = curA->next;
curB = curB->next;
}
return NULL;
}
};
四、环形链表
1. 142环形链表II
题目:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
代码:
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
//这题在leetcode101做过,试试能不能直接ac。
//用双指针思想,一快一慢。
ListNode* fast = head;
ListNode* slow = head;
while (fast!= NULL && fast->next != NULL){
fast = fast->next->next;
slow = slow->next;
if(slow == fast){
break;
}
}
if(fast == NULL || fast->next == NULL) return NULL;
fast = head;
while (fast != slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
思考:
慢指针slow的路程:slow = x+y+k(y+z)
快指针fast的路程: fast = x+y+n(y+z)
那么x+y+n(y+z) = 2(x+y+k(y+z))
=> x= (n-2k-1)(y+z)+z
特殊情况下,比如k=0(慢指针一圈都没走就被追上),n=1(快指针走了一圈就追上慢指针)。那么x=z,直观的发现,如果在快慢指针相遇的时候有一个指针3从头节点出发,这个指针3会和慢指针在环形入口的地方相遇。
推导出普遍情况:快慢指针相遇时,一个指针3从头节点出发,慢指针走了n-2k-1圈后会和指针3在环形入口节点处相遇。
总结
链表还是蛮有意思的,和数组疑难点不一样的地方是,链表要时时考虑节点是否是空的情况,或者是否进入死循环。总结一下:
1.ListNode* xx = new ListNode();和ListNode* xx = node;
前者相当于新建了一个节点,后者是一个指针用来指向节点(多用于用来遍历链表或者暂存节点。)
2.很多链表问题通过引入一个虚拟节点dummyhead,会轻松很多,减少头节点前面的问题。
3.160和142的数学思想很有意思,可以常复习复习。