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

71 阅读4分钟

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

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

image.png

思路

使用虚拟头节点,交换的步骤一定要弄清楚,建议画图。
一次交换分为四步:

  1. pre.next = node2;
  2. node2.next = node1;
  3. node1.next = node3;
  4. pre = node1;

image.png
按照上图步骤操作完了之后得到下面这张图:

image.png
知道步骤之后,代码就很好写了!

代码实现

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyNode = new ListNode(-1, head);
        ListNode pre = dummyNode;
        ListNode temp = null;
        ListNode first = null;
        ListNode second = null;
        while (pre.next != null && pre.next.next != null) {
            temp = pre.next.next.next;
            first = pre.next;
            second = pre.next.next;
            pre.next = second;
            second.next = first;
            first.next = temp;
            pre = first;
        }
        return dummyNode.next;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

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

给你一个链表,删除链表的倒数第 n **个结点,并且返回链表的头结点。

image.png

思路

使用快慢指针;

image.png
首先让快指针先移动 n + 1步;n + 1步是因为当我们需要删除一个节点的时候,我们需要获得该节点的前置节点。

image.png
然后同时移动快慢指针直至快指针指向了null

image.png
然后我们就可以进行节点的删除。

image.png

代码实现

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (n == 0) {
            return head;
        }
        ListNode dummy = new ListNode(-1, head);
        ListNode fast = dummy;
        ListNode slow = dummy;
        for (int i = 0; i <= n; i++ ) {
            fast = fast.next;
        }

        while (fast != null) {
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return dummy.next;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

面试题 02.07. 链表相交

image.png

思路

我们首先遍历两个链表,求出它们的长度的差值diff。如下面这个示例,其差值为1。

image.png

然后我们让较长的那个链表先移动diff步,这时链表B中剩余的节点数量就和链表A中的节点数量一致了。

image.png

然后我们同时移动A, B 两个链表的指针,并比较他们是否为同一个节点;如果是我们就找到了相交点,如果一直到链表结束都没有找到,那么就代表这两个链表不相交。

image.png

代码实现

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode curA = headA;
        ListNode curB = headB;
        int lenA = 0;
        int lenB = 0;
        while (curA != null) {
            curA = curA.next;
            lenA++;
        }

        while ( curB != null) {
            curB = curB.next;
            lenB++;
        }

        curA = headA;
        curB = headB;

        if (lenB > lenA) {
            int temp = lenA;
            lenA = lenB;
            lenB = temp;
            ListNode tempNode = curA;
            curA = curB;
            curB = tempNode;
        }

        int diff = lenA - lenB;
        for (int i = 0; i < diff; ++i) {
            curA = curA.next;
        }

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

        return null;
    }
}
  • 时间复杂度:O(n + m)
  • 空间复杂度:O(1)

142. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 不允许修改 链表。

image.png

思路 - 快慢指针

首先我们需要定义两个指针:

快指针-每次移动两格子
慢指针-每次移动一个格子
这样定义的目的是:如果链表中存在环,那么快慢指针都进入环以后,快指针实际上是以每个循环一格的速度在接近慢指针,这样只要有环那么快慢指针就一定会在某一时刻重合。

判断是否有环

要判断有没有环也非常简单,如果有环,那么快慢指针就一定会相等,如果没有环那么快慢指针就永远不会相等,因此当快指针遍历完整个链表快慢指针还没有相等的话,那么就可以断定这个链表中不存在环!

找出环形入口节点

最后我们需要用数学推理来帮助我们得到进入环的节点,如下图:
我们首先假设连边内有环,x代表从头节点到进入环所需的节点数,y为从环形入口节点到 fast指针与slow指针相遇节点所需的节点数。z为从相遇节点到环形入口节点所需的节点数。 image.png 因为快指针会先进入环,因此当快慢指针相遇时,快指针一定在环内走过了至少一圈,这里我们将圈数定义为n并且n >= 1
并且由上图我们可以得出:
慢指针到相遇所走过的节点数为x + y
快指针到相遇时所走过的节点数就为x + y + n(y + z),而快指针的速度是慢指针的两倍,因此快指针所走过的节点数也可以写为2(x + y)
由此,我们得到等式:2(x + y) = x + y + n(y + z)
通过化简:

2(x+y)=x+y+(n1)(y+z)2(x + y) = x + y + (n - 1)(y + z)
x+y=(n1)(y+z)+y+zx + y = (n - 1)(y + z) + y + z
x=(n1)(y+z)+zx = (n - 1)(y + z) + z

因为n >= 1,我们假设n = 1

x=zx = z

得到这个公式之后,当我们找到快慢指针相遇的节点时,把快指针重新指向头节点,慢指针保持在相遇节点的位置;这时同时移动他们,快慢指针的速度都为一次一格。当他们再次相遇时,就是环形入口节点。(因为x = z

代码实现

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        ListNode temp = null;
        while ( fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                fast = head;
                while ( slow != fast) {
                    slow = slow.next;
                    fast = fast.next;
                }
                return slow;
            }
        }
        return null;
    }
}
  • 时间复杂度:O(n), 快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
  • 空间复杂度:O(1)

参考资料

代码随想录:142.环形链表II