每日一问:如何判断一个链表有环

41 阅读2分钟

本文最重要的不是如何解决这个有环问题

或许,读者觉得此文为了解决有环的问题,但此文的目的在于升华思维如何产生的?如何快速解决问题.

1.链表及方法定义

type ListNode struct {
   Val  int
   Next *ListNode
}
func hasCycle(head *ListNode) bool {

}

image.png

(通常看到的链表)

2.常规思路分析

有环是个啥特征呢?没有环是一个啥特征呢? 如何解决一个问题的关键是明白问题是什么?如何描述言简意赅地描述清楚问题,那么解决问题就进入了关键的一步了.

image.png

最简单的判断思路:遍历所有节点,看看每个节点是否之前出现过(map),时间复杂度O(N),空间复杂度我们近似看做O(N)

3.传说中快慢指针的思路

思路的设想:不借助其他额外的存储空间,能否判断呢? 思路的精华在于:想到这思路的人,是怎么突破出这么创造性的思维的?(这有点无解,天赐灵感?),我们通常把我们自己代入问题中进行突破,所以产生了发现节点重复的问题.但这个结题的思路的提出者的,放入了两颗棋子(快慢指针),然后观察这两颗棋子在这种局面下产生的特性. 我们不知道谁提出的这个思路,但此人异于常人:他告诉我们,把自己代入问题是常人的思路.把棋子放入棋盘,让自己成为观察者,才是高人.

思路根源:只要在这棋局(有环的情况下)中,里面的棋子无论多厉害,他们都会在原地打转,蜗牛和兔子总会不断重合.

原理:

快慢指针,一前一后;

慢走一步,快走两步;

如果有环,快可追上;

如果无环,快到尽头

如果这两个指针跑到了nil上,表示到了尽头,

如果有环不会到尽头,且两个必然会重合

func hasCycle(head *ListNode) bool {
    //空指针,或者就一个节点,那么必然无环
   if head== nil || head.Next== nil {
      return false
   }
  //快慢指针,一前一后
  slow,fast:=head,head.Next    //不用都在同一起跑线,因为快的在前
    //只要不重合,那么就一直走下去
   for slow!=fast{
      //快指针到头了,说明没有环
      if fast==nil||fast.Next==nil{
         return false
      }
      //慢走一步
      slow=slow.Next
      fast=fast.Next.Next //快走两步
   }
   return true  

}