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

93 阅读6分钟

第二章-链表 part02

今日任务

    1. 两两交换链表中的节点
  • 19.删除链表的倒数第 N 个节点
  • 面试题 02.07. 链表相交
  • 142.环形链表 II
  • 总结

第一题:24. 两两交换链表中的节点

用虚拟头结点,这样会方便很多。

本题链表操作就比较复杂了,建议大家先看视频,视频里我讲解了注意事项,为什么需要 temp 保存临时节点。

题目链接:24. 两两交换链表中的节点 - 力扣(LeetCode)

文章讲解/视频讲解:代码随想录 (programmercarl.com)

方法一 - 虚拟头结点

  • 题目强制要求交换结点,而不是交换数值
  • 使用虚拟头结点 一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序!!!

每一次循环:

image.png

将 currentNode 移动到 tempNode1

image.png

下一次循环

image.png

直到 currentNode.next != null && currentNode.next.next != null​​ 退出循环

  • 前者判断偶数情况 ​currentNode.next != null​​
  • 后者判断奇数情况 currentNode.next.next != null​​

Code:

/**
 * 方法一 - 虚拟头结点
 */
class Solution {
    public ListNode swapPairs(ListNode head) {

        // 虚拟头结点
        ListNode dummyHead = new ListNode(Integer.MAX_VALUE, head);

        // 当前节点
        ListNode currentNode = dummyHead;

        // 由题意可知有两种情况
        // 1. 偶数时 两两交换 循环结束条件为 currentNode.next != null
        // 2. 奇数时 两两交换 剩一个 结束条件为 currentNode.next.next != null
        // 注意 一定是 currentNode.next != null 写在前面 防止偶数时出现空指针异常
        while (currentNode.next != null && currentNode.next.next != null) {
            // 两两交换的策略
            // 1. 将当前节点 currentNode -> 当前节点的 next 的 next 那个节点
            // 2. 将当前节点的 next 的 next 那个节点 -> 当前节点的 next 那个点
            // 3. 将当前节点的 next 那个点 -> 当前节点的 next 的 next 的 next 的节点
            // 注意:在操作第一步前 要把当前节点的 next 节点保存下来 - tempNode1
            //      这样就可以避免出现 当前节点已经指向其他点导致获取不到 当前点的 next 点
            //      同理 第二步前 也要存一个临时值 - tempNode2

            ListNode tempNode1 = currentNode.next;
            currentNode.next = currentNode.next.next;

            ListNode tempNode2 = currentNode.next.next;
            currentNode.next.next = tempNode1;

            tempNode1.next = tempNode2;

            currentNode = tempNode1;
        }

        return dummyHead.next; // 返回虚拟节点的下一个节点就是我们合法的链表
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

方法二 - 递归法

  • 挖坑 以后熟练在研究~

第二题:19.删除链表的倒数第 N 个节点

双指针的操作,要注意,删除第 N 个节点,那么我们当前遍历的指针一定要指向 第 N 个节点的前一个节点,建议先看视频。

题目链接:19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

文章讲解/视频讲解:代码随想录 (programmercarl.com)

思路

双指针的经典应用,如果要删除倒数第 n 个节点,让 fast 移动 n 步,然后让 fast 和 slow 同时移动,直到 fast 指向链表末尾。删掉 slow 所指向的节点就可以了。

  • 首先使用虚拟头结点,这样方便处理删除实际头结点的逻辑

  • 定义 fast 指针和 slow 指针,初始值为虚拟头结点,如图:

image.png

  • fast 首先走 n + 1 步 ,为什么是 n+1 呢,因为只有这样同时移动的时候 slow 才能指向删除节点的上一个节点(方便做删除操作),如图:

image.png

  • fast 和 slow 同时移动,直到 fast 指向末尾,如题:

image.png

  • 删除 slow 指向的下一个节点,如图:

image.png

方法一 - 快慢指针

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {

        ListNode dummyHead = new ListNode(Integer.MAX_VALUE,head);
        ListNode fast = dummyHead; // 快指针
        ListNode slow = dummyHead; // 慢指针

        // 快指针先向后移动 n + 1步
        while (n-- != 0){
            fast = fast.next;
        }
        fast = fast.next;

        // 当快指针走到 null 时,慢指针正好在需要删除节点的前一个节点
        while (fast != null){
            fast = fast.next;
            slow = slow.next;
        }

        slow.next = slow.next.next; // 删除 链表的倒数第 N 个结点
    
        return dummyHead.next;
    }
}

第三题:面试题 02.07. 链表相交

本题没有视频讲解,大家注意 数值相同,不代表指针相同。

题目链接:面试题 02.07. 链表相交 - 力扣(LeetCode)

文章讲解:代码随想录 (programmercarl.com)

思路

  • 简单来说,就是求两个链表交点节点的指针。
  • 注意,交点不是数值相等,而是指针相等。

看如下两个链表,目前 curA 指向链表 A 的头结点,curB 指向链表 B 的头结点:

image.png 我们求出两个链表的长度,并求出两个链表长度的差值,然后让 curA 移动到,和 curB 末尾对齐的位置,如图:

image.png

此时我们就可以比较 curA 和 curB 是否相同,如果不相同,同时向后移动 curA 和 curB,如果遇到 curA == curB,则找到交点。

否则循环退出返回空指针。

方法一 - 对齐法

class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        // 初始化
        int lengthA = 0; // A 链表长
        int lengthB = 0; // B 链表长
        ListNode curA = headA;
        ListNode curB = headB;

        while (curA != null) {// 求链表A的长度
            lengthA++;
            curA = curA.next;
        }

        while (curB != null) { // 求链表B的长度
            lengthB++;
            curB = curB.next;
        }

        // 重置
        curA = headA; 
        curB = headB;

        // 求长度差
        int lengthAB = lengthA - lengthB;


        if (lengthAB >= 0) { // 如果 A 链表长 则让A先走 使得 A B 对齐
            while (lengthA-- != lengthB) {
                curA = curA.next;
            }
        } else { // 如果 B 链表长 则让B先走 使得 A B 对齐
            while (lengthA++ != lengthB) {
                curB = curB.next;
            }
        }

        // 遍历curA 和 curB,遇到相同则直接返回
        while (curA != null) { 

            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;

    }
}

第四题:142.环形链表 II

算是链表比较有难度的题目,需要多花点时间理解 确定环和找环入口,建议先看视频。

题目链接:142. 环形链表 II - 力扣(LeetCode)

文章讲解/视频讲解:代码随想录 (programmercarl.com)

思路

  • 判断链表是否环
  • 如果有环,如何找到这个环的入口

判断链表是否有环

  • 使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发

    • fast 指针每次移动两个节点
    • slow 指针每次移动一个节点
  • 如果 fast 和 slow 指针在途中相遇 ,说明这个链表有环

    • 因为两者的相对速度是 1 因此如果有环必定会相遇~

image.png

找到这个环的入口

image.png

  1. slow 指针走过的节点数为: ​x+y​

  2. fast 指针走过的节点数:x+y+n(y+z)

    • n 为 fast 指针在环内走了 n 圈才遇到 slow 指针,(y+z)为 一圈内节点的个数 A。
  3. 因为 fast 指针是一步走两个节点,slow 指针一步走一个节点,

    • 所以 fast 指针走过的节点数 = slow 指针走过的节点数 * 2:​

    • 整理得到:​

      • 注意这里 n 一定是大于等于 1 的,因为 fast 指针至少要多走一圈才能相遇 slow 指针。
  4. 这个公式说明

    • 先拿 n 为 1 的情况来举例,意味着 fast 指针在环形里转了一圈之后,就遇到了 slow 指针了。

      • 当 n 为 1 的时候,公式就化解为 x = z​​​,
    • 从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

      • 这里的 n 就是表示 相遇点出发的指针 index1 在环形链表中的圈数
  5. 定义一个指针 index1,在头结点处定一个指针 index2。

    • 让 index1 和 index2 同时移动,每次移动一个节点, 那么他们相遇的地方就是环形入口的节点。

方法 - 快慢指针 + 双指针

/**
 * 快慢指针 + 双指针
 */
class Solution888 {
    public ListNode detectCycle(ListNode head) {


        ListNode fast = head; // 快指针
        ListNode slow = head; // 慢指针

        ListNode index1 = null; // 相遇点
        ListNode index2 = head; // 起点

        // 判断链表是否有环
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) { // 相遇点
                index1 = fast;
                break;// 别忘了退出哦~
            }
        }


        // 找到这个环的入口
        while (index1 != null) {

            if (index1 == index2) {
                return index1;
            }

            index1 = index1.next;
            index2 = index2.next;
        }
        return null;

    }
}

总结

  • 感谢大佬!!!海螺人

image.png