代码随想录算法训练营第4天|24. 两两交换链表中的节点 、19.删除链表的倒数第N个节点 、面试题 02.07. 链表相交、142.环形链表II

LeetCode24

2025-01-25 15:05:55 星期六

题目描述:力扣24
文档讲解:代码随想录(programmercarl)24.两两交换链表中的节点
视频讲解:《代码随想录》算法视频公开课:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点

代码随想录视频内容简记

用虚拟头结点的方式来做

梳理

上图中蓝线表示的执行的反转操作,红色序号表示的每一步反转操作导致消失的的指针

  1. while的循环条件

  2. 反转操作

  3. 设置两个tmp临时的指针为了循环往下遍历

大致代码内容

  1. while (cur->next != NULL && cur->next->next != NULL),,前一个是在链表有偶数个结点的情况,后一个是链表有奇数个结点的情况。这个条件一定不能写反,因为一旦反过来写,如果cur->next为空指针,那么cur->next->next就相当于操作空指针了

  2. 反转操作


tmp = cur->next;
tmp1 = cur->next->next->next;
cur->next = cur->next->next;
cur->next->next = tmp;
cur->next->next->next = tmp1;
  1. 因为为了避免链表结点反转时出现对空指针的操作,所以要设置两个temp指针

LeetCode测试

代码比较好通过

点击查看代码
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* cur = dummyHead;
        ListNode* tmp;
        ListNode* tmp1;
        while (cur->next != NULL && cur->next->next != NULL) {
            // 交换结点
            tmp = cur->next;
            tmp1 = cur->next->next->next;
            cur->next = cur->next->next;
            cur->next->next = tmp;
            cur->next->next->next = tmp1;
            // 指针后移
            cur = cur->next->next;
        }
        return dummyHead->next;
    }
};

LeetCode19

题目描述:力扣19
文档讲解:代码随想录(programmercarl)19.删除链表的倒数第N个节点
视频讲解:《代码随想录》算法视频公开课:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点

代码随想录视频内容简记

这道题仍然使用的是双指针的思想,一个快指针和一个满指针,和27.移除元素很相近,但是这道题目巧妙的地方在于先让快指针移动n+1步,之后快慢指针同时移动直到快指针为空。

梳理

  1. 首先是定义虚拟头结点,以及对快慢指针的定义

  2. 第一个while循环控制先让快指针移动n+1步。如上图,我们确实需要删除slow指针为倒数n=2的结点,但是就因为如此,才需要移动该位置的前驱结点,来对其进行删除。也就是slow需要少移动一个位置,而fast需要多移动一个位置,移到红框所指的实箭头处。

  3. 第二个while循环控制再让快慢指针同时移动

  4. 删除结点

大致代码内容

  1. 快指针先移动。首先进行n++操作,之后进行while (n-- && fast != NULL)

while (n-- && fast != NULL) {
	fast = fast->next;
}
  1. 两个指针同时移动

while (fast != NULL) {
	fast = fast->next;
	slow = slow->next;
}

LeetCode测试

代码比较好通过,就是不要总是忘了dummyHead->next = head,要不然总会报空指针的错误。

点击查看代码
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummyHead = new ListNode(0);
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        dummyHead->next = head;
        n++;
        while (n-- && fast != NULL) {
            fast = fast->next;
        }
        while (fast != NULL) {
            fast = fast->next;
            slow = slow->next;
        }
        ListNode* tmp = slow->next;
        slow->next = slow->next->next;
        delete tmp;
        return dummyHead->next;
    }
};

面试题 02.07. 链表相交

题目描述:力扣面试题 02.07. 链表相交
文档讲解:代码随想录(programmercarl)面试题 02.07. 链表相交

内容梳理

  1. 这个在王道书上见过,之要判断只要中间有一个指针是相同的,那么其后的部分就一定相交,因为指针所指的地址肯定是一样的。因为链表的长度不一样,所以要先在长链表上遍历两个链表之间长度的差

  2. 核心还是双指针的思想,一个负责A链表,一个负责B链表

LeetCode测试

这个题还是有几点值得注意的

  1. 那就是指针相等数值相等。可以看力扣在题目描述中给出的示例1

刚开始我也很疑惑,为什么不是从1开始的。后来看评论区,这是是假设了一种可能,也就是curA指向的1curB指向的1的地址不一样,但是二者所指向的8的地址是一样的。所以在判断是就要用if (curA == curB)来直接进行判断,而不是if (curA->val == curB->val)

  1. 注意这个循环遍历的终止条件的书写,因为题目要求“如果两个链表没有交点,返回 null”,所以while循环中要同时给出两个情况的return

while (curA != NULL) {
	if (curA == curB) return curA;
	...
}
return NULL;

链表相交完整代码

点击查看代码
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB = 0, lenS;
        while (curA != NULL) {
            lenA++;
            curA = curA->next;
        }
        while (curB != NULL) {
            lenB++;
            curB = curB->next;
        }
        curA = headA;
        curB = headB;
        if (lenB > lenA) {
            swap(lenB, lenA);
            swap(curB, curA);
        }
        lenS = lenA - lenB;
        while (lenS-- && curA != NULL) {
            curA = curA->next;
        }
        // while (curA->val != curB->val) {
        //     curA = curA->next;
        //     curB = curB->next;
        // }
        // return curA;
        while (curA != NULL) {
            if (curA == curB) {
                return curA;
            }
            curA = curA->next;
            curB = curB->next;
        }
        return NULL;
    
    }
};

LeetCode142

题目描述:力扣142
文档讲解:代码随想录(programmercarl)142.环形链表II
视频讲解:《代码随想录》算法视频公开课:把环形链表讲清楚!| LeetCode:142.环形链表II

代码随想录视频简记

梳理

  1. 判断是否有环

    首先是一个快指针,一个慢指针,如果两个指针能出现相等的情况,那说明这个链表一定有环。
    但是如何保证不出现快指针把满指针正好跳过的情况呢?,这里就需要对二者的移动速度进行特殊定义,也就是快指针每次移动两个位置,慢指针每次移动一个位置。这样快指针每次相对于慢指针的移动位数就是1,那么以1个相对位置的距离靠近,就可以保证中间没有跳过。

还有一点就是,注意二者的相遇并不是追及,从动图中可以看出来,快指针中间追上了慢指针,但是仍然会往前走一个距离,而等慢指针再走一个距离。也就是无论是fast还是slow指针,其next操作必须走完才行

  1. 确定环的入口

    这里需要做一些简单的数学推导

    我们假设慢指针首先走过了x+y,而快指针因为在圈内绕过了n圈,所以快指针走过了x+y+n(y+z),而因为二者的速度差异,即快指针是慢指针的速度二倍。所以可列出下面的等式

2(x+y)=x+y+n(y+z)x+y=n(y+z)x=n(y+z)yx=(n1)(y+z)+z

由此,我们便得到了关键的公式。我们可以代入特殊值n=1发现,x=z,也就是说无论快指针在环内转了多少圈,x的距离等于n1圈加上z的距离。

那么如果定义一个index1指针指向两个指针相遇的地方,一个index2指向头结点,那么两个指针同时移动,最终相等的地方就是环的入口


这里还有一个问题就是K哥讲到的,为什么慢指针走过的距离不能是x+y+k(y+z),就是为什么慢指针在走完第一圈的时候就一定会被快指针追上呢?

可以看到,慢指针从入口进入,在走完第一圈的时候,快指针会以二倍慢指针的速度超过他,所以一定会在慢指针走完第一圈的时候追上慢指针

大致代码内容

注意代码中仍然是指针与指针相等,并不是指针的val


while (fast != NULL && fast->next != NULL) {
	fast = fast->next->next;
	slow = slow->next;
	if (fast == slow) {
		index1 = fast;
		index2 = head;
		while (index1 != index2) {
			index1 = index1->next;
			index2 = index2->next;
		}
		return index1;
	}
}
return NULL;

还有就是为什么需要while (fast != NULL && fast->next != NULL),因为我们需要保证fast = fast->next->next是有意义的,如果fast->next = NULL,那么NULL->next就显然没有意义了。

按照k哥的说法

这么做是为了放心大胆的进行fast = fast->next->next

LeetCode测试

关于时间复杂度

关于代码的时间复杂度,文档中是这么写的,在快慢指针相遇前,快慢指针的元素的平均操作次数小于链表长度n,所以时间度复杂度为O(n),在相遇之后,index1和index2指针对元素的平均操作操作长度也小于链表长度n,所以时间复杂度也为O(n),最终的时间复杂度就为O(n)

环形链表Ⅱ完整代码如下

点击查看代码
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        ListNode* index1;
        ListNode* index2;
        while (fast != NULL && fast->next != NULL) {
            fast = fast->next->next;
            slow = slow->next;
            if (fast == slow) {
                index1 = fast;
                index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index1;
            }
        }
        return NULL;
    }
};
posted on   bnbncch  阅读(2123)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示