代码随想录 链表

Java中并没有定义ListNode这个节点类,包括单向链表,双向链表,循环链表,都需要去学习它的写法,自定义

在处理链表的题目时,要注意通过画图的方法来摸清楚各个节点的应用和处理模式

在Java中自定义一个节点类(ListNode)

public class ListNode {
    int val;
    ListNode next;

    ListNode() {}

    ListNode(int val) { 
        this.val = val; 
    }

    ListNode(int val, ListNode next) { 
        this.val = val;
        this.next = next; 
    }
}

203.移除链表元素

203. 移除链表元素

简单

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例 1:

代码随想录 链表_第1张图片

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]

提示:

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

方法一:不添加哨兵节点,使用原链表

对头结点和头结点之后的节点的处理方式不同

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }
        // 处理头节点值等于val的情况,直到找到一个节点值不为val的节点当做头结点
        //因为要调用head.next,必须保证head不为空
        while (head != null && head.val == val) {
            head = head.next;
        }

        // 遍历链表,移除节点
        //因为要调用cur.next和cur.next.next,必须保证cur和cur.next不为空
        ListNode cur = head;
        while (cur != null && cur.next != null) {
            if (cur.next.val == val) {
                cur.next = cur.next.next; // 跳过当前节点的下一个节点
            } else {
                cur = cur.next; // 继续遍历下一个节点
            }
            //上述代码的目的是更新cur.next的值
        }

        return head; // 返回移除节点后的链表头节点
    }
}

方法二:添加哨兵节点作为头结点

在处理头节点时,引入了一个虚拟节点(dummy),它的值设置为Integer.MIN_VALUE,并且将原来的头节点作为虚拟节点的下一个节点。这样可以避免对头节点的特殊处理

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        // 处理空链表的情况
        if (head == null) {
            return null;
        }

        // 使用虚拟节点作为头节点,简化对头节点的操作
        ListNode dummy = new ListNode(Integer.MIN_VALUE, head);
        ListNode cur = dummy;

        // 遍历链表,移除节点
        while (cur != null && cur.next != null) {
            if (cur.next.val == val) {
                cur.next = cur.next.next; // 跳过当前节点的下一个节点
            } else {
                cur = cur.next; // 继续遍历下一个节点
            }
        }

        return dummy.next; // 返回移除节点后的链表头节点
    }
}

707.设计链表

707. 设计链表

中等

你可以选择使用单链表或者双链表,设计并实现自己的链表。

单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。

如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点。假设链表中的所有节点下标从 0 开始。

实现 MyLinkedList 类:

  • MyLinkedList() 初始化 MyLinkedList 对象。
  • int get(int index) 获取链表中下标为 index 的节点的值。如果下标无效,则返回 -1 。
  • void addAtHead(int val) 将一个值为 val 的节点插入到链表中第一个元素之前。在插入完成后,新节点会成为链表的第一个节点。
  • void addAtTail(int val) 将一个值为 val 的节点追加到链表中作为链表的最后一个元素。
  • void addAtIndex(int index, int val) 将一个值为 val 的节点插入到链表中下标为 index 的节点之前。如果 index 等于链表的长度,那么该节点会被追加到链表的末尾。如果 index 比长度更大,该节点将 不会插入 到链表中。
  • void deleteAtIndex(int index) 如果下标有效,则删除链表中下标为 index 的节点。

示例:

输入
["MyLinkedList", "addAtHead", "addAtTail", "addAtIndex", "get", "deleteAtIndex", "get"]
[[], [1], [3], [1, 2], [1], [1], [1]]
输出
[null, null, null, null, 2, null, 3]

解释
MyLinkedList myLinkedList = new MyLinkedList();
myLinkedList.addAtHead(1);
myLinkedList.addAtTail(3);
myLinkedList.addAtIndex(1, 2);    // 链表变为 1->2->3
myLinkedList.get(1);              // 返回 2
myLinkedList.deleteAtIndex(1);    // 现在,链表变为 1->3
myLinkedList.get(1);              // 返回 3

提示:

  • 0 <= index, val <= 1000
  • 请不要使用内置的 LinkedList 库。
  • 调用 getaddAtHeadaddAtTailaddAtIndex 和 deleteAtIndex 的次数不超过 2000 。

单链表

//单链表
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}
class MyLinkedList {
    //size存储链表元素的个数
    int size;
    //虚拟头结点
    ListNode head;

    //初始化链表
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }

    //获取第index个节点的数值
    public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode currentNode = head;
        //包含一个虚拟头节点,所以查找第 index+1 个节点
        for (int i = 0; i <= index; i++) {
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }

    //在链表最前面插入一个节点
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    //在链表的最后插入一个节点
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        //找到要插入节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }

    //删除第index个节点
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

双链表

//双链表类
class MyLinkedList {
    //内部节点类
    class ListNode {
        int val; //节点值
        ListNode next,prev; //指向前后节点的指针
        ListNode(int x) {val = x;} //构造函数,初始化节点值
    }

    int size; //链表长度
    ListNode head,tail; //头尾哨兵节点

    //构造函数,初始化链表
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0); //头节点值为0
        tail = new ListNode(0); //尾节点值为0
        head.next = tail; //头节点指向尾节点
        tail.prev = head; //尾节点指向头节点
    }
    
    //获取指定索引位置的值
    public int get(int index) {
        if(index < 0 || index >= size){return -1;} //如果索引越界,返回-1
        ListNode cur = head; //从头节点开始遍历

        // 通过判断 index < (size - 1) / 2 来决定是从头结点还是尾节点遍历,提高效率
        if(index < (size - 1) / 2){
            for(int i = 0; i <= index; i++){
                cur = cur.next; //向前遍历
            }            
        }else{
            cur = tail; //从尾节点开始遍历
            for(int i = 0; i <= size - index - 1; i++){
                cur = cur.prev; //向后遍历
            }
        }
        return cur.val; //返回当前节点的值
    }
    
    //在头部添加元素
    public void addAtHead(int val) {
        ListNode cur = head; //从头节点开始遍历
        ListNode newNode = new ListNode(val); //创建新节点
        newNode.next = cur.next; //新节点指向原头节点的下一个节点
        cur.next.prev = newNode; //原头节点的下一个节点指向新节点
        cur.next = newNode; //头节点指向新节点
        newNode.prev = cur; //新节点指向头节点
        size++; //链表长度加1
    }
    
    //在尾部添加元素
    public void addAtTail(int val) {
        ListNode cur = tail; //从尾节点开始遍历
        ListNode newNode = new ListNode(val); //创建新节点
        newNode.next = tail; //新节点指向尾节点
        newNode.prev = cur.prev; //新节点的前一个节点指向原尾节点的前一个节点
        cur.prev.next = newNode; //原尾节点的前一个节点指向新节点
        cur.prev = newNode; //尾节点指向新节点
        size++; //链表长度加1
    }
    
    //在指定索引位置插入元素
    public void addAtIndex(int index, int val) {
        if(index > size){return;} //如果索引大于链表长度,直接返回
        if(index < 0){index = 0;} //如果索引小于0,将索引设置为0
        ListNode cur = head; //从头节点开始遍历
        for(int i = 0; i < index; i++){
            cur = cur.next; //向前遍历
        }
        ListNode newNode = new ListNode(val); //创建新节点
        newNode.next = cur.next; //新节点指向原索引位置的节点的下一个节点
        cur.next.prev = newNode; //原索引位置的节点的下一个节点指向新节点
        cur.next = newNode; //原索引位置的节点指向新节点
        newNode.prev = cur; //新节点指向原索引位置的节点
        size++; //链表长度加1
    }
    
    //删除指定索引位置的元素
    public void deleteAtIndex(int index) {
        if(index >= size || index < 0){return;} //如果索引越界,直接返回
        ListNode cur = head; //从头节点开始遍历
        for(int i = 0; i < index; i++){
            cur = cur.next; //向前遍历
        }
        cur.next.next.prev = cur; //删除节点后,更新前后节点的指针关系
        cur.next = cur.next.next; //删除节点后,更新当前节点的下一个节点指针
        size--; //链表长度减1
    }
}

206.反转链表

206. 反转链表

已解答

简单

相关标签

相关企业

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

代码随想录 链表_第2张图片

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

示例 2:

代码随想录 链表_第3张图片

输入:head = [1,2]
输出:[2,1]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000

进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?

双指针法
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    /**
     * 反转单链表的方法
     * @param head 给定链表的头节点
     * @return 反转后的链表头节点
     */
    public ListNode reverseList(ListNode head) {
        ListNode cur = head;   // 当前节点
        ListNode pre = null;   // 前一个节点

        // 遍历链表
        while (cur != null) {
            ListNode temp = cur.next;  // 暂存下一个节点的引用
            cur.next = pre;            // 当前节点的下一个节点指向前一个节点,实现反转
            pre = cur;                 // 更新前一个节点为当前节点
            cur = temp;                // 更新当前节点为下一个节点
        }

        return pre;  // 反转后的链表头节点
    }
}
递归法

原理和双指针法相同

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    /**
     * 反转单链表的方法(入口方法)
     * @param head 给定链表的头节点
     * @return 反转后的链表头节点
     */
    public ListNode reverseList(ListNode head) {
        // 调用递归函数,初始时前一个节点为null
        return reverse(head, null);
    }
    
    /**
     * 递归函数:反转单链表
     * @param cur 当前节点
     * @param pre 前一个节点
     * @return 反转后的链表头节点
     */
    public ListNode reverse(ListNode cur, ListNode pre) {
        // 递归终止条件:当前节点为空,返回前一个节点作为新的头节点
        if (cur == null) {
            return pre;
        }
        // 暂存当前节点的下一个节点的引用
        ListNode temp = cur.next;
        // 反转当前节点的next指针,指向前一个节点
        cur.next = pre;
        // 更新前一个节点为当前节点
        pre = cur;
        // 更新当前节点为下一个节点
        cur = temp;
        // 递归调用,继续反转下一个节点
        return reverse(cur, pre);
    }
}

24.两两交换链表中的节点

24. 两两交换链表中的节点

中等

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

代码随想录 链表_第4张图片

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100] 内
  • 0 <= Node.val <= 100
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    /**
     * 交换相邻节点的方法
     * @param head 给定链表的头节点
     * @return 交换相邻节点后的链表头节点
     */
    public ListNode swapPairs(ListNode head) {
        // 创建一个虚拟节点作为头节点的前一个节点
        ListNode dummyNode = new ListNode(0, head);
        // 当前节点指向虚拟节点
        ListNode cur = dummyNode;

        // 遍历链表,交换相邻节点
        while (cur.next != null && cur.next.next != null) {
            // 暂存当前相邻节点的前一个节点和后一个节点
            ListNode temp1 = cur.next;
            ListNode temp2 = cur.next.next.next;
            
            // 交换相邻节点
            cur.next = cur.next.next;
            cur.next.next = temp1;
            cur.next.next.next = temp2;
            
            // 移动到下一组相邻节点的前一个节点
            cur = cur.next.next;
        }

        return dummyNode.next; // 返回交换相邻节点后的链表头节点
    }
}

19.删除链表的倒数第N个节点

19. 删除链表的倒数第 N 个结点

中等

提示

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

代码随想录 链表_第5张图片

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

进阶:你能尝试使用一趟扫描实现吗?

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummyHead = new ListNode(0,head);
        ListNode slow = dummyHead;  //快慢指针均指向虚拟头节点
        ListNode fast = dummyHead;
        while(n >= 0){
            n--;
            fast = fast.next;       //将快慢指针之间的间距变为n+1
        }
        while(fast != null){        //使快指针指向最后一个节点的next(null)
            fast = fast.next;       //此刻慢指针指向要删除的节点的前一个节点
            slow = slow.next;
        }
        slow.next = slow.next.next; //执行删除操作
        return dummyHead.next;      //返回头结点
    }
}

这道题的思路大致如注释,这里需要关注的是快慢节点之间的间距,以及快指针最后指向的位置,此处用作图的方式能更好的理解

  • 定义fast指针和slow指针,初始值为虚拟头结点,如图:

代码随想录 链表_第6张图片

  • fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图: 

    代码随想录 链表_第7张图片

  • fast和slow同时移动,直到fast指向末尾,如题: 

    代码随想录 链表_第8张图片

  • 删除slow指向的下一个节点,如图: 

    代码随想录 链表_第9张图片

有点纠结在是fast为空还是fast.next为空的终止条件,看了图解才真正理解 

面试题02.07.链表相交

面试题 02.07. 链表相交

简单

提示

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

图示两个链表在节点 c1 开始相交

代码随想录 链表_第10张图片

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

示例 1:

代码随想录 链表_第11张图片

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

代码随想录 链表_第12张图片

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Intersected at '2'
解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。
在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

代码随想录 链表_第13张图片

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。
由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
这两个链表不相交,因此返回 null 。

提示:

  • listA 中节点数目为 m
  • listB 中节点数目为 n
  • 0 <= m, n <= 3 * 104
  • 1 <= Node.val <= 105
  • 0 <= skipA <= m
  • 0 <= skipB <= n
  • 如果 listA 和 listB 没有交点,intersectVal 为 0
  • 如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]

进阶:你能否设计一个时间复杂度 O(n) 、仅用 O(1) 内存的解决方案?

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    // 获取两个链表的交点
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 创建两个指针,分别指向两个链表的头节点
        ListNode curA = headA;
        ListNode curB = headB;
        // 计算两个链表的长度
        int lengthA = 0;
        int lengthB = 0;
        while(curA != null){
            lengthA++;
            curA = curA.next;
        }
        while(curB != null){
            lengthB++;
            curB = curB.next;
        }
        // 创建两个指针,分别指向两个链表的头节点
        ListNode longer = headA;
        ListNode shorter = headB;
        // 计算两个链表的长度差值
        int distance = lengthA - lengthB;
        // 如果链表B比链表A长,则将longer指向链表B的头节点,shorter指向链表A的头节点,distance为长度差值
        if(lengthB > lengthA){
            longer = headB;
            shorter = headA;
            distance = lengthB - lengthA;
        }
        // 将longer指针向前移动distance步
        while (distance > 0){
            longer = longer.next;
            distance--;
        }
        // 同时遍历两个链表,直到找到交点或者遍历结束
        while(longer != null && shorter != null){
            // 如果找到交点,则返回交点
            if(longer == shorter){
                return longer;
            }
            // 否则,将longer和shorter指针向前移动一步
            longer = longer.next;
            shorter = shorter.next;
        }
        // 如果没有找到交点,则返回null
        return null;
    }
}

简单的思路描述:找出两个链表中长度更长的一条,由于两条链表相交后面的部分均是一样的,所以使长度较长的链表移动到和长度较短的链表的长度一样时,此时的两节点才可能相交。

142.环形链表II

142. 环形链表 II

已解答

中等

相关标签

相关企业

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

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

不允许修改 链表。

示例 1:

代码随想录 链表_第14张图片

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

示例 2:

代码随想录 链表_第15张图片

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

示例 3:

输入: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) {
        // 创建一个列表用于存储遍历过的节点
        List node = new ArrayList<>();
        // 初始化当前节点为头节点
        ListNode cur = head;
        // 遍历链表
        while(cur != null){
            // 如果当前节点已经在列表中,说明存在环,返回当前节点作为环的起始节点
            if(node.contains(cur)){
                return cur;
            }
            // 将当前节点添加到列表中
            node.add(cur);
            // 移动到下一个节点
            cur = cur.next;
        }
        // 如果遍历完链表都没有发现环,返回null
        return null;
    }
}

题解这里就照搬了 

可以使用快慢指针法,分别定义 fast 和 slow 指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。

为什么fast 走两个节点,slow走一个节点,有环的话,一定会在环内相遇呢,而不是永远的错开呢

首先第一点:fast指针一定先进入环中,如果fast指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。

那么来看一下,为什么fast指针和slow指针一定会相遇呢?

可以画一个环,然后让 fast指针在任意一个节点开始追赶slow指针。

会发现最终都是这种情况, 如下图:

代码随想录 链表_第16张图片

fast和slow各自再走一步, fast和slow就相遇了

这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。

动画如下:

#如果有环,如何找到这个环的入口

此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

代码随想录 链表_第17张图片

那么相遇时: slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为 一圈内节点的个数A。

因为fast指针是一步走两个节点,slow指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2:

(x + y) * 2 = x + y + n (y + z)

两边消掉一个(x+y): x + y = n (y + z)

因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。

所以要求x ,将x单独放在左面:x = n (y + z) - y ,

再从n(y+z)中提出一个 (y+z)来,整理公式之后为如下公式:x = (n - 1) (y + z) + z 注意这里n一定是大于等于1的,因为 fast指针至少要多走一圈才能相遇slow指针。

这个公式说明什么呢?

先拿n为1的情况来举例,意味着fast指针在环形里转了一圈之后,就遇到了 slow指针了。

当 n为1的时候,公式就化解为 x = z

这就意味着,从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点

也就是在相遇节点处,定义一个指针index1,在头结点处定一个指针index2。

让index1和index2同时移动,每次移动一个节点, 那么他们相遇的地方就是 环形入口的节点。

动画如下:

那么 n如果大于1是什么情况呢,就是fast指针在环形转n圈之后才遇到 slow指针。

其实这种情况和n为1的时候 效果是一样的,一样可以通过这个方法找到 环形的入口节点,只不过,index1 指针在环里 多转了(n-1)圈,然后再遇到index2,相遇点依然是环形的入口节点。

代码如下:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
            if (slow == fast) {// 有环
                ListNode index1 = fast;
                ListNode index2 = head;
                // 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
                while (index1 != index2) {
                    index1 = index1.next;
                    index2 = index2.next;
                }
                return index1;
            }
        }
        return null;
    }
}
  • 时间复杂度: O(n),快慢指针相遇前,指针走的次数小于链表长度,快慢指针相遇后,两个index指针走的次数也小于链表长度,总体为走的次数小于 2n
  • 空间复杂度: O(1)

你可能感兴趣的:(代码随想录,链表,算法)