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. 移除链表元素
简单
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 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. 设计链表
中等
你可以选择使用单链表或者双链表,设计并实现自己的链表。
单链表中的节点应该具备两个属性: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
get
、addAtHead
、addAtTail
、addAtIndex
和 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. 反转链表
已解答
简单
相关标签
相关企业
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]
示例 2:
输入: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. 两两交换链表中的节点
中等
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入: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 个结点
中等
提示
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入: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指针,初始值为虚拟头结点,如图:
fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
fast和slow同时移动,直到fast指向末尾,如题:
删除slow指向的下一个节点,如图:
有点纠结在是fast为空还是fast.next为空的终止条件,看了图解才真正理解
面试题 02.07. 链表相交
简单
提示
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入: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:
输入: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:
输入: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
已解答
中等
相关标签
相关企业
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入: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指针。
会发现最终都是这种情况, 如下图:
fast和slow各自再走一步, fast和slow就相遇了
这是因为fast是走两步,slow是走一步,其实相对于slow来说,fast是一个节点一个节点的靠近slow的,所以fast一定可以和slow重合。
动画如下:
此时已经可以判断链表是否有环了,那么接下来要找这个环的入口了。
假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:
那么相遇时: 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;
}
}