面试题之 【链表是否有环】

62 篇文章 0 订阅

1 判断一个链表中是否有环

Leetcode面试题
在这里插入图片描述
以上面这个链表为例,链表中出现了环路,但是如何检测出该链表是否含有环路呢?

1.1 思路

使用两个指针,一个快指针和一个慢指针,使用快指针追赶慢指针,当两个指针相遇,那么证明该链表中存在环路。

1.2 Java代码

	/**
     * 追赶理论:当两个人在一个环形跑道上跑步时,只要其中一个人的速度比另一个人的速度快,
     * 那么只要给足够多的时间。该速度快的人一定能追上那个速度慢的人。
     * 思路:
     * 使用两个指针,一个慢指针,一次移动一格,
     * 一格快指针,一次移动两格,最终会追赶上
     *
     * @param root
     * @return
     */
    public boolean isCycle(ListNode root) {
        if (root==null||root.next==null)
            return false;
        ListNode p1 = root, p2 = root;
        while (p1 != null && p2!=null&&p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            if (p1 == p2) {
                return true;
            }
        }
        return false;
    }

2 改进版本(求环路的长度)

当两个人在足球场跑步比赛时,一个人速度快(A),一个人速度慢(B),同时出发,当他A追上B时停止跑步,只要给足够的时间,A一定可以和B在某处相遇,应为足球场时一个环形。
所以,借鉴以上思路,当快慢指针相遇之后,让他们继续走,每走一步环的长度加1,当快慢指针再次相遇时,就表示环的长度。

2.1 Java代码

	/**
     * 思路:
     * 当两个指针相遇之后,他们继续跑,因为快指针比慢指针快一倍,即
     * 快指针一次移动两格。慢指针一次移动一格。那么从同一个起点开始跑时,
     * 他们最终会相遇,此时快指针比满指针多跑了一圈。只要计算慢指针所跑的
     * 路程就可以知道环的长度
     *
     * @param root
     * @return
     */
    public int howCycleLong(ListNode root) {
        if (root == null || root.next == null)
            return -1;
        ListNode p1 = root, p2 = root;
        int cycleLong = 0;
        int meetTime = 0;
        while (p1 != null && p2!=null&&p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            if (meetTime == 1)
                cycleLong++;
            if (meetTime == 2)
                break;
            if (p1 == p2) {
                meetTime++;
            }
        }
        return cycleLong;
    }

3 改进版本(返回头结点)

在这里插入图片描述

当快指针和慢指针相遇之后,让其继续前进,当他们在相遇时:

  • 慢指针走过了刚好一圈
  • 快指针走过的路程是慢指针的n

假设从链表的头结点到环的头结点的距离是D,环的头结点到快慢指针相遇后的节点处的距离是S1,相遇节点继续前进到环的头结点处的距离是S2。那么有以下数学等式:

  • fast=D+S1+n*(S1+S2)
  • slow=D+S1
  • fast=n*slown取决于快指针比慢指针行进速度快几倍,通常取2

那么计算之后就有D=S2,将快指针移动到链表头结点处,然后移动快慢指针,当两个指针重合时,表示当前节点时环路的头结点。

3.1 Java代码

	/**
     * 获得环的头结点
     * 思路:
     * 假设从链表的头结点到环头结点的距离为D,环头结点到p1和p2首次相遇的节点处的
     * 距离为S1,抽次相遇的节点到头结点的另一半的距离为S2,
     * 由于:
     * - p1指针一次走一步
     * - p2指针一次走两步
     * 所以在p1和p2相遇时,两个指针走过的路程如下:
     * p1 = D + S1
     * P2 = D + S1 + S1 + S2
     * 由于p2比p1快一倍,所以p2所走的路程是p1走的路程的两倍
     * 即: 2(D+S1)=D+2*S1+S2 => 2*D + 2*S2 = D+ 2*S1 + S2 => D=S2
     * 此时:将p1指针放回到头指针,然后让p1和p2以相同的步长前进,最终当p1和p2相等时,
     * 便是停留在环的头结点上
     *
     * @param root
     * @return
     */
    public ListNode getStartCycle(ListNode root) {
        if (root == null || root.next == null)
            return null;
        ListNode p1 = root, p2 = root;
        while (p1 != null && p2!=null&&p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            if (p1 == p2)
                break;
        }
        if (p1 != p2){
            return null;
        }
        p1 = root;
        while (p1 != p2 && p1 != null && p2 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p2;
    }

4 总代码

import utils.ListNode;

/**
 * 判断链表是否有环
 */
public class IsCycle {
    /**
     * 追赶理论:当两个人在一个环形跑道上跑步时,只要其中一个人的速度比另一个人的速度快,
     * 那么只要给足够多的时间。该速度快的人一定能追上那个速度慢的人。
     * 思路:
     * 使用两个指针,一个慢指针,一次移动一格,
     * 一格快指针,一次移动两格,最终会追赶上
     *
     * @param root
     * @return
     */
    public boolean isCycle(ListNode root) {
        if (root==null||root.next==null)
            return false;
        ListNode p1 = root, p2 = root;
        while (p1 != null && p2!=null&&p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            if (p1 == p2) {
                return true;
            }
        }
        return false;
    }

    /**
     * 思路:
     * 当两个指针相遇之后,他们继续继续跑,因为快指针比慢指针快一倍,即
     * 快指针一次移动两格。慢指针一次移动一格。那么从同一个起点开始跑时,
     * 他们最终会相遇,此时快指针比满指针多跑了一圈。只要计算慢指针所跑的
     * 路程就可以知道环的长度
     *
     * @param root
     * @return
     */
    public int howCycleLong(ListNode root) {
        if (root == null || root.next == null)
            return -1;
        ListNode p1 = root, p2 = root;
        int cycleLong = 0;
        int meetTime = 0;
        while (p1 != null && p2!=null&&p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            if (meetTime == 1)
                cycleLong++;
            if (meetTime == 2)
                break;
            if (p1 == p2) {
                meetTime++;
            }
        }
        return cycleLong;
    }

    /**
     * 获得环的头结点
     * 思路:
     * 假设从链表的头结点到环头结点的距离为D,环头结点到p1和p2首次相遇的节点处的
     * 距离为S1,抽次相遇的节点到头结点的另一半的距离为S2,
     * 由于:
     * - p1指针一次走一步
     * - p2指针一次走两步
     * 所以在p1和p2相遇时,两个指针走过的路程如下:
     * p1 = D + S1
     * P2 = D + S1 + S1 + S2
     * 由于p2比p1快一倍,所以p2所走的路程是p1走的路程的两倍
     * 即: 2(D+S1)=D+2*S1+S2 => 2*D + 2*S2 = D+ 2*S1 + S2 => D=S2
     * 此时:将p1指针放回到头指针,然后让p1和p2以相同的步长前进,最终当p1和p2相等时,
     * 便是停留在环的头结点上
     *
     * @param root
     * @return
     */
    public ListNode getStartCycle(ListNode root) {
        if (root == null || root.next == null)
            return null;
        ListNode p1 = root, p2 = root;
        while (p1 != null && p2!=null&&p2.next != null) {
            p1 = p1.next;
            p2 = p2.next.next;
            if (p1 == p2)
                break;
        }
        if (p1 != p2){
            return null;
        }
        p1 = root;
        while (p1 != p2 && p1 != null && p2 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p2;
    }

    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(7);
        ListNode node4 = new ListNode(1);
        ListNode node5 = new ListNode(0);
        ListNode node6 = new ListNode(10);
        ListNode node7 = new ListNode(20);

        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = node3;

        IsCycle isCycle = new IsCycle();

        System.out.println(isCycle.isCycle(node1));
        System.out.println(isCycle.howCycleLong(node1));
        ListNode res = isCycle.getStartCycle(node1);
        if (res == null)
            System.out.println(res);
        else
            System.out.println(res.val);

    }
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值