LeetCode:141 环形链表

163 阅读3分钟

「这是我参与2022首次更文挑战的第6天,活动详情查看:2022首次更文挑战

题目

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

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

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

示例 1:

img

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

示例 2:

img

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

示例 3:

img

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

提示:

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

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

解题

解题一:判断重复节点(HashSet)

思路

从头节点开始,依次遍历单链表中的每一个节点,用 HashSet 存放曾经遍历过的 Node 节点,每遍历一个节点判断 HashSet 是否有相同的值,如果发现 HashSet 中存在与之相同的的节点,则说明链表有环,返回 True,如果发现 HashSet 中不存在与之相同的的节点,就把这个新节点存入 HashSet 中,之后进入下一个节点,继续重复刚才的操作。遍历完成后还没有返回值,就返回 -1

代码

/**
 * 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) {
        // 当链表为空,或者只有一个节点时,不可能构成环形链表
        if (head == null || head.next == null) {
            return false;
        }
        // 存放已经遍历过的节点,用于判断是否有相同节点
        HashSet<ListNode> resultSet = new HashSet<>();
        // 遍历链表
        while (head != null) {
            if (resultSet.contains(head)) {
                // 当 resultSet 存在相同节点时,代表形成环形链表,返回 true
                return true;
            }
            // 当resultSet 不存在相同节点时,将遍历的节点存入 resultSet,继续遍历链表
            resultSet.add(head);
            head = head.next;
        }
        // 链表已经遍历完,没有相同节点,代表没有构成环形链表
        return false;
    }
}

总结

特点

  • 使用了 HashSet 作为额外的缓存

注意点

  • 判断是否有相同的节点使用 Node 判断,而不是 Node.val 判断
  • 使用 HashSet 比 HashMap 能更节省空间

性能分析

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
  • 执行用时:4 ms,在所有 Java 提交中击败了 22.35% 的用户
  • 内存消耗:38.8 MB,在所有 Java 提交中击败了 97.67% 的用户

解题二:双指针法(快慢指针)

思路

将链表有环问题改变为追及问题

首先创建两个指针 p1 和 p2,让它们同时指向这个链表的头节点。然后开始一个大循环,在循环体中,让指针 p1 每次向后移动 1 个节点,让指针 p2 每次向后移动 2 个节点,然后比较两个指针指向的节点是否相同。如果相同,则可以判断出链表有环,如果不同,则继续下一次循环,直到 p2 指针指向空

代码

/**
 * 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) {
        // 当链表为空,或者只有一个节点时,不可能构成环形链表
        if (head == null || head.next == null) {
            return false;
        }
        // p1 指针指向头节点的下一个节点
        ListNode p1 = head.next;
        ListNode p2 = null;
        if(p1.next !=null){
            // 当 p1 的下一个节点不为 null 时,设置 p2 指针指向 p1 指针的下一个节点
            p2 = p1.next;
        }
        while(p2 !=null && p2.next !=null && p2.next.next !=null){
            // 满足 p1 走 1 步,p2 走 2 步,开始遍历链表
            if(p1 == p2){
                // 当 p1 == p2 时,代表构成循环链表
                return true;
            }else{
                // 当 p1 != p2 时,继续循环链表,p1 走 1 步,p2 走两步
                p1 = p1.next;
                p2 = p2.next.next;
            }
        }
        // 链表已经遍历完,没有相同节点,代表没有构成环形链表
        return false;
    }
}

动态图片

总结

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 执行用时:0 ms,在所有 Java 提交中击败了 100% 的用户
  • 内存消耗:39.3 MB,在所有 Java 提交中击败了 72.74% 的用户