LeetCode技巧篇(三)双指针中的快慢指针

介绍

快慢指针是链表中经常用到的一种技巧,借助该技巧能够一次遍历就可以确定链表中点,判断链表是否含有环等。

实例

先介绍一个非常经典的问题,LeetCode 142 返回链表的入口节点。
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。说明:不允许修改给定的链表。
在这之前有一个简单的问题,就是如何判断链表是否有环,那么很简单,我定义两个指针,一个走的快,一个走的慢,如果有环那么它们一定会相遇。那么如果没有环呢?那肯定就有终点,因此只要走到链表的末尾,那就是没有环。代码如下:

    public boolean findCycle(ListNode head) {
        if(head==null||head.next==null||head.next.next==null) return false;
        ListNode first = head.next;
        ListNode second = head.next.next;
        while(first!=second){
            if(second==null||second.next==null) return false;
            first = first.next;
            second = second.next.next;
        }
        return true;
    }

回过头来,这道题可以看成是链表有环问题的拓展,找到环的入口。同样的,这里我们还是用快慢指针。如果有环,那么快慢指针一定会相遇,那么问题来了,在哪里相遇呢?相遇的时候快指针走了多久?慢指针呢?
假设链表的非环长度为L,环的长度为C,那么链表的总长度为L+C;
假设相遇地点在离入口节点长度为X的地方相遇(这里假设指针是顺时针移动),那么有以下公式:
2 ∗ ( L + ( C − X ) ) = L + n ∗ C + ( C − X ) 2*(L+(C-X)) = L + n*C + (C-X) 2(L+(CX))=L+nC+(CX)
这个怎么理解呢,就是快指针走过的路程是慢指针的两倍,这里n表示走了n圈环的长度。
展开得:
L − X + C = n ∗ C L-X +C= n*C LX+C=nC
L = X + ( n − 1 ) ∗ C L= X + (n-1)*C L=X+(n1)C
这个公式代表的含义就是只要头指针(此时可以令快指针指向头结点)和慢指针再各自前进L步,那么它们一定在环的入口节点相遇,此时返回头指针即可。

    public ListNode detectCycle(ListNode head) {
        if(head==null||head.next==null||head.next.next==null) return null;
        ListNode first = head.next;
        ListNode second = head.next.next;
        while(first!=second){
            if(second==null||second.next==null) return null;
            first = first.next;
            second = second.next.next;
        }
        second = head;
        while(second!=first){
            second = second.next;
            first = first.next;
        }
        return second;
    }

除了可以判断链表是否有环,还有LeetCode 876,确定链表的中点。通常做法是遍历两次,一次确定链表长度,一次在链表的中间点停下:

    public ListNode middleNode(ListNode head) {
        int count = 1;
        ListNode copyHead = head;
        while(head.next!=null){
            head = head.next;
            count++;
        }
        count=count/2;
        while(count>0){
            copyHead = copyHead.next;
            count--;
        }
        return copyHead;
    }

但如果用快慢指针,可以一次遍历就能得到结果:

public ListNode middleNode(ListNode head) {
	ListNode fast = head, slow = head;
	while (fast != null && fast.next != null) {
    	fast = fast.next.next;
    	slow = slow.next;
	}
	return slow;
}

确定中点只是一种特殊情况,可以一次遍历更一般地确定倒数第K个节点:
先让快指针往前走K步,然后停下;然后慢指针和快指针往前走,直到快指针走到节点末尾。(这里就不加代码了)

总结

双指针是非常实用的技巧,不管是滑动窗口中的左右指针还是用户链表中的快慢指针。除了判断链表是否有环,还可以只用一遍扫描就能确定链表的终点、确定链表的倒数第K个节点等。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值