算法与数据结构-链表

算法与数据结构-链表

  • 链表
    • 单链表
      • 概述
      • 任务:英雄结点
    • 双向链表
      • 概述
      • 代码示例(修改自单向链表)
      • 练习
        • 430. 扁平化多级双向链表
    • 环形链表
      • 概述
      • 代码实现
      • 练习
        • 141. 环形链表
        • 142. 环形链表 II

链表

单链表

概述

  1. 链表是以结点的方式进行存储
  2. 每个结点包含data域,next域:指向下一个结点
  3. 存储位置是无序的 and 未知的,但是存储的数据是有序的
  4. 链表分带头结点和不带头结点的链表,根据实际需求来

提问:数组和单链表有什么区别?

算法与数据结构-链表_第1张图片

  • 数组的存储位置是连续的,链表的存储位置是不连续的

  • 数组访问数据速度快,但是增加和删除数据的效率低;链表则与其相反

    算法与数据结构-链表_第2张图片

  • 数组存储内容就是数据,而链表存储内容是数据和下个结点的地址

  • 数组的存储空间是静态的,连续分布的,导致存储空间会出现过剩或者溢出现象

    链表的存储空间是动态分布的,只要内存空间尚有空闲,就不会产生溢出;链表中每个结点是可以动态变化的,空间利用率会更好

任务:英雄结点

任务要求:对英雄人物的创建,删除,更新,查找的实现

主函数调用

HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
HeroNode hero2 = new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
HeroNode hero4 = new HeroNode(4, "李逵", "黑旋风");
HeroNode hero5 = new HeroNode(5, "林冲", "豹子头");

// 创建要给的链表
SingLeLinkedListDemo demo  = new SingLeLinkedListDemo();

// 加入4个节点
demo.addByOrder(hero1);
demo.addByOrder(hero3);
demo.addByOrder(hero2);
demo.addByOrder(hero4);
demo.update(hero5);
demo.delete(hero1);

// 显示链表
demo.list();

// 显示英雄的个数
System.out.println(demo.heroCount(SingLeLinkedListDemo.getHead()));
System.out.println("**************************************");

// 删除对应的英雄观察来判断正确
demo.delete(SingLeLinkedListDemo.bottomNode(SingLeLinkedListDemo.getHead(),4));
demo.list();
System.out.println("**************************************");

// 单链表的反转[腾讯面试题]
demo.ReverseHero(SingLeLinkedListDemo.getHead());
demo.list();
System.out.println("**************************************");
demo.ReverseHero2(SingLeLinkedListDemo.getHead());
demo.list();

定义一个链表类,用来生成每一个结点

class HeroNode{
    private int no;
    private  String name;
    private  String nickname;
    public  HeroNode next; // 指向下一个节点

    // 无参/有参构造函数
    public  HeroNode(){}
    public HeroNode(int no,String name,String nickname){
        this.name = name;
        this.no = no;
        this.nickname = nickname;
    }

    // 获得编号的方法
    public int GetNo(){
        return no;
    }

    // 获得姓名的方法
    public String GetName(){
        return name;
    }

    // 获得外号的方法
    public String GetNickname(){
        return nickname;
    }

    // 显示方便,重写toString()方法
    @Override
    public  String toString(){
        return "HeroNode[no="+no+",name="+name+",nickname"+nickname+"]";
    }
}

核心代码:具体实现算法

我分步骤进行讲述:

1.先初始化一个头节点,头节点不要动,不放具体数据

private static HeroNode head = new HeroNode(0,"","");
// 获得头结点
 public static HeroNode getHead() {
     return head;
 }

// 计算英雄的个数(单链表的节点个数)
public static int heroCount(HeroNode heroNode){
    int Length = 0;
    if(head == null){
        return 0;
    }
    HeroNode temp = head;
    while(temp.next != null){
        Length++;
        temp = temp.next;
    }
    return Length;
}

2.添加英雄结点(默认添加到最后一个位置)

// 添加节点到单项链表
// 思路:当不考虑编号顺序时
// 1.找到当前链表对的最后节点
// 2.将最后这个节点的next指向新的节点
public void add(HeroNode heroNode){
    // head节点不能动,我们需要用head结点来遍历链表
    HeroNode temp = head;
    // 遍历链表,找到最后一个结点
    while(true){
        if(temp.next == null){
            break;
        }
        temp = temp.next;
    }
    // 当退出while循环时候,temp就指向最后一个节点
    temp.next = heroNode;
}

3.添加英雄结点升级版(添加到指定位置)

public void addByOrder(HeroNode heroNode){
    // head节点不能动,我们需要用head结点来遍历链表
    HeroNode temp = head;
    while(true){
        if(temp.next == null){
            break;
        }
        if(temp.next.GetNo() == heroNode.GetNo()){  // heroNode.GetNo() 英雄数据编号
            throw new RuntimeException("传入英雄数据编号有误!");
        }
        // 找到指定位置 进行添加结点
        if(temp.next.GetNo() > heroNode.GetNo()){
            heroNode.next = temp.next;
            temp.next = heroNode;
            return;
        }
        temp = temp.next;
    }
    // 当退出while循环时候,temp就指向最后一个节点
    heroNode.next = temp.next;
    temp.next = heroNode;
}

4.更新英雄结点(根据英雄数据编号进行更新)

public void update(HeroNode heroNode){
    // 判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空~~");
        return;
    }
    //找到需要修给的结点,根据编号修改该英雄结点数据
    HeroNode temp = head;
    while(true) {
        if (temp.next == null) {
            System.out.println("没有该编号的人物,无法更新");
            return;
        }
        if (temp.next.GetNo() == heroNode.GetNo()) {
            heroNode.next = temp.next.next;
            temp.next = heroNode;
            return;
        }
        temp = temp.next;
    }
}

5.删除英雄结点(根据英雄数据编号进行删除)

public void delete(HeroNode heroNode){
    // 判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空~~");
        return;
    }
    //找到需要修给的节点,根据编号修删除该英雄结点数据
    HeroNode temp = head;
    while (true){
        if(temp.next == null){
            System.out.println("没有该人物英雄~~");
            return;
        }
        if(temp.next.GetNo() == heroNode.GetNo()){
            if(temp.next.GetName() == heroNode.GetName() && temp.next.GetNickname() == heroNode.GetNickname()){
                temp.next = temp.next.next;
                return;
            }else{
                System.out.println("删除的该人物英雄对象不匹配~~");
                return;
            }
        }
        temp = temp.next;
    }
}

6.显示全部链表数据

public void list(){
    // 判断链表是否为空
    if(head.next == null){
        System.out.println("链表为空!");
        return;
    }
    // 因为头节点不能动,因此需要一个辅助变量
    HeroNode temp = head;
    while (true){
        if(temp.next == null){
            break;
        }
        temp = temp.next;
        System.out.println("链表数据:"+temp.toString());
    }
}

面试题补充进该任务

1.查找单链表中倒数第K个节点[新浪面试题]

public static HeroNode bottomNode (HeroNode heroNode,int k){
    if(heroCount(heroNode) < k){
        return null;
    }
    int count = heroCount(heroNode) - k;
    HeroNode temp = head.next;
    while(count != 0){
        count--;
        temp = temp.next;
    }
    return temp;
}

2.单链表的反转[腾讯面试题]

/**
  * 单链表的反转[腾讯面试题]
  * 思路:
  * 1.先定义一个节点reverseHead = new HeroNode();
  * 2.从头到尾遍历原来的链表,每遍历一个节点就将其取出,并放在新的链表reverseHead的最前端
  * 3.原来的链表的head.next = reverseHead.next
  */
// MyCode
public static HeroNode ReverseHero(HeroNode heroNode){
    HeroNode reverseHead = new HeroNode(0,"","");
    if(heroNode.next == null) return heroNode;
    // 记录heroNode.next
    HeroNode next= heroNode.next;
    // 记录reverseHead.next
    HeroNode reverseNext= reverseHead.next;
    // 记录heroNode.next.next
    HeroNode nextNext = heroNode.next.next;
    while(next != null){
        if(nextNext != null){
            heroNode.next.next = reverseNext;
            reverseHead.next = next;
            heroNode.next = nextNext;
            nextNext = nextNext.next;
        }else{
            heroNode.next.next = reverseNext;
            reverseHead.next = next;
            heroNode.next = nextNext;
        }
        next = heroNode.next;
        reverseNext = reverseHead.next;
    }
    heroNode.next = reverseHead.next;
    return heroNode;
}

// Teacher's Code
public static HeroNode ReverseHero2(HeroNode heroNode){
    if(heroNode.next == null) return heroNode;
    HeroNode reverseHead = new HeroNode(0,"","");
    HeroNode next= heroNode.next; // 记录heroNode.next
    HeroNode nextNext = heroNode.next; // 记录heroNode.next.next

    while(next != null){
        nextNext = nextNext.next;
        next.next = reverseHead.next;
        reverseHead.next = next;
        next = nextNext;
    }
    heroNode.next = reverseHead.next;
    return heroNode;
}

双向链表

概述

对于一个结点,有单链表一样有存储数据的data,指向后方的 next 它拥有单链表的所有操作和内容。但是还有一个前驱节点 pre

算法与数据结构-链表_第3张图片

单链表 Vs 双向链表

  • 单链表,查找的方向只能是一个,而双向链表可以向前或者向后查找,灵活性大

  • 单向链表不能自我删除,需要靠辅助节点,而双向链表则可以自我删除

代码示例(修改自单向链表)

public class DoubleLinkedList {
    public static void main(String[] args) {
        HeroNoded[] heroList= new  HeroNoded[5];
        // 进行测试
        heroList[0] = new HeroNoded(1, "宋江", "及时雨");
        heroList[1] = new HeroNoded(2, "卢俊义", "玉麒麟");
        heroList[2] = new HeroNoded(3, "吴用", "智多星");
        heroList[3] = new HeroNoded(4, "李逵", "黑旋风");
        heroList[4] = new HeroNoded(5, "林冲", "豹子头");

        // 添加英雄结点数据
        DoubleLinkedListDemo demo  = new DoubleLinkedListDemo();

        for (int i = 0; i < heroList.length; i++) {
            demo.addHero(heroList[i]);
        }

        // 更新英雄结点数据
        demo.update(new HeroNoded(5, "鲁智深", "花和尚"));
        DoubleLinkedListDemo.list();
        System.out.println();
        System.out.println();

        // 删除节点
        demo.delete(new HeroNoded(5, "鲁智深", "花和尚"));
        DoubleLinkedListDemo.list();
    }
}


/**
 * DoubleLinkedList 管理我们的英雄
 */
class DoubleLinkedListDemo{
    private static HeroNoded head = new HeroNoded(0,"","");

    private boolean IsEmptyList(){
        return head.next == null;
    }

    // 增加英雄 (根据no排序)
    public void addHero(HeroNoded heroNoded){
        if(heroNoded == null){
            System.out.println("未传入英雄!!!");
            return;
        }
        if(IsEmptyList()){
            head.next = heroNoded;
            heroNoded.pre = head;
            return;
        }
        HeroNoded temp = head.next;
        while(true){
            if(temp.next == null) break;
            if(temp.GetNo() == heroNoded.GetNo()){
                throw new RuntimeException("传入英雄数据编号有误!");
            }
            // 加入的英雄结点的no 小于 双向链表中的某一个结点的no
            if(heroNoded.GetNo() < temp.GetNo()){
                temp.pre.next = heroNoded;
                heroNoded.next = temp;
                heroNoded.pre = temp.pre;
                temp.pre = heroNoded;
                return;
            }
            temp = temp.next;
        }
        // 当退出while循环时候,temp就指向最后一个节点
        temp.next = heroNoded;
        heroNoded.pre = temp;
    }

    // 更新功能:修改节点的信息即根据no编号来修改
    public void update(HeroNoded heroNoded){
        // 判断链表是否为空
        if(IsEmptyList()){
            System.out.println("链表为空~~");
            return;
        }
        //找到需要修给的节点,根据编号修改
        HeroNoded temp = head.next;
        while(true) {
            if (temp == null) {
                System.out.println("没有该编号的人物,无法更新");
                return;
            }
            if (temp.GetNo() == heroNoded.GetNo()) {
                if(temp.next != null){
                    heroNoded.next = temp.next;
                    temp.next.pre = heroNoded;
                }
                heroNoded.pre = temp.pre;
                temp.pre.next = heroNoded;
                return;
            }
            temp = temp.next;
        }
    }

    // 删除功能:从双向链表中删除一个节点的思路
    public void delete(HeroNoded heroNoded){
        // 判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空~~");
            return;
        }
        //找到需要修给的节点,根据编号修改
        HeroNoded temp = head.next;
        while (true){
            if(temp == null){
                System.out.println("没有该人物英雄~~");
                return;
            }
            if(temp.GetNo() == heroNoded.GetNo()){
                if(temp.GetName().equals(heroNoded.GetName()) && temp.GetNickname().equals(heroNoded.GetNickname())){
                    temp.pre.next = temp.next;
                    if(temp.next != null){
                        temp.next.pre = temp.pre;
                    }
                    return;
                }else{
                    System.out.println("删除的该人物英雄对象不匹配~~");
                    return;
                }
            }
            temp = temp.next;
        }
    }

    // 显示链表
    public static void list(){
        // 判断链表是否为空
        if(head.next == null){
            System.out.println("链表为空!");
            return;
        }
        // 因为头节点不能动,因此需要一个辅助变量
        HeroNoded temp = head;
        while (true){
            if(temp.next == null){
                break;
            }
            temp = temp.next;
            System.out.println("链表数据:"+temp.toString());
        }
    }
}

/**
 * 定义HeroNode,每个HeroNode对象就是一个节点
 */
class HeroNoded{
    private int no;
    private  String name;
    private  String nickname;
    public  HeroNoded next; // 指向下一个节点
    public HeroNoded pre; //指向上一个结点

    // 无参/有参构造函数
    public  HeroNoded(){}
    public HeroNoded(int no,String name,String nickname){
        this.name = name;
        this.no = no;
        this.nickname = nickname;
    }

    // 获得编号的方法
    public int GetNo(){
        return no;
    }

    // 获得姓名的方法
    public String GetName(){
        return name;
    }

    // 获得外号的方法
    public String GetNickname(){
        return nickname;
    }

    // 显示方便,重写toString()方法
    @Override
    public  String toString(){
        return "HeroNode[no="+no+",name="+name+",nickname"+nickname+"]";
    }
}

练习

430. 扁平化多级双向链表

你会得到一个双链表,其中包含的节点有一个下一个指针、一个前一个指针和一个额外的 子指针 。这个子指针可能指向一个单独的双向链表,也包含这些特殊的节点。这些子列表可以有一个或多个自己的子列表,以此类推,以生成如下面的示例所示的 多层数据结构

给定链表的头节点 head ,将链表 扁平化 ,以便所有节点都出现在单层双链表中。让 curr 是一个带有子列表的节点。子列表中的节点应该出现在扁平化列表中的 curr 之后curr.next 之前

返回 扁平列表的 head 。列表中的节点必须将其 所有 子指针设置为 null

示例 1:

算法与数据结构-链表_第4张图片

输入:head = [1,2,3,4,5,6,null,null,null,7,8,9,10,null,null,11,12]
输出:[1,2,3,7,8,11,12,9,10,4,5,6]
解释:输入的多级列表如上图所示。
扁平化后的链表如下图:

示例 2:

算法与数据结构-链表_第5张图片

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:输入的多级列表如上图所示。
扁平化后的链表如下图:

示例 3:

输入:head = []
输出:[]
说明:输入中可能存在空列表。

提示:

  • 节点数目不超过 1000
  • 1 <= Node.val <= 105

栈遍历

/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/

class Solution {
    public Node flatten(Node head) {
        if(head == null || (head.next == null && head.child == null)) return head;
        else{
            Stack<Node> st = new Stack<>();
            Node node = head;
            while(node.next != null || node.child != null || !st.empty()){
                if(node.child == null && node.next == null && !st.empty()){
                    Node temp = st.pop();
                    temp.prev = node;
                    node.next = temp;
                }else if(node.child == null){
                    node = node.next;
                }else{
                    if(node.next != null){
                        st.push(node.next);
                    }
                    Node temp = node.child;
                    node.next = temp;
                    temp.prev = node;
                    node.child = null;
                }
            }
            return head;
        }
    }
}

执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:39.6 MB, 在所有 Java 提交中击败了29.70%的用户

环形链表

概述

如果把单链表的最后一个节点的指针指向链表头部,而不是指向NULL,那么就构成了一个单向循环链表,通俗讲就是让尾节点指向头结点。

算法与数据结构-链表_第6张图片

单向环形链表应用场景:Josephu(约瑟夫、约瑟夫环)问题

代码实现

算法与数据结构-链表_第7张图片

构建一个单向的环形链表思路
1.先创建第一个节点,让first指向该节点,并形成环形
2.后面当我们每创建一个新的节点,就把该节点,加入到已有的环形链表中即可
遍历环形链表
1.先让一个辅助指针(变量)temp,指向firt节点
2.然后通过一个while循环遍历该环形链表即可temp…next=first结束

package linkedList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class CircleSingleLinkedList {
    public static void main(String[] args) {
        final int nums = 25;
        CircleSingleLinkedListDemo circle = new CircleSingleLinkedListDemo();
        circle.addNode(nums);
        circle.showNode();
        System.out.println(circle.orderNode(1,2,nums));;

    }
}

class CircleSingleLinkedListDemo{
    private CircleNode firstNode;

    // 添加节点
    public void addNode(int nums){
        // 校验nums值
        String str = "^[1-9][0-9]*$";
        Pattern pattern = Pattern.compile(str);
        Matcher matcher = pattern.matcher(nums + "");
        if(!matcher.find()){
            System.out.println("输入num不正确!");
        }

        // 辅助节点
        CircleNode temp = null;
        // 创建nums个节点
        for (int i = 1; i <= nums; i++) {
            CircleNode circleNode = new CircleNode(i);
            if(i == 1){
                firstNode = circleNode;
                temp = firstNode;
                temp.next = firstNode;
            }
            else{
                temp.next = circleNode;
                temp = temp.next;
                temp.next = firstNode;
            }
        }
    }

    // 根据用户的输入计算节点出圈的顺序
    // startNo:开始数数的节点,countNums:数的次数 nums:一共有多少小孩在圈中
    public String orderNode(int startNo, int countNums,int nums){
        // 校验nums值
       if(firstNode == null || startNo < 1 || startNo > nums){
           System.out.println("输出数据有误!");
           return "";
       }else{
           String str = "";
           // temp指向开始节点的前一个节点,firstNode指向开始节点
           while(firstNode.next.getNo() != startNo){
               firstNode = firstNode.next;
           }
           CircleNode temp = firstNode;
           firstNode = firstNode.next;

           while(nums-- > 0){
               int step = countNums;
               while(step > 1){
                   firstNode = firstNode.next;
                   temp = temp.next;
                   step--;
               }
               str += firstNode.getNo() + "  ";
               firstNode = firstNode.next;
               temp.next = firstNode;
           }
           return str;
       }
    }

    // 显示节点
    public void showNode(){
        // 辅助节点
        CircleNode temp = firstNode;

        if(temp == null){
            System.out.println("环形链表为空");
        }

        while(temp.next != firstNode){
            System.out.println(temp);
            temp = temp.next;
        }
        System.out.println(temp);
    }
}

class CircleNode{
    private int no;
    public CircleNode next;

    public CircleNode(int no) {
        this.no = no;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public CircleNode getNext() {
        return next;
    }

    public void setNext(CircleNode next) {
        this.next = next;
    }

    @Override
    public String toString() {
        return "CircleNode{" +
                "no=" + no +
                '}';
    }
}

练习

141. 环形链表

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

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

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

示例 1:

算法与数据结构-链表_第8张图片

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

示例 2:

算法与数据结构-链表_第9张图片

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

示例 3:

img

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

提示:

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

**进阶:**你能用 O(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) {
        HashSet<ListNode> set = new HashSet<>();
        while(head != null){
            if(set.contains(head)) return true;
            set.add(head);
            head = head.next;
        }
        return false;
    }
}

快慢指针

/**
 * 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 fast = head,slow = head;
        while(slow != null){
            slow = slow.next;
            if(fast.next != null){
                fast = fast.next.next;
            }else{
                fast = fast.next;
            }
            if(slow == null || fast == null) return false;
            if(slow == fast) return true;
        }
        return false;
    }
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:42.3 MB, 在所有 Java 提交中击败了64.21%的用户
142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

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

不允许修改 链表。

示例 1:

算法与数据结构-链表_第10张图片

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

示例 2:

算法与数据结构-链表_第11张图片

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

示例 3:

img

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

提示:

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

**进阶:**你是否可以使用 O(1) 空间解决此题?

哈希表

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head == null) return null;
        Set<ListNode> set = new HashSet<>();
        while(head != null){
            if(set.contains(head)){
                return head;
            }else{
                set.add(head);
            }
            head = head.next;
        }
        return null;
    }
}

快慢指针

分析图取自官方(a:未入环,紫色点是相遇点)

算法与数据结构-链表_第12张图片

数学分析原理

  1. 快慢指针的速度我们设置为快指针是慢指针的两倍

  2. 数学推导

    // 第一次相遇,在慢指针第一圈移动时,不管快指针在哪里,快指针总能追上慢指针
    // 假设一圈为a+b,快指针和慢指针刚入环相差x,快指针每一步都可以多靠近慢指针一个点,所以一定会在a+b-x之前遇到,因为a+b-x
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (fast == slow) {
                ListNode ptr = head;
                while (ptr != slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }
}
执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
内存消耗:42 MB, 在所有 Java 提交中击败了20.90%的用户

你可能感兴趣的:(算法与数据结构,链表,数据结构,算法)