链表中的快慢指针法

快慢指针法:

快慢指针一般都初始化指向链表的头结点 head,前进时快指针 fast 在前,慢指针 slow 在后,巧妙解决一些链表中的问题。

1.判定链表中是否含有环(leetcode141.环形链表)

这应该属于链表最基本的操作了,单链表的特点是每个节点只知道下一个节点,所以一个指针的话无法判断链表中是否含有环的。
如果链表中不含环,那么这个指针最终会遇到空指针 null 表示链表到头了
但是如果链表中含有环,那么这个指针就会陷入死循环,因为环形数组中没有 null 指针作为尾部节点。
经典解法就是用两个指针,一个每次前进两步,一个每次前进一步。如果不含有环,跑得快的那个指针最终会遇到 null,说明链表不含环;如果含有环,快指针最终会超慢指针一圈,和慢指针相遇,说明链表含有环。

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode fast,slow;
        fast=slow=head;

        while(fast!=null && fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow){
                return true;
            }
        }
        return false;       
    }
}
# 这里写法要注意的是因为fast快,所有如果无环,那么肯定是fast先碰到null
# 另外因为fast=fast.next.next; 所以fast本身可能是null,fast.next也可能是null

2.已知链表中含有环,返回这个环的起始位置

当快慢指针相遇时,让其中任一个指针重新指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
第一次相遇时,假设慢指针 slow 走了 k 步,那么快指针 fast 一定走了 2k 步,也就是说比 slow 多走了 k 步(也就是环的长度)
设相遇点距环的起点的距离为 m,那么环的起点距头结点 head 的距离为 k - m,也就是说如果从 head 前进 k - m 步就能到达环起点,巧的是,如果从相遇点继续前进 k - m 步,也恰好到达环起点。
所以,只要我们把快慢指针中的任一个重新指向 head,然后两个指针同速前进,k - m 步后就会相遇,相遇之处就是环的起点了。

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head, slow = head;
        //获取首次相遇时候,slow的位置
        while(fast!= null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
                break;
            }
        }
        //如果快指针走到尽头,没环
        if(fast == null || fast.next == null) return null;
        //快指针重新出发,相遇位置就是入口位置
        fast = head;
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}
//之所以要在第一个循环结束之后再判断if(fast == null || fast.next == null) return null;
//是因为可能存在链表中只有一个值,那么相当于根本没有进行第一个循环
//这行就是为了处理这个特殊的可能性

3.寻找链表的中点(寻找链表中点的一个重要作用是对链表进行归并排序)

我们还可以让快指针一次前进两步,慢指针一次前进一步,当快指针到达链表尽头时,慢指针就处于链表的中间位置。
当链表的长度是奇数时,slow 恰巧停在中点位置;如果长度是偶数,slow 最终的位置是中间偏右:

class Solution {
    public ListNode middleNode(ListNode head) {
        if(head==null){return null;}
        ListNode fast,slow;
        fast=slow=head;

        while(slow!=null){
            if(fast.next==null){
                return slow;
            }
            if(fast.next.next==null){
                return slow.next;
            }
            slow=slow.next;
            fast=fast.next.next;
        }
        return null;

    }
}

以上是我的代码,虽然是分类讨论了,但要注意分类讨论完成之后还是要找共性,尽量合并的写出

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

4.寻找链表倒数第k个元素

思路还是使用快慢指针,让快指针先走 k 步,然后快慢指针开始同速前进。这样当快指针走到链表末尾 null 时,慢指针所在的位置就是倒数第 k 个链表节点(为了简化,假设 k 不会超过链表长度):

class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode former = head, latter = head;
        for(int i = 0; i < k; i++)
            former = former.next;
        while(former != null) {
            former = former.next;
            latter = latter.next;
        }
        return latter;
    }
}
原文地址:https://www.cnblogs.com/shiji-note/p/14394711.html