「前端刷题」141.环形链表(EASY)

73 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情

题目(Linked List Cycle)

链接:https://leetcode-cn.com/problems/linked-list-cycle
解决数:3666
通过率:51.4%
标签:哈希表 链表 双指针 
相关公司:amazon microsoft bytedance 

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

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

 

示例 1:

输入: head = [3,2,0,-4], pos = 1
输出: true
解释: 链表中有一个环,其尾部连接到第二个节点。

示例 2:

输入: head = [1,2], pos = 0
输出: true
解释: 链表中有一个环,其尾部连接到第一个节点。

示例 3:

输入: head = [1], pos = -1
输出: false
解释: 链表中没有环。

 

提示:

  • 链表中节点的数目范围是 [0, 104]
  • -105 <= Node.val <= 105
  • pos 为 -1 或者链表中的一个 有效索引 。

 

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

思路

暴力法

  • cur1 每遍历到一个节点,就让 cur2 从头遍历之前所有节点
  • 如果 cur2 走到 cur1,所用的步数二者一样,则相遇点不是入环点
  • 如果 cur2 走到 cur1,用的步数二者不一样,则相遇点是入环点,cur1比 cur2多走一个环
  • 时间复杂度为 O(n2)O(n^2),空间复杂度为 O(1)O(1)
var hasCycle = (head) => {
  let cur1 = head;    // cur1指针
  let step1 = 0;      // cur1指针走的步数
  while (cur1) {
    step1++;
    let cur2 = head;  // cur2指针从头遍历
    let step2 = 0;    // cur2指针走的步数
    while (cur2) {
      step2++;
      if (cur1 == cur2) {     // cur1和cur2的元素相同
        if (step1 == step2) { // 如果走的步数一样,即走到了cur1这里
          break;              // 退出内层while
        } else {              // 相遇但步数不一样
          return true;        // 说明链表有环。cur1比cur2多走一个环
        }
      }
      cur2 = cur2.next;   // cur2一次走一步
    }
    cur1 = cur1.next;     // cur1一次走一步
  }
  return false;
};
func hasCycle(head *ListNode) bool {
	cur1, step1 := head, 0
	for cur1 != nil {
		step1++
		cur2, step2 := head, 0
		for cur2 != nil {
			step2++
			if cur1 == cur2 {
				if step1 == step2 {
					break
				} else {
					return true
				}
			}
			cur2 = cur2.Next
		}
		cur1 = cur1.Next
	}
	return false
}

方法2:借助哈希表

  • 哈希表存遍历过的节点,每遍历一个节点,都查看哈希表是否存在当前节点,如果存在,则说明链表有环
  • 如果不存在,则存入哈希表,继续遍历
  • 时间复杂度为 O(n),空间复杂度为 O(n)
var hasCycle = (head) => {
  let map = new Map();
  while (head) {
    if (map.has(head)) return true;
    map.set(head, true); // 存的是节点的地址引用,而不是节点值
    head = head.next;
  }
  return false;
};
func hasCycle(head *ListNode) bool {
	hash := map[*ListNode]bool{}
	for head != nil {
		if hash[head] {
			return true
		}
		hash[head] = true
		head = head.Next
	}
	return false
}

快慢指针法

  • 快、慢指针,从头节点出发
  • 慢指针每次走一步,快指针每次走两步,不断比较它们指向的节点的值
  • 如果节点值相同,说明有环。如果不同,继续循环。
  • 类似 “追及问题”
    • 两个人在环形跑道上赛跑,同一个起点出发,一个跑得快一个跑得慢,在某一时刻,跑得快的必定会追上跑得慢的,只要是跑道是环形的,不是环形就肯定追不上。
func hasCycle(head *ListNode) bool {
	slow, fast := head, head
	for fast != nil { // 快指针指向真实节点
		if fast.Next == nil { // 如果Next就出去了,说明没有环
			return false
		}
		slow, fast = slow.Next, fast.Next.Next // 慢的走一步 快的走两步
		if slow == fast {                      // 快慢指针相遇,有环
			return true
		}
	}
	return false // fast走出去了,没有环
}
var hasCycle = (head) => {
  let fast = head;
  let slow = head;
  while (fast) {                        
    if (fast.next == null) return false; 
    slow = slow.next;                 
    fast = fast.next.next;             
    if (slow == fast) return true;   
  }
  return false;                   
}