双指针 - 判断链表是否存在环

双指针 - 判断链表是否存在环

题目:141. Linked List Cycle

题目如下:

Given head, the head of a linked list, determine if the linked list has a cycle in it.

There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to. Note that pos is not passed as a parameter.

Return true if there is a cycle in the linked list. Otherwise, return false.

解题思路:

这道题也挺长的,而且想要能够答出这题就必须知道什么是链表。

  1. 什么是链表

    简单的描述一下就是,链表是链式关联的一个个节点,每个节点会存储自身的数据和下一个节点的位置,如图:

  2. 链表中是否存在一个环

    也就是说,顺着第一个节点 head 一直向下找,最终能够找回到链表中的某个节点,这就代表这链表中存在一个闭环,如图:

  3. 如果存在闭环,则返回 true,不存在闭环,则返回 false

  4. 寻找到闭环的方法

    已知顺着当前节点能够找到下一个节点,而形成闭环的要求就是,链表中的一个节点的下一个节点,是存在于链表内部的节点,这样就会形成无限循环。

    所以根据定义就可以知道,如果能找到一个节点,它的下一个节点存在于链表中,那么就能够存在闭环。

  5. 这道题提示是双指针

    所以可以设定两个指针都指向自己,在每次移动时,指针 1 移动 1 个节点,指针 2 移动两个节点。

    如果存在闭环,那么在移动过程之中二者一定会相遇;如果不存在闭环,移动数量比较多的那个指针总会遇到空值——尾节点的下一个地址的引用对象必然为 undefined。

根据解题思路解题:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
  // 边界条件
  // 如果多加一个边界条件 head.next.next === null
  // 就能提速16ms,就差不多是20%
  // 所以说最能提速的方法还是多加边界条件……
  if (head === null || head.next === null || head.next.next === null) {
    return false;
  }

  let headNode = head,
    tailNode = head.next;
  while (
    headNode !== null &&
    headNode.next !== null &&
    tailNode !== null &&
    tailNode.next !== null &&
    tailNode.next.next !== null
  ) {
    if (headNode === tailNode) return true;
    headNode = headNode.next;
    tailNode = tailNode.next;
    tailNode = tailNode.next;
  }
  return false;
};

其实如果没有双指针这个提示的话,我大概会用 Set 去解这道题:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
  if (head === null || head.next === null) {
    return false;
  }

  let curr = head.next;
  const nodes = new Set([head]);

  while (curr !== null) {
    nodes.add(curr);
    curr = curr.next;
    if (nodes.has(curr)) {
      return true;
    }
  }
  return false;
};

如果下一个节点是在 Set 中已有的元素,那么就代表有闭环,可以即时返回。

理论上来说会快一些,不过从实际测试的角度上来说,时间没怎么提速,反倒是空间有些浪费:

第一、二条数据是双指针,第三条数据是使用 set 的解题思路,使用 set 提速还不如多加一个边界条件(笑)

不过正常来说,runtime 能够超过 50%就差不多了,70-80%应该已经算是比较优的解题思路了,想要 90%以上,真的是只能靠加莫名奇妙的边界条件来提前返回。

可以加奇奇怪怪的边界条件,但是没必要。

日常开发还是要以代码可读性为准,如果因为加了特别多的边界条件而降低了代码的可读性,那么就得不偿失了。


最近在用 python 刷题,所以补充一下 python 的解法:

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        slow, fast = head, head

        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next

            if slow == fast:
                return True

        return False


现在回头看看,上面用 JS 解的确实太过冗杂了……直接判断 fast 的 truthy value 就可以了,毕竟不可能存在 fast. 不为空,但是 slow.为空的情况

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值