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

[LeetCode] 24. 两两交换链表中的节点

[LeetCode] 24. 两两交换链表中的节点文章解释[LeetCode] 24. 两两交换链表中的节点视频解释

自己看到题目的第一想法

    使用 previousNode, currentNode 来记录当前的一对的节点组. 然后将 currentNode 指向 previousNode, previousNode 指向 currentNode 的后驱节点. 然后再将 previousNode 指向自己的后驱节点, 将currentNode 指向 previousNode.next.next.

    然而程序执行过程中才发现, 自己无法获取到第一组的两两交换后, 位于后面的那个节点, 于是链表产生了异常中断.

看完代码随想录之后的想法

    依旧使用 firstNode 和 secondNode 指向需要交换位置的节点, 同时用 beforeFirstNode 指向 firstNode 的前一个节点. 这样当 firstNode 和 secondNode 交换顺序后, 只要将 beforeFistNode.next = secondNode 就可以了.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode swapPairs(ListNode head) {

        if (head == null) {
            return null;
        }

        ListNode dummy = new ListNode(0, head);
        ListNode beforeFirstNode = dummy;
        ListNode firstNode = dummy.next;
        ListNode secondNode = null;

        ListNode temp;

        while (firstNode != null && firstNode.next != null) {

            secondNode = firstNode.next;
            temp = secondNode.next;

            secondNode.next = firstNode;
            firstNode.next = temp;
            beforeFirstNode.next = secondNode;

            beforeFirstNode = firstNode;
            firstNode = firstNode.next;
        }

        return dummy.next;
    }
}

自己实现过程中遇到哪些困难

    没有想清楚两个相邻的 node 交换顺序后, 整个链表的状态. 导致第二组两个相邻的 node 交换顺序后, 第二组的 node 就丢失了. 后来通过画图, 好像找到了原因, 但是一时间也没想到解决办法. 看了视频后才知道怎么解.

[LeetCode] 19. 删除链表的倒数第 N 个结点

[LeetCode] 19. 删除链表的倒数第 N 个结点文章解释

[LeetCode] 19. 删除链表的倒数第 N 个结点视频解释

自己看到题目的第一想法 

    很简单, 只要用双指针, 让快速指针向前移动 n 次, 造成一个有 n + 1 个元素的窗口, 然后同时移动快慢指针, 一直到快速指针到链表结尾.

    然而过程中发现, 当删除列表中第一个元素的时候, 怎么都算不对. 问题到底在哪呢. 在 while 循环中, 用来计算当前快速指针的索引值, 为什么怎么都算不对呢? 如果 n 的值大于列表的长度, 又要怎么处理才会不误删第一个元素呢?

    于是有了下面这种别扭的方案.

// 真的很丑陋, 很不优雅, 很不易读
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyNode = new ListNode(0, head);
        ListNode startNode ;
        ListNode endNode;

        startNode = endNode = dummyNode;
        while (endNode.next != null) {
            endNode = endNode.next;
            if (n >= 0) {
                n--;
            }

            if (n == -1) {
                startNode = startNode.next;
            }
        }
        if (startNode != null && startNode.next != null) {
            startNode.next = startNode.next.next;
        }
        return dummyNode.next;
    }
}

看完代码随想录之后的想法

    咦, 可以单独先移动快指针到指定位置, 然后再让慢指针和快指针联动, 一起移动到链表的尾部. 这样如果 n 大于列表的长度, 快速指针就会先移动到列表的末尾, 导致快速指针为空, 也就避免了慢指针和快指针一起移动的问题, 解决了 n 异常的问题.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyNode = new ListNode(0, head);
        ListNode fastNode = dummyNode;
        ListNode slowNode = dummyNode;;

        while (n-- > 0 && fastNode != null) {
            fastNode = fastNode.next;
        }
        if (fastNode != null) {
            fastNode = fastNode.next;
        }
        while (fastNode != null) {
            fastNode = fastNode.next;
            slowNode = slowNode.next;
        }
        if (slowNode != null && slowNode.next != null) {
            slowNode.next = slowNode.next.next;
        }
        return dummyNode.next;
    }
}

自己实现过程中遇到哪些困难

    fastNode 的位置一直计算错误, 对双指针的具体应用还是生疏. 企图在一个 while 循环里同时移动 fastNode 和 slowNode, 造成了额外的复杂度, 降低了可阅读性. 值的反思.

[LeetCode] 面试题 02.07. 链表相交

[LeetCode] 面试题 02.07. 链表相交文章解释

自己看到题目的第一想法 

    题目到底再说啥, intersectVal 是什么, skipA 和 skipB 又是干嘛的? 文章中说把两个列表尾部对齐, 然后将长列表的 node 指针移动到和短列表的头对齐的位置. 就是说长指针从上往下移动到某个 node , 这个node代表的子链的长度, 和短列表的长度是一样的, 然后开始逐个比对 node 节点, 存在相同的就是交叉节点, 否则就是不存在.

    为什么这么一对齐就可以, 这到底再说啥?

看完代码随想录之后的想法

    说实话, 这一题看了挺久的, 从出门开始看, 一直到走到吃饭的地方才恍然大悟. 如果有一个节点是相同的, 那从这个节点之后所有的数据就是完全一致的. 所以不存在其他情况, 就是说尾巴没对齐, 却有节点重复的. 因此将两个链表尾部对齐之后, 将长链表高出来的部分截掉, 再注意对比生下的两个列表, 存在重复的就是找到第一个分叉点. 因为如果第一个分叉点在长链表高出来的那部分的话, 那这个分叉点到被截掉的这部分的末尾, 就应该也存在短链表中. 这是不可能的, 因此第一个分叉点一定在截掉高出来的部分后, 剩余的两个链表里.

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        if (headA == null || headB == null) {
            return null;
        }

        int lengthA = 0;
        int lengthB = 0;

        ListNode currentA = headA;
        ListNode currentB = headB;

        while (currentA != null) {
            lengthA++;
            currentA = currentA.next;
        }
        while (currentB != null) {
            lengthB++;
            currentB = currentB.next;
        }
        if (lengthA < lengthB) {
            currentA = headB;
            currentB = headA;
            int temp = lengthB;
            lengthB = lengthA;
            lengthA = temp;
        } else {
            currentA = headA;
            currentB = headB;
        }
        while (lengthA-- > lengthB) {
            currentA = currentA.next;
        }
        while (currentA != null && currentB != null) {
            if (currentA == currentB) {
                return currentA;
            }
            currentA = currentA.next;
            currentB = currentB.next;
        }
        return null;
    }
}

自己实现过程中遇到哪些困难

    无

[LeetCode] 142. 环形链表 II

[LeetCode] 142. 环形链表 II 文章解释

[LeetCode] 142. 环形链表 II 视频解释

自己看到题目的第一想法 

    肯定不会, 直接看视频解答吧.

看完代码随想录之后的想法

    1. 链表如何判断是否存在环路:

        通过快慢指针, 快指针每次移动两个节点, 慢指针每次移动1个节点. 如果快指针最终和慢指针重合, 则说明有环路. 这是因为如果快慢指针都进入环路后, 快指针每一走一次就向慢指针靠近一次, 如果环上有 n 个节点的话, 最多 n - 1 次后, 快指针就会和慢指针重叠.

    2. 为什么当慢指针进入环以后, 快指针只需要一圈以内就可以和慢指针重叠.

        因为一定是快指针追赶上慢指针, 因此快慢指针之间最大的距离就是快指针刚站到环的入口的下一节点,  而慢指针就踏入环的入口节点. 假设环一共有n个节点, 那快指针和慢指针之间, 就差了 n - 1 个节点. 当慢指针走了 n - 1 个节点的时候, 快指针就和他重叠了. 因此慢指针在一圈之内, 必定会被快指针赶上.

    3. 如何找到环路的入口节点

        定义从 “HEAD 节点” 到 ”环的入口节点” 的节点数为 x , 从 “环的入口节点” 到 “快慢指针重叠处” 的节点数为 y,  从 “快慢指针重叠处” 继续向前走到 “环的入口节点” 的节点数为 y.

        因此存在这样的一种关系: 首先慢节点走不到一圈一定会被快指针追到导致重叠, 因此慢指针被追到时一共走了 x + y 个节点, 快指针走了 x + y + m * (y + z) 个节点数.

因为快慢节点走的步数是一样的, 而快节点是慢节点速度的两倍, 因此有 2(x + y) = x + y + m * (y + z), 做一下合并同类项可以得到 x = m * (y + z) - y = (m - 1) * (y + z) + z. 从这个公式里可以看出, 如果一个节点, 从HEAD 节点开始慢慢走到 ”环的入口节点”, 而另外一个节点从快慢指针重叠处开始绕环走, 最终他们一定会在 “环的入口节点” 相遇. 假设快慢节点相遇节点为 nodeA, 则 head->next 最终一定会等于 nodeA.next

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode fastNode = head;
        ListNode slowNode = head;
        while (fastNode != null && fastNode.next != null) {
            fastNode = fastNode.next.next;
            slowNode = slowNode.next;
            if (fastNode == slowNode) {
                ListNode headCurrent = head;
                ListNode circleCurrent = fastNode;
                while (headCurrent != circleCurrent) {
                    headCurrent = headCurrent.next;
                    circleCurrent = circleCurrent.next;
                }
                return headCurrent;
            }
        }
        return null;
    }
}

自己实现过程中遇到哪些困难

    不看解答, 根本想不到这些关系, 就绝对根本做不出来. 而看过解答后, 答案变得如此简单.  但是依旧会在想, 面试的时候会怎么考察呢? 过一段时间忘记了这个推理过程, 就又无迹可寻想不起解法了. 不知道自己是不是真的掌握了.

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值