面试官问我有环链表中怎么找到入口,本以为很简单当场却想傻了

简介: 链表是否有环问题看似简单,但实际处理上有很多需要注意的,这个问题是非常高频笔试面试题,记忆不牢固容易遗忘,可以认真看看学习一波!有个小伙伴就在某手面试中遇到了。

链表是否有环问题看似简单,但实际处理上有很多需要注意的,这个问题是非常高频笔试面试题,记忆不牢固容易遗忘,可以认真看看学习一波!有个小伙伴就在某手面试中遇到了。


判断链表是否有环



题目描述:


给定一个链表,判断链表中是否有环。


如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。


如果链表中存在环,则返回 true 。 否则,返回 false 。


20210530215724496.png


你能用 O(1)(即,常量)内存解决此问题吗?


分析:


对于这个问题,如果没有内存空间的限制,首先想到的就是使用哈希的方法,用一个哈希存储节点,然后向下枚举链表节点:


如果发现其中有在哈希中,那么就说明有环返回true。

如果枚举到最后结束,那就说明没有环


20210531003036559.png


但是这样并不满足O(1)空间复杂度的要求,我们应该怎么处理呢?


如果链表尾部有环,如果一个节点枚举到后面会在闭环中不断循环枚举,那么怎么样能高效判断有环并且能快速终止呢?


有环,其实就是第二次、第三次走过这条路才能说它有环,一个指针在不借助太多空间存储状态下无法有效判断是否有环(有可能链表很长、有可能已经在循环了),咱们可以借助 快慢指针(双指针) 啊。


其核心思想就是利用两个指针:快指针(fast)和慢指针(slow),它们两个同时从链表头遍历链表,只不过两者速度不同,如果存在环那么最终会在循环链表中相遇。


我们在具体实现的时候,可以快指针(fast)每次走两步,慢指针(slow)每次走一步。如果存在环的话快指针先进入环,慢指针后入环,在慢指针到达末尾前快指针会追上慢指针。


快慢指针如果有相遇那就说明有环,如果快指针先为null那就说明没环。


20210531105256680.png


具体实现代码为:


/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
    ListNode fast=head;
    ListNode slow=fast;
    while (fast!=null&&fast.next!=null) {
        slow=slow.next;
      fast=fast.next.next;
      if(fast==slow)
        return true;
    }
    return false;    
    }
}


提高:找到环的入口位置



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


为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。


说明:不允许修改给定的链表。


你是否可以使用 O(1) 空间解决此题?


这题相比上一题又难了一些,因为如果链表成环,需要找到入口。


分析:


如果不考虑内存使用,我肯定还会首先考虑哈希,将节点存着然后如果出现第二次则说明有环并直接返回,实现的代码也很简单,走投无路可以用这个方法:


/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        int pos=-1;
        Map<ListNode,Integer>map=new HashMap<ListNode, Integer>();
        ListNode team=head;
        while (team!=null)
        {
            if(map.containsKey(team)){
                pos=map.get(team);
                return team;
            }
            else 
                map.put(team,++pos);
            team=team.next;
        }
        return null;
    }
}

但是怎么使用O(1)的空间复杂度完成这个操作呢?上面一题的思路是使用快慢指针判断是否有环,但是怎么锁定环的入口呢?


这个题看起来是个算法题,实际上是个数学推理题。这题的关键也是快慢指针,不过需要挖掘更多的细节 。


回忆一下快慢指针能够挖掘的细节:


知道慢指针走了x步,快指针走了2x步,但是仅仅知道这两个条件还推导不出什么东西,我们能够进行的操作也只有用O(1)的方法进行一些操作。不过这里面快慢指针和前面有点不同的是我们前面用一个头结点开始计数。


我们还可以进行什么操作?


既然知道相遇的这个点在环内,那么我们可以用一个新的节点去枚举一圈看看环的长度是多少哇!


这里面,我们可以知道fast走的步数2x,slow走的步数x,以及环长y。


20210531175114476.png


我们知道,慢指针是第一次入环,但快指针可能已经走了好几圈,但是多走的步数一定是环的整数倍(不然不可能在同一个位置相遇)。


那么可以得到 快指针步数=慢指针步数+n圈环长度。当然这里n我暂时不知道是多少。换算成公式,那就是 2x=x+ny 消去一个x得到:x=ny。


上面的图我也标注快指针多走的是整数圈数。难点就在这里,需要变通:

快指针多走的x是环长y的整数倍n,慢指针走的x也是环长y的整数倍n。


20210531180250873.png


那么这样有什么用呢?


如果某个节点从起点出发,走到fast,slow交汇点走的是x步(n*y步)。此时,如果某个指针从fast,slow交汇点开始如果走环长的整数倍,那么它到时候还会在原位置。


20210531181301766.png


也就是说从开始head节点team1走x步,从fast,slow交汇节点team2走x步,它们最终依然到达fast,slow交汇的节点,但是在枚举的途中,一旦team1节点遍历的到环内,那么就和team2节点重合了,所以它们一旦相等那就是第一个交汇的点了。


20210531182643502.png


实现代码为:


/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
      boolean isloop=false;
    ListNode fast=new ListNode(0);//头指针
    ListNode slow=fast;
    fast.next=head;
    if(fast.next==null||fast.next.next==null)
      return null;
    while (fast!=null&&fast.next!=null) {
      fast=fast.next.next;
      slow=slow.next;
      if(fast==slow)
      {
        isloop=true;
        break;
      }
    }
    if(!isloop)//如果没有环返回
      return null;
    ListNode team=new ListNode(-1);//头指针 下一个才是head
    team.next=head;
    while (team!=fast) {
      team=team.next;
      fast=fast.next;
    }
    return team;
    }
}


结语



到这里,链表找环问题就解决了,代码分析可能写的不够好,有问题还请指出,再接再厉!加油!


目录
相关文章
|
10月前
|
存储 SQL 算法
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
例如,在一个有 10 个节点的系统中,增加一个新节点,只会影响到该新节点在哈希环上相邻的部分数据,其他大部分数据仍然可以保持在原节点,大大减少了数据迁移的工作量和对系统的影响。狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由”。在 3 - 5 年的中期阶段,随着业务的稳定发展和市场份额的进一步扩大,订单数据的增长速度可能会有所放缓,但仍然会保持在每年 20% - 30% 的水平。
阿里面试:每天新增100w订单,如何的分库分表?这份答案让我当场拿了offer
|
10月前
|
算法 NoSQL 应用服务中间件
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
在 Nacos 的配置管理界面或通过 Nacos 的 API,创建一个名为(与配置文件中 dataId 一致)的配置项,用于存储 Sentinel 的流量控制规则。上述规则表示对名为的资源进行流量控制,QPS 阈值为 10。resource:要保护的资源名称。limitApp:来源应用,default表示所有应用。grade:限流阈值类型,1 表示 QPS 限流,0 表示线程数限流。count:限流阈值。strategy:流控模式,0 为直接模式,1 为关联模式,2 为链路模式。
阿里面试:10WQPS高并发,怎么限流?这份答案让我当场拿了offer
|
Java API
面试官上来就让手撕HashMap的7种遍历方式,当场愣住,最后只写出了3种
面试官上来就让手撕HashMap的7种遍历方式,当场愣住,最后只写出了3种
109 1
|
人工智能 Java
每日一题《剑指offer》链表篇之链表中环的入口节点
每日一题《剑指offer》链表篇之链表中环的入口节点
130 0
每日一题《剑指offer》链表篇之链表中环的入口节点
|
存储 索引
判断环形链表是否有环??返回环形链表的入口点!!
判断环形链表是否有环??返回环形链表的入口点!!
165 1
|
Java 测试技术
【Java】剑指offer(23)链表中环的入口结点
【Java】剑指offer(23)链表中环的入口结点
|
消息中间件 Dubbo Kafka
蚂蚁面试官:Zookeeper 的选举流程是怎样的?我当场懵逼了
面试经常会遇到面试官问 Zookeeper 的选举原理,我心想,问这些有啥用吗?又不要我造火箭! 每次面试也只知道个大概,并没有深究具体的流程,所以在面试的时候总是不能打动面试官,总是特别吃亏,所以这篇就总结一下其中的要点,也希望能帮助大家搞定面试。 有一说一, Zookeeper 这些工作原理、选举流程,也许大多数人在工作中不会用到,但了解多一点也是自己的优势,避免求职面试被面试官打压工资。Zookeeper 也是现在后端主流的分布式协调框架,很多热门框架都有直接或者间接依赖它,比如:Dubbo、Elastic Job、Kafka 等,所以掌握 ZK 选举流程也是非常有必要的。
|
算法
BM7 链表中环的入口结点
BM7 链表中环的入口结点
105 0
【面试必刷TOP101】链表中环的入口结点 & 链表中倒数最后k个结点
【面试必刷TOP101】链表中环的入口结点 & 链表中倒数最后k个结点
133 0
|
存储 C++ 容器
剑指offer(C++)-JZ23:链表中环的入口结点(数据结构-链表)
剑指offer(C++)-JZ23:链表中环的入口结点(数据结构-链表)
111 0