力扣最热一百题——环形链表

目录

题目链接:141. 环形链表 - 力扣(LeetCode)

题目描述

示例

提示:

解法一:哈希表

总结

Java写法:

运行时间

C++写法:

运行时间 

解法二:快慢指针

Java写法:

运行时间

C++写法:

 

运行时间

总结

哈希表法

快慢指针法(Floyd判圈算法)


题目链接:141. 环形链表 - 力扣(LeetCode)

注:下述题目描述和示例均来自力扣

题目描述

        给你一个链表的头节点 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]
  • -10^5 <= Node.val <= 10^5
  • pos 为 -1 或者链表中的一个 有效索引 。


解法一:哈希表

  1. 初始化
    • 创建一个 set集合(哈希表) 来存储已经访问过的节点指针。这个集合将用于快速检查一个节点是否已经被访问过。
    • 创建一个指针cur,并将其初始化为链表的头节点head
  2. 遍历链表
    • 使用一个while循环来遍历链表。这里采用死循环,但是死循环中有结束的逻辑,所以不用担心死循环的问题,或者你可以写成cur != null,然后删去第一个if判断也是一样的效果。
    • 在每次循环中,首先使用set自带的函数来检查当前节点cur是否已经在set集合中。
      • 如果查询函数显示 cur已经在集合中,即之前已经访问过这个节点,因此链表存在环,函数返回true
    • 如果当前节点cur不在set集合中,就将其添加到集合中,表示这个节点已经被访问过。
    • 然后,将cur移动到下一个节点,即cur = cur.next
  3. 遍历结束
    • 如果链表遍历完成(即cur变为null),且没有在遍历过程中发现环,则函数返回false,表示链表没有环。

总结

        这个算法利用了哈希集合的快速查找特性来高效地检测链表中的环。通过遍历链表并记录已经访问过的节点,我们可以在O(n)的时间复杂度内完成检测,其中n是链表中节点的数量。这种方法的空间复杂度也是O(n),因为最坏情况下,我们需要将链表中的所有节点都添加到集合中。然而,对于检测链表环的问题来说,这种空间复杂度是可以接受的。

Java写法:

/**
 * 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) {
        ListNode cur = head;
        Set<ListNode> haveNode = new HashSet<>();
        while (true){
            if (cur == null){
                // 不是环走出来了
                return false;
            }
            if (haveNode.contains(cur)){
                // 如果包含那么就是环了
                return true;
            }
            // 加入
            haveNode.add(cur);
            // 下一位
            cur = cur.next;
        }
    }
}
运行时间

C++写法:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {  
public:  
    bool hasCycle(ListNode *head) {  
        std::unordered_set<ListNode*> haveNode;  
        ListNode* cur = head;  
        while (true) {  
            if(cur == NULL){
                return false;
            }
            if (haveNode.find(cur) != haveNode.end()) {  
                // 如果在集合中找到了当前节点,说明存在环  
                return true;  
            }  
            // 将当前节点添加到集合中  
            haveNode.insert(cur);  
            // 移动到下一个节点  
            cur = cur->next;  
        }  
        // 如果遍历完整个链表都没有找到环,则返回false  
        return false;  
    }  
};
运行时间 



解法二:快慢指针

  1. 初始化
    • 定义两个指针,通常称为slow(慢指针)和fast(快指针),并将它们都指向链表的头节点head
  2. 移动指针
    • 在循环中,快指针fast每次向前移动两步(即fast = fast.next.next),而慢指针slow每次向前移动一步(即slow = slow.next)。
    • 注意:在移动快指针之前,需要检查fastfast.next是否都不为null,以确保快指针的两步跳跃是安全的。如果任一为null,则说明链表没有环,且快指针已经到达链表末尾,可以直接返回false
  3. 检查相遇
    • 在每次循环中,检查快指针fast和慢指针slow是否指向同一个节点(即fast == slow)。
    • 如果它们在某次迭代中相遇,那么可以断定链表中存在环。因为快指针每次移动两步,慢指针每次移动一步,如果有环,那么快指针最终会追上慢指针。
  4. 循环结束
    • 如果快指针已经到达链表末尾(即fastfast.nextnull),但快慢指针尚未相遇,则说明链表中没有环,可以返回false
  5. 返回结果
    • 如果快慢指针相遇,则返回true,表示链表中有环。
    • 如果循环正常结束(即快指针到达链表末尾),则返回false,表示链表没有环。

        这个算法的时间复杂度是O(n),其中n是链表中节点的数量,因为最坏情况下快指针需要遍历整个链表一次才能与慢指针相遇或到达链表末尾。空间复杂度是O(1),因为只使用了两个指针。

Java写法:

/**
 * 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;  
        }  
          
        ListNode fast = head;  
        ListNode slow = head;  
          
        // 循环直到快指针到达链表末尾或快慢指针相遇  
        while (fast != null && fast.next != null) {  
            fast = fast.next.next;  // 快指针每次跳两步  
            slow = slow.next;       // 慢指针每次跳一步  
              
            if (fast == slow) {  
                // 快慢指针相遇,说明链表有环  
                return true;  
            }  
        }  
          
        // 如果没有相遇,且快指针已经到达链表末尾,说明链表无环  
        return false;  
    }  
}
运行时间

C++写法:

 

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {  
public:  
    bool hasCycle(ListNode *head) {  
        if (head == nullptr || head->next == nullptr) {  
            return false;  
        }  
          
        ListNode *slow = head;  
        ListNode *fast = head;  
          
        while (fast != nullptr && fast->next != nullptr) {  
            slow = slow->next;  
            fast = fast->next->next;  
              
            if (slow == fast) {  
                return true;  
            }  
        }  
          
        return false;  
    }  
};  
运行时间

 


总结

哈希表法

优点

  • 通用性强:不仅可以用于链表,还可以用于其他需要检测重复元素的数据结构。
  • 能够提供更多信息:除了检测环的存在外,还可以确定环中元素的具体位置。

缺点

  • 时间复杂度:虽然也是O(n),但在实际应用中,由于哈希表操作的常数因子可能较大,性能可能略逊于快慢指针法。
  • 空间复杂度高:O(n),需要额外的存储空间来存储访问过的节点。

应用场景

  • 当链表可能包含大量重复元素,且需要快速定位这些元素时。
  • 当内存使用不是主要关注点,且需要更通用或更详细的检测结果时。

快慢指针法(Floyd判圈算法)

优点

  • 时间复杂度低:O(n),其中n是链表中节点的数量。在最坏情况下,快指针也只需要遍历整个链表一次。
  • 空间复杂度低:O(1),因为它只使用了两个指针变量,不需要额外的存储空间。
  • 实现简单:算法逻辑直观,易于理解和实现。

缺点

  • 仅能检测环的存在,无法直接确定环的起点或环的长度。
  • 仅限于链表结构,对于其他数据结构(如树)不适用。

应用场景

  • 当链表可能包含环,且内存使用是一个关键考虑因素时。
  • 需要快速检测链表是否有环的场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WenJGo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值