题目:环形链表

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

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

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

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/linked-list-cycle

  1. // 输入:
  2. head = [3,2,0,-4], pos = 1
  3. // 输出:
  4. true
  5. // 解释:链表中有一个环,其尾部连接到第二个节点
  6. // 输入:
  7. head = [1,2], pos = 0
  8. // 输出:
  9. true
  10. // 解释:链表中有一个环,其尾部连接到第一个节点。
  11. // 输入:
  12. head = [1], pos = -1
  13. // 输出:
  14. false
  15. // 解释:链表中没有环。

理解题意

判断链表是否有环,如果遍历的过程中存在一个指针指向了已经遍历过的节点上,证明此时会形成一个环。

数据结构及算法思维的选择

数据结构:数组
算法思维:遍历

基本解法及编码的实现

暴力解法:


// head的值可能会相等
// 这个问题导致我解题出错
function hasCycle(head: ListNode | null): boolean {
    const arr = [];
    while( head && (head.next !== null)) {
        for(let i = 0; i < arr.length; i++) {
            if (arr[i] === head) {
                return true
            }
        }
        arr[arr.length] = head;
        head = head.next;
    }

    return false;
};

时间复杂度:O(n ^ 2)

  1. 遍历整个链表 O(n)
  2. 遍历数组进行匹配 O(n - 1)

空间复杂度:O(1)

  1. 数组长度是已知的(我觉得应该是O(n))

    思考最优解

  2. 剔除无效代码或优化空间消耗

    1. 每个节点都需要遍历容器查找,比较耗时
    2. 按最大测试数据量创建容器,空间消耗大
  3. 寻找更好的算法思维

    1. 快慢指针

      最优解思路及编码实现

      使用两个指针,一个指针遍历快、一个指针遍历慢,等快指针追上慢指针就证明有环
      最优解:
      边界细节问题和复杂度分析
  4. 遍历的停止条件就是快指针或者快指针next指向null

    var hasCycle = function(head) {
     if(!head) {
         return false;
     };
    
     let slow = head, fast = head.next;
    
     // 必然是快指针先到达终点
     while(fast !== null && fast.next !== null) {
         if (fast === slow) {
             return true;
         };
    
         fast = fast.next.next;
         slow = slow.next;
     };
    
     return false
    };
    

    时间复杂度:O(n)

  5. 遍历整个链表

空间复杂度: O(1)

  1. 只定义了两个指针

    总结

  2. 快慢指针的思想和使用