leetcode之判断环形链表以及入环点,简单易懂!

面试的时候经常会遇到要判断一个链表是否有环,今天就来总结一下如何判断一个链表是否有环,以及链表环长、链表的入环点。

首先我们来看一下如何判断链表是否有环
1.利用哈希表
  • 我们可以在遍历的时候将每一个链表节点存入到哈希表中,然后在遍历的过程中寻找是否有这个节点了,如果有,那就是环形链表,如果没有,继续遍历,直到链表节点为空(null)。这个思路很简单也很容易懂,我们直接看代码。
public boolean hasCycle(ListNode head) {
    Set<ListNode> set = new HashSet<>();//这边利用set集合
    while (head != null) {
        if (set.contains(head)) {//如果包含了链表节点,那么我们就直接返回true
            return true;
        } else {
            set.add(head);
        }
        head = head.next;
    }
    return false;
}
2.利用双指针(快慢指针法)
  • 我们定义一个慢指针slow(每次前进1步),一个快指针fast(每次前进2步),刚开始的时候他们都指向头结点。根据以前学的知识,如果是个环形链表,快指针一定会在某一时刻追上慢指针,并且在他们首次相遇的时候,快指针一定比慢指针多移动了一圈!
    1
    2
    3
    4
    5
    是不是很简单?很多算法中经常会用到快慢指针,下面附上代码:
public boolean hasCycle(ListNode head) {
     if(head == null){
         return false;
     }
     ListNode slow = head;
     ListNode fast = head;
      //因为如果链表无环,那么一定是快指针先到达null
     while(fast != null && fast.next != null){
         slow = slow.next;
         fast = fast.next.next;
         if(fast == slow){
             return true;
         }
     }
     return false;
 }

接着我们来看一下如何判断环形链表的长度
  • 我们定义的快指针恰好比慢指针每次多走1步,并且他们首次相遇的时候,快指针比慢指针多跑了1圈,那么
    环 长 = 快 指 针 前 进 的 步 数 − 慢 指 针 前 进 的 步 数 环长=快指针前进的步数-慢指针前进的步数 =
public class Main{
    static class Node{
        int data;
        Node next;
        Node(int data){
            this.data = data;
        }
    }
    public static void main(String[] args) {
        Node node1 = new Node(5);
        Node node2 = new Node(2);
        Node node3 = new Node(1);
        Node node4 = new Node(7);
        Node node5 = new Node(4);
        Node node6 = new Node(3);
        Node node7 = new Node(8);
        Node node8 = new Node(9);
        
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = node8;
        node8.next = node3;
        System.out.println(nodeLen(node1));
    }
    //判断环长
    public static int nodeLen(Node head){
        Node slow = head;
        Node fast = head;
        int slow_count = 0;//记录慢指针前进的步数
        int fast_count = 0;//记录快指针前进的步数
        while(fast != null && fast.next != null){
            slow = slow.next;
            slow_count++;
            fast = fast.next.next;
            fast_count += 2;
            if(fast == slow){
                return fast_count - slow_count;
            }
        }
        return 0;
    }
}

接着我们来看一下如何判断环形链表的入环点

在这里插入图片描述

  • 我们试想一下,如果定义起点到入环点的距离为d,入环点到快慢指针首次相遇的点为s1,首次相遇点到入环点的距离为s2,那么我们可以得出以下结论:当快慢指针相遇的时候,
    慢指针假设行动了:
    d 1 = d + x ∗ ( s 1 + s 2 ) + s 1 d1=d+x*(s1+s2)+s1 d1=d+x(s1+s2)+s1
    快指针行动了:
    d 2 = d + n ∗ ( s 1 + s 2 ) + s 1 d2=d+n*(s1+s2)+s1 d2=d+n(s1+s2)+s1
    因为我们这里快指针每次是慢指针行动的2倍,即:
    2 ∗ d 1 = d 2 2*d1=d2 2d1=d2
    所以两个公式整理得:
    d = ( n − 2 x − 1 ) ( s 1 + s 2 ) + s 2 d=(n-2x-1)(s1+s2)+s2 d=(n2x1)(s1+s2)+s2
    也就是说:
    链 表 的 起 点 到 入 环 点 的 距 离 = 首 次 相 遇 点 开 始 绕 环 行 动 ( n − 2 x − 1 ) 圈 之 后 + 首 次 相 遇 点 到 入 环 点 的 距 离 。 链表的起点到入环点的距离=首次相遇点开始绕环行动(n-2x-1)圈之后+首次相遇点到入环点的距离。 =n2x1+
  • 我们可以假设n=2x+1(因为绕环跑了0、1、2、3…m圈的结果都是一样的,最后都是加上s2的长度,这里我们就可以假设绕环行动了0圈),那么我们就可以在找到首次相遇点的时候,定义一个指针指向链表的起点,一个指针指向首次相遇点,然后两个指针每次前进1步,当两个指针相遇的时候就是链表的入环点。
public ListNode detectCycle(ListNode head) {
	  if(head == null || head.next == null){
         return null;
      }
     ListNode slow = head;
     ListNode fast = head;
     ListNode first = null;
     ListNode now = null;
     while(fast != null && fast.next != null){
         slow = slow.next;
         fast = fast.next.next;
         if(slow == fast){
             first = head;
             now = slow;
             break;
         }
     }
     if(first != null && now != null){
         while(first != now){
             first = first.next;
             now = now.next;
         }
         if(first == now){
             return now;
         }
     }
     return null;
 }
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值