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

24. 两两交换链表中的节点

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

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

题目链接/文章讲解/视频讲解: 代码随想录

重点:

1. 虚拟头节点

保证了第一对pair节点和其余pair节点的操作一致

2. 模拟操作

考验代码实现能力

3. 递归

和模拟操作的操作一样,但要考虑清楚终止条件

// 自己一开始写的代码
public static ListNode swapPairs(ListNode head) {
        // 创建虚拟头节点,保持处理头两个和处理不是头两个的操作一样
        ListNode dummyHead = new ListNode(-1, head);
        ListNode pre = dummyHead;
        ListNode front = dummyHead.next;
        ListNode back;
        while (front != null) {
            // 如果是奇数个,走到最后一个节点直接break
            if (front.next == null) {
                break;
            }
            // 先把pair中的后一个节点保存下来
            back = front.next;
            // 把pair对的前一个节点的next指向pair中的后一个节点
            pre.next = back;
            // 把pair中的前一个节点的next指向pair中后一个节点的next
            front.next = back.next;
            // 把pair中的后一个节点的next指向pair中前一个节点
            back.next = front;
            // 现在pair的前后节点的顺序调换完成了,但front和pre所指向的还是原本的节点
            // 把pre指向更新好之后的pair中的后一个节点
            pre = front;
            // 把front指向要更新的下一对pair中的第一个节点
            front = front.next;
        }
        return dummyHead.next;
}
// 递归写法
public static ListNode swapPairs(ListNode head) {
        // 终止条件,就是到Null或者奇数个
        if (head == null || head.next == null) {
            return head;
        }
        // 获取当前递归的front和back
        ListNode front = head;
        ListNode back = head.next;
        // 获取下一轮递归完毕调转到前面的back节点
        ListNode newNode = swapPairs(front.next.next);
        // 把当前递归的back.next指向当前递归的front
        back.next = front;
        // 把当前递归的front.next指向下一轮递归完毕调转到前面的back节点
        front.next = newNode;
        // 返回当前递归的调转好的前节点
        return back;
}

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

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

题目链接/文章讲解/视频讲解:代码随想录

重点:

1. 虚拟头节点

2. 模拟操作

因为我们是要删除倒数第N个节点,那么快慢指针中间就要相差N个节点来同时移动,这样当快指针指向NULL时,慢指针就正好指向了倒数第N+1个节点

public static ListNode removeNthFromEnd(ListNode head, int n) {
        // 虚拟头节点
        ListNode dummyHead = new ListNode(-1, head);
        // 快慢指针
        ListNode fast = dummyHead;
        ListNode slow = dummyHead;
        // 因为要删除倒数第n个,所以快慢指针中间要相差n个元素
        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }
        // 当快指针移到null时,说明慢指针也移到了要删除节点的前一个节点
        while (fast != null) {
            slow = slow.next;
            fast = fast.next;
        }
        // 把前一个节点的next指向next.next
        slow.next = slow.next.next;
        return dummyHead.next;
}

面试题 02. 07. 链表相交

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

题目链接/文章讲解:代码随想录

重点:这里的相交指的是地址相同,并不是说值相同,也就是说有两个链表,他们在某一处之后共用一段子链表

思路:

1. 目前curA指向链表A的头结点,curB指向链表B的头结点

2. 我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置
3. 此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。否则循环退出返回空指针

public static ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        // 计算链表A和链表B的长度
        int ALength = 0;
        while (curA != null) {
            ALength++;
            curA = curA.next;
        }
        int BLength = 0;
        while (curB != null) {
            BLength++;
            curB = curB.next;
        }
        curA = headA;
        curB = headB;
        // 保证ALength和curA是最长的那个链表
        if (BLength > ALength) {
            int tempLength = ALength;
            ALength = BLength;
            BLength = tempLength;
            ListNode tempNode = curA;
            curA = headB;
            curB = tempNode;
        }
        // 计算最长的那个链表需要先向后移动几个
        int ABLengthGap = ALength - BLength;
        while (ABLengthGap > 0) {
            curA = curA.next;
            ABLengthGap--;
        }
        // 两个链表同时移动,直到找到地址相同的节点
        while (curA != null) {
            if (curA == curB) {
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
}

142.环形链表II

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

题目链接/文章讲解/视频讲解:代码随想录

重点:

这道题目,不仅考察对链表的操作,而且还需要一些数学运算。

主要考察两知识点:

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

思路:

1. 判断是否有环

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

2. 如果有环,如何找到这个环的入口

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2

(x + y) * 2 = x + y + n (y + z)

两边消掉一个 (x+y) :

x + y = n (y + z)

因为要找环形的入口,那么要求的是x,将x单独放在左面:

x = n (y + z) - y

再从n(y+z)中提出一个 (y+z) 来,整理公式之后为如下公式:

x = (n - 1) (y + z) + z

注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当n为1的时候,公式就化解为:

x = z

这就意味着,从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

那么n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候效果是一样的,一样可以通过这个方法找到环形的入口节点。

3. 补充

为什么第一次在环中相遇,slow的步数是 x+y 而不是 x + 若干环的长度 + y 呢?

slow进环的时候,fast一定是在环的任意一个位置,如图(环入口1代表fast当前所在的这一环(slow进入环时的上一环),环入口2代表slow第一次进入环):

那么fast指针走到环入口3的时候,已经走了 k + n 个节点,slow相应的应该走了 (k + n) / 2 个节点。因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。

也就是说slow一定没有走到环入口3,而fast已经到环入口3了。这说明在slow开始走的那一环已经和fast相遇了

那有同学又说了,为什么fast不能跳过去呢? 在刚刚已经说过一次了,fast相对于slow是一次移动一个节点,所以不可能跳过去

public static ListNode detectCycle(ListNode head) {
        // 定义快慢指针来找是否存在环
        ListNode slow = head;
        ListNode fast = head;
        // 快指针每次移动两个,慢指针每次移动一个
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            // 快慢指针同时指向同一个,说明一定存在环
            if (fast == slow) {
                // 开始查找环入口
                // 定义两个指针,从快慢指针的相遇点和链表的头节点同步开始走
                // 这两个新指针的相遇点就是环的入口
                ListNode index1 = fast;
                ListNode index2 = head;
                while (index1 != null) {
                    if (index1 == index2) {
                        return index2;
                    }
                    index1 = index1.next;
                    index2 = index2.next;
                }
            }
        }
        return null;
}

链表总结

链表的理论基础

在这篇文章关于链表,你该了解这些! (opens new window)中,介绍了如下几点:

  • 链表的种类主要为:单链表,双链表,循环链表
  • 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。
  • 链表是如何进行增删改查的。
  • 数组和链表在不同场景下的性能分析。

链表经典题目

虚拟头节点

链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。

链表的基本操作

链表:一道题目考察了常见的五个操作! (opens new window)中,我们通设计链表把链表常见的五个操作练习了一遍。

这是练习链表基础操作的非常好的一道题目,考察了:

  • 获取链表第index个节点的数值
  • 在链表的最前面插入一个节点
  • 在链表的最后面插入一个节点
  • 在链表第index个节点前面插入一个节点
  • 删除链表的第index个节点的数值

可以说把这道题目做了,链表基本操作就OK了,再也不用担心链表增删改查整不明白了

反转链表

链表:听说过两天反转链表又写不出来了? (opens new window)中,讲解了如何反转链表。

因为反转链表的代码相对简单,有的同学可能直接背下来了,但一写还是容易出问题。

建议大家先学透迭代法,然后再看递归法,因为递归法比较绕,如果迭代还写不明白,递归基本也写不明白了。

可以先通过迭代法,彻底弄清楚链表反转的过程!

删除倒数第N个节点

链表:删除链表倒数第N个节点,怎么删? (opens new window)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。

链表相交

链表:链表相交 (opens new window)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点)

环形链表

链表:环找到了,那入口呢? (opens new window)中,讲解了在链表如何找环,以及如何找环的入口位置。需要搞明白数学证明。

总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值