美文网首页
算法 1.5 链表 + 快慢指针:环形链表【leetcode 1

算法 1.5 链表 + 快慢指针:环形链表【leetcode 1

作者: 珺王不早朝 | 来源:发表于2021-01-04 21:17 被阅读0次

题目描述

给定一个链表,判断链表中是否有环,存在环返回 true,否则返回 false

  • 连续跟踪 next 指针再次到达某个节点,则链表中有环
  • 你能用 O(1)(即,常量)内存解决此问题吗?

提示:
链表中节点的数目范围是 [0, 10^4]
-10^5 <= Node.val <= 10^5
pos 为环开始节点的索引,若 pos = -1,则没有环
pos 为 -1 或者链表中的一个 有效索引
pos 不作为参数进行传递,仅仅是为了标识链表的实际情况

注意:pos 只是帮助理解测试用例,并不是真正的输入值

数据结构

  • 数组、指针变量

算法思维

  • 遍历、双指针、快慢指针、追击

解题要点

  • 链表的特点
  • 快慢指针的思想以及使用
  • 模型的应用:涉及"环"的问题 --> 追击问题 --> 快慢指针

解题步骤

一. Comprehend 理解题意

可以理解为"重复访问"问题,检测链表的某节点能否二次到达

  • 需要一个容器记录已经访问过的节点
  • 每次访问到新的节点,都与容器中的记录进行匹配,若相同则存在环
  • 若匹配之后没有相同节点,则存入容器,继续访问新的节点
  • 直到访问节点的next指针返回null,或者当前节点与容器的某个记录相同,操作结束

也可以理解为“追击”问题,如果存在环,跑得快的一定能追上跑得慢的

  • 就像一快一慢两个运动员,如果在直道赛跑,不存在追击问题;
    如果是在环道赛跑,快的绕了一圈肯定可以追上慢的
  • 定义快慢两个运动员,指向链表的第一、第二个节点:slow = head; fast = head.next;
  • 快运动员的步长为2,慢的为1,即fast每次移动两个节点,slow每次移动一个节点
  • 若最终 fast == slow ,说明存在环;若 fast == null || fast.next == null,操作结束
二. Choose 选择数据结构与算法
解题方法
  • 解法一:二次到达法
  • 解法二:追击问题法
解法一:二次到达法

数据结构:数组(或者:链表、栈、队列)
算法思维:遍历

解法二:追击问题法

数据结构:指针变量 x 2
算法思维:遍历、双指针(快慢指针)

三. Code 编码实现基本解法
解法一:二次到达法 -- 思路分析
  1. 定义数组记录已访问节点:new ListNode[10000];
  2. 遍历链表的每个节点,并与容器中已存放的节点依次比较:
    • 相同则方法结束,返回 true
    • 不同则存入最新位置,继续遍历下个节点
  3. 若 next 指针为 null,则方法结束,返回 false
边界问题
  • 数组越界:
    链表最多有一万个节点,容器不会越界;
    与容器中节点进行比对,正向遍历容器,元素为 null 时终止,后续都是未使用的空间
细节问题
  • 遍历链表,通过 head=head.next 进行迭代
    当且仅当此节点与容器某个节点相同时返回 true,其它情况都返回 false
    比较相等时可以用 "=="(比较地址)
class Solution {
    public boolean hasCycle(ListNode head) {
        // 1.定义数组记录已访问节点 
        ListNode[] array = new ListNode[10000];
        // 2.遍历链表的每个节点, 
        while(head != null) {
            // 并与容器钟已存放的节点依次比较 
            for(int i = 0; i < array.length; i++) {
                if(array[i] == head) {
                    return true;
                }
                if(array[i] == null) {
                    array[i] = head; // 将当前节点存放到最新位置 
                    break; // 结束容器的遍历 
                }
            }
            head = head.next;
        }
        // 3.若next指针为null,则方法结束,返回false
        return false;
    }
}

时间复杂度:O(n2) -- 遍历数组 O(n),每个节点都需要额外再遍历一次数组 O(n2)
空间复杂度:O(1) -- 固定长度 10000 的数组 O(1)
执行耗时:108 ms,击败了 5.26% 的Java用户
内存消耗:38.3 MB,击败了 99.89% 的Java用户

四. Consider 思考更优解
剔除无效代码,优化空间消耗
  • 每个节点都需要遍历容器查找,比较耗时
  • 按最大测试数据量创建容器,空间消耗巨大
寻找更好的算法思维
  • 要证明某个节点是否第二次到达,可否将已遍历节点进行标记?
  • 环形结构对应生活中的追击问题,可否使用“追击问题”模拟实现?
  • 借鉴其它算法
五. Code 编码实现最优解
解法二:追击问题法 -- 思路分析
  1. 定义快慢两个指针:slow = head; fast = head.next;
  2. 遍历链表:
    • 快指针步长为 2:fast = fast.next.next;
    • 慢指针步长为 1:slow = slow.next;
  3. 当且仅当快慢指针重合,有环,返回 true
  4. 快指针为 null,或其 next 指向 null,没有环,返回 false,操作结束
边界问题
  • fast 和 fast.next 指针的非空判断
  • slow.next 指针不需要非空判断
    若有环,则始终有:slow.next != null
    若无环,则 fast 或 fast.next 先为空
细节问题
  • 需要注意:由于 fast 的步长为 2,因此 fast == nullfast.next == null 都需要判断
class Solution {
    public boolean hasCycle(ListNode head) {

        //0.非空判断
        if (head == null) return false;

        //1.声明快慢两个指针
        ListNode slow = head;
        ListNode fast = head.next;

        //2.利用短路特性,先判断 fast 是否为空,再判断 fast.next 是否为空
        while (slow != fast && fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow == fast;
    }
}

时间复杂度:O(n) -- 遍历链表 O(n)
空间复杂度:O(1) -- 两个指针变量 O(1)
执行耗时:0 ms,击败了 100% 的Java用户
内存消耗:38.8 MB,击败了 88.78% 的Java用户

六. Change 变形与延伸
题目变形
  • (练习)分别使用 head 和 head.next 作为链表的 快/慢 指针
  • (练习)标记值法:对节点的 val 属性进行标记,赋一个超出合法范围的值
  • (练习)hash 表法
延伸扩展
  • 龟兔赛跑问题,追击问题
  • 地球,火星与太阳连为一线的问题

相关文章

  • 算法 1.5 链表 + 快慢指针:环形链表【leetcode 1

    题目描述 给定一个链表,判断链表中是否有环,存在环返回 true,否则返回 false 连续跟踪 next 指针再...

  • Tourist with Data Structure Seco

    链表 读题要仔细,只看题干,容易死的很惨。 设计链表 环形链表 一般环形链表使用快慢指针方式去做,快慢指针算法。参...

  • 双指针

    一、双指针总结 1.1题目 快慢指针(主要解决链表中的问题) 141.环形链表 142.环形链表 II 876.链...

  • 热题 HOT 100(1-10)

    环形链表 1.给定一个链表,判断链表中是否有环。 将快指针的移动速度设置为慢指针的两倍,将快慢指针同时遍历链表,若...

  • 每日Leetcode—算法(14)

    141.环形链表 算法(快慢指针): 155.最小栈 算法一: 算法二: 167. 两数之和 II - 输入有序数...

  • 数据结构与算法整理

    (1)链表的技巧 快慢指针(找环,环入口,环长度) 双指针(倒数K个节点) 合并链表(递归求解) 约瑟夫环(环形链...

  • leetcode刷题-链表

    链表题目汇总: 主要是发现 206 141环形链表(快慢指针) 21写递归 19 876 快慢指针,一个走2步,一...

  • Java面试题集四

    1、怎么判断环形链表 使用快慢指针:创建两个指针,同时指向这个链表的头节点,然后开始循环,指针1每次移动一个结点,...

  • 2.链表类的设计

    反转链表 检查是否是环形链表 快慢指针,快指针走两步,慢指针走一步,相遇或者能找到nil尾结点 删除链表中的重复元...

  • 快慢指针的应用

    --- 最近在刷LeetCode时,发现快慢指针在链表和查找算法中应用非常广泛,对于很多即将面试或者学习算法的同学...

网友评论

      本文标题:算法 1.5 链表 + 快慢指针:环形链表【leetcode 1

      本文链接:https://www.haomeiwen.com/subject/xxbuoktx.html