数据结构学习--环形链表

环形链表

我们在判断一个链表是否是环形的,即首尾相连,我们可以以使用快慢指针,如果快指针能再次追上慢指针,就说明该链表是环形的,这边可以举个操场跑步的例子,当操场是环形的,跑的快的,就可以对跑的慢的实现套圈.

判断环形链表

给你一个链表,如果其是环形链表,则返回 true 否则返回 false

存放到集合中

分析:通过集合里面查找来判断是否回到头指针,不建议

java 复制代码
public boolean hasCycle(ListNode head) {
    Set<ListNode> set = new HashSet<>();
    while (head != null) {
        //如果重复出现说明有环
        if (set.contains(head))
            return true;
        //否则就把当前节点加入到集合中
        set.add(head);
        head = head.next;
    }
    return false;
}

快慢指针(快2慢1)

java 复制代码
package LinkList;

public class roundList {
    public class Solution {
        class ListNode {
            int val;
            ListNode next;

            public ListNode(int val, ListNode next) {
                this.val = val;
                this.next = next;
            }
        }
        public boolean hasCycle(ListNode head) {
            if (head == 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;
        }
    }
}

先反转再比较

如果反转过后的头结点与原来的头结点相同,那么就是环形链表

反转原理:

  1. 先给头结点的下个结点赋值一个新结点 temp
  2. 然后把头结点的 next 指向一个新节点 newHead
  3. 然后把那个新节点 newHead 指向 头结点
  4. 然后再令头结点 为 temp (之前头结点的下一个结点)
    在上面的流程中,temp充当相当于完成原来的链表的下个位置的指针,而newHead相当于是一种前驱指针的感觉,让原来在后面的元素指向前面的元素
java 复制代码
public ListNode reverseList(ListNode head) {
        //新链表
        ListNode newHead = null;
        while (head != null) {
            //先保存访问的节点的下一个节点,保存起来
            //留着下一步访问的
            ListNode temp = head.next;
            //每次访问的原链表节点都会成为新链表的头结点,
            //其实就是把新链表挂到访问的原链表节点的
            //后面就行了
            head.next = newHead;
            //更新新链表
            newHead = head;
            //重新赋值,继续访问
            head = temp;
        }
        //返回新链表
        return newHead;
    }

    public boolean hasCycle(ListNode head) {
        ListNode rev = reverseList(head);
        if (head != null && head.next != null && rev == head) {
            return true;
        }
        return false;
    }

快慢指针的一般化

我们可以知道快指针要追上慢指针 需要多跑至少"一圈",即一个链表长度,假设为m,当然所需要的时间就是 = 需要的圈数 / (快指针的速度 - 慢指针的速度)

为什么说多跑至少一圈?不妨想,快指针的速度 - 慢指针的速度 为偶数,而链表长度为奇数,那么就会出现第一次快指针追慢指针时,它们位置没有重合,而是快指针直接超越慢指针的情况,那么 快指针需要再追一圈,此时的圈数 = 2 * m,就一定为偶数就能追上

所以 所需要的时间就是 = 需要的圈数 / (快指针的速度 - 慢指针的速度) 必须为整数,当速度差分之圈数不是整数时,就需要多跑几圈来实现整除(即快慢指针的位置重合)

当然上面假设的环可能是从链表某处开始形成的环,有可能不是整个链表是环形

注意:循环的条件是快指针当前的不为null,如果要一次前进 m 步(即 fast = fast.m next)(m 个 next),那么就要保证其前面 m - 1个 next都不为null,否则会抛出空指针异常

代码(快3慢1)
java 复制代码
ublic boolean hasCycle(ListNode head) {
        if (head == null)
            return false;
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null && fast.next.next != null) {
            slow = slow.next;
            fast = fast.next.next.next;
            if (slow == fast)
                return true;
        }
        return false;
    }
代码(快5慢1)
java 复制代码
 public boolean hasCycle(ListNode head) {
        if (head == null)
            return false;
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null && fast.next.next != null && fast.next.next.next != null && fast.next.next.next.next != null) {
            slow = slow.next;
            fast = fast.next.next.next.next.next;
            if (slow == fast)
                return true;
        }
        return false;
    }
代码(快3慢2)
java 复制代码
 public boolean hasCycle(ListNode head) {
        if (head == null)
            return false;
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null && fast.next.next != null) {
            slow = slow.next.next;
            fast = fast.next.next.next;
            if (slow == fast)
                return true;
        }
        return false;
    }

环形链表II

给你给定一个链表的头节点 head ,返回链表开始入环的第一个节点(在哪里开始形成环的)。 如果链表无环,则返回 null

分析

注:来源于魏梦舒老师的《算法漫画》

主要抓住两点,第一次相遇两指针的关系,一个走的路程是另外一个两倍

代码

java 复制代码
public class roundListPlus {
    class ListNode{
        int val;
        ListNode next;

        public ListNode(int val) {
            this.val = val;
            next = null;
        }
    }
    public class Solution {
        public ListNode detectCycle(ListNode head) {
            ListNode fast = head;
            ListNode slow = head;
            // 快指针每次移动2,慢指针每次移动1
            // 用循环来寻找第一次相遇的点
            while (fast != null && fast.next != null){
                fast = fast.next.next;
                slow = slow.next;
                if (fast == slow){
                    break;
                }
            }
            // 循环结束要么是该表非环形,要么是快慢指针相遇
            // 判断,如果非环形链表,直接返回 null
            if ( fast == null || fast.next == null){
                return null;
            }
            // 根据递推关系,此时把其中一个指针放回头结点,另一个指针在第一次相遇位置
            // 此时两个指针一次都只移动1,下一次相遇就是入环结点
            slow = head;
            while (fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return slow;
        }
    }
}
相关推荐
奶香臭豆腐25 分钟前
C++ —— 模板类具体化
开发语言·c++·学习
波音彬要多做1 小时前
41 stack类与queue类
开发语言·数据结构·c++·学习·算法
Noah_aa1 小时前
代码随想录算法训练营第五十六天 | 图 | 拓扑排序(BFS)
数据结构
m0_748256782 小时前
WebGIS实战开源项目:智慧机场三维可视化(学习笔记)
笔记·学习·开源
KpLn_HJL2 小时前
leetcode - 2139. Minimum Moves to Reach Target Score
java·数据结构·leetcode
南七澄江3 小时前
各种网站(学习资源及其他)
开发语言·网络·python·深度学习·学习·机器学习·ai
AC使者8 小时前
5820 丰富的周日生活
数据结构·算法
无 证明8 小时前
new 分配空间;引用
数据结构·c++
机智的叉烧9 小时前
前沿重器[57] | sigir24:大模型推荐系统的文本ID对齐学习
人工智能·学习·机器学习
量子-Alex10 小时前
【多模态聚类】用于无标记视频自监督学习的多模态聚类网络
学习·音视频·聚类