LeetCode | 链表
题485. 最大连续1的个数
给定一个二进制数组, 计算其中最大连续1的个数
示例:
输入: [1,1,0,1,1,1]
输出: 3
解释: 开头的两位和最后的三位都是连续1,所以最大连续1的个数是 3.
思路:
Java:需要遍历数组,记录最大的连续 1 的个数和当前的连续 1 的个数。如果当前元素是 1,则将当前的连续 1 的个数加 1,否则,比较之前的连续 1 的个数和当前连续 1的个数,去大的保存,并将当前的连续 1 的个数清零。
class Solution {
public int findMaxConsecutiveOnes(int[] nums) {
int nbOneMax=0;
int nbOneCurrent=0;
for (int i=0;i<nums.length;i++){
if(nums[i]==1){
nbOneCurrent++;
}else{
if(nbOneCurrent>nbOneMax){
nbOneMax=nbOneCurrent;
}
//这一步可以换用nbOneMax = Math.max(nbOneMax, nbOneCurrent);
nbOneCurrent=0;
}
}
if(nbOneCurrent>nbOneMax){
nbOneMax=nbOneCurrent;
}
return nbOneMax;
}
}
时间:O(n)
空间:O(1)
2. 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.
思路1:我的解法
- 这里链表的head也储存值
- 两个地方要注意判断是否next节点为null:
1)头节点进入while循环之前,如果两个加数的next节点都为null,直接返回头节点;
2)最后一位,如果两个加数的next节点都为null,大于10进一小于10不再进0;
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode listHeadSum=new ListNode((l1.val+l2.val)%10);
if ((l1.val+l2.val)>=10){
listHeadSum.next=new ListNode(1);
}else{
if(l1.next==null && l2.next==null){
return listHeadSum;
}
listHeadSum.next=new ListNode(0);
}
ListNode listNodeSum=listHeadSum;
l1=l1.next;
l2=l2.next;
while(l1!=null || l2!=null){
if(l1!=null){
if(l2!=null){
if((l1.val+l2.val+listNodeSum.next.val)>=10){
listNodeSum.next.val=(l1.val+l2.val+listNodeSum.next.val)%10;
listNodeSum=listNodeSum.next;
listNodeSum.next=new ListNode(1);
l1=l1.next;
l2=l2.next;
}else{
listNodeSum.next.val=(l1.val+l2.val+listNodeSum.next.val)%10;
l1=l1.next;
l2=l2.next;
if(l1!=null || l2!=null){
listNodeSum=listNodeSum.next;
listNodeSum.next=new ListNode(0);
}
}
}else{
if((l1.val+listNodeSum.next.val)>=10){
listNodeSum.next.val=(l1.val+listNodeSum.next.val)%10;
listNodeSum=listNodeSum.next;
listNodeSum.next=new ListNode(1);
l1=l1.next;
}else{
listNodeSum.next.val=(l1.val+listNodeSum.next.val)%10;
l1=l1.next;
if(l1!=null){
listNodeSum=listNodeSum.next;
listNodeSum.next=new ListNode(0);
}
}
}
}else{
if((l2.val+listNodeSum.next.val)>=10){
listNodeSum.next.val=(l2.val+listNodeSum.next.val)%10;
listNodeSum=listNodeSum.next;
listNodeSum.next=new ListNode(1);
l2=l2.next;
}else{
listNodeSum.next.val=(l2.val+listNodeSum.next.val)%10;
l2=l2.next;
if(l2!=null){
listNodeSum=listNodeSum.next;
listNodeSum.next=new ListNode(0);
}
}
}
}
return listHeadSum;
}
}
时间:O(max(m,n)),其中 m,nm,n 为两个链表的长度。
空间:O(max(m,n))。答案链表的长度最多为较长链表的长度+1。
思路2: 官方解法
1)用head、tail来表示链表的头节点、尾节点来进行迭代
2)l1或l2为null时,用0替代
3)用取整运算来计算进位值,保存在一个int中,不要保存在tail.next里
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null;
ListNode tail = null;
int carry=0;
while(l1!=null || l2!=null){
int n1=l1!=null? l1.val :0;
int n2=l2!=null? l2.val :0;
int sum = n1+n2 +carry;
if(head==null){
head=new ListNode(sum%10);
tail=head;
}else{
tail.next=new ListNode(sum%10);
tail=tail.next;
}
carry=sum/10;
if (l1!=null){
l1=l1.next;
}
if (l2!=null){
l2=l2.next;
}
}
if(carry>0){
tail.next=new ListNode(carry);
tail=tail.next;
}
return head;
}
}
时间:O(max(m,n)),其中 m,nm,n 为两个链表的长度。
空间:O(max(m,n))
题19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
输入:head = [1], n = 1
输出:[]
输入:head = [1,2], n = 1
输出:[1]
思路:
要一次遍历,必须使用双指针法。
使用中要注意:
1)不要将p1 p2直接初始化为head,而将其next设为head,方便迭代计数,使得最后p2是倒数第n个节点的前一个节点,方便删除
2)当n=链表size时,p1走完n已经到了tail,此时p1.next==null,直接将head.next返回。
/**
* 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 p1=new ListNode();
ListNode p2=new ListNode();
p1.next=head;
p2.next=head;
int k=0;
while (k<n){
p1=p1.next;
k++;
}
if (p1.next==null){
head=head.next;
}
while(p1.next!=null){
p1=p1.next;
p2=p2.next;
}
p2.next=p2.next.next;
return head;
}
}
时间:O(n)
空间:O(1)
题21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
输入:l1 = [], l2 = []
输出:[]
输入:l1 = [], l2 = [0]
输出:[0]
思路一:迭代
当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。
注意几点:
1)因为要保留头节点变量,为了避免第一次迭代的繁琐讨论,使得能够将所有if讨论都塞进while里,通常将head定义为所讨论节点的前一个节点(也称为prehead),tial=head,然后进入while做讨论。结尾时返回head .next。
2)当l1或l2有一个为null时,所以哪个链表是非null的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。因此我们只需将非空链表接在合并链表的后面即可,不需要再一个一个节点讨论。while()只需要讨论l1、l2都不为null的情况。
3)当l1和l2都为null的情况被包含在l1、l2有一个为null的情况里。
/**
* 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 mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head=new ListNode();
ListNode tail=head;
while(l1!=null && l2!=null){
if(l1.val<l2.val){
tail.next=l1;
tail=tail.next;
l1=l1.next;
continue;
}else{
tail.next=l2;
tail=tail.next;
l2=l2.next;
continue;
}
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
tail.next = l1 == null ? l2 : l1;
return head.next;
}
}
时间复杂度(最差):O(n+m),其中 n 和 m 为两个链表的长度。每次循环迭代中,l1 和 l2 只有一个元素会被放进合并链表中, 因此 while 循环的次数不会超过两个链表的长度之和。所有其他操作的时间复杂度都是常数级别的。
空间复杂度:O(1)。我们只需要常数的空间存放若干变量。
思路二:递归
忽略边界情况(比如空链表等)下,我们可以递归地定义两个链表里的 merge 操作:
list1[0]+merge(list1[1:],list2) if list1[0]<list2[0]
list2[0]+merge(list1,list2[1:]) otherwise
也就是,两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。
考虑边界情况:
1)如果 l1 或者 l2 一开始就是空链表,直接返回空链表。
2)否则,判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地取出下一个添加到结果里的节点。
3)一旦两个链表有一个为空,递归结束。
/**
* 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 mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){
return l2;
}else if(l2==null){
return l1;
}else{
if(l1.val<l2.val){
l1.next=mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next=mergeTwoLists(l1,l2.next);
return l2;
}
}
}
}
时间复杂度(最差):O(n+m),其中 n 和 m 为两个链表的长度。每次调用递归都会去掉 l1 或者 l2 的头节点(直到至少有一个链表为空),函数 mergeTwoList 至多只会递归调用每个节点一次。
空间复杂度:O(n+m)。递归调用 mergeTwoLists 函数时需要消耗栈空间,所消耗的栈空间的大小取决于递归调用的深度。结束递归调用时 mergeTwoLists 函数最多调用 n+m 次。
题24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
输入:head = []
输出:[]
输入:head = [1]
输出:[1]
我的解法
快慢指针、每次都复制创建新节点,以此保留原链表都顺序关系,以空间消耗换时间
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode preHead=new ListNode();
ListNode tail=preHead;
ListNode p1=new ListNode();
ListNode p2=new ListNode();
p1.next=p2;
p2.next=head;
while(p2.next!=null){
if(p2.next.next!=null){
p1=p1.next.next;
p2=p2.next.next;
tail.next=new ListNode(p2.val);
tail=tail.next;
tail.next=new ListNode(p1.val);
tail=tail.next;
}else{
p1=p1.next.next;
p2=p2.next;
tail.next=new ListNode(p1.val);
tail=tail.next;
}
}
return preHead.next;
}
}
时间复杂度:O(n)。
空间复杂度:O(n)。
思路一:迭代
注意到,其实要保留原链表都顺序关系,需要复制节点,但不用创建新节点,只需要利用一个当前节点tail,每次交换tail后面都两个节点node1和node2
tail.next=node2;
node1.next=node2.next;
node2.next=node1;
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode preHead=new ListNode();
preHead.next=head;
ListNode tail=preHead;
while(tail.next!=null && tail.next.next!=null){
ListNode node1=tail.next;
ListNode node2=tail.next.next;
tail.next=node2;
node1.next=node2.next;
node2.next=node1;
tail=node1;
}
return preHead.next;
}
}
时间复杂度:O(n)。
空间复杂度:O(1)。
思路二:递归
当链表中至少有两个节点时,在两两交换链表中的节点之后,原始链表的头节点变成新的链表的第二个节点,原始链表的第二个节点变成新的链表的头节点。链表中的其余节点的两两交换可以递归地实现
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null || head.next==null){
return head;
}else{
ListNode newHead=head.next;
head.next=swapPairs(newHead.next);
newHead.next=head;
return newHead;
}
}
}
时间:O(n)
空间:O(n)
题61. 旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL
思路一:
先将链表闭合成环
找到相应的位置断开这个环,确定新的链表头和链表尾
class Solution {
public ListNode rotateRight(ListNode head, int k) {
ListNode temp;
ListNode tail=new ListNode();
tail.next=head;
int n=0;
if(head==null || head.next==null){
return head;
}
while(tail.next!=null){
tail=tail.next;
n++;
}
tail.next=head;
for (int i=0;i<(n-k%n);i++){
tail=tail.next;
}
head=tail.next;
tail.next=null;
return head;
}
}
时间复杂度:O(n)。
空间复杂度:O(1)。
题83. 删除排序链表中的重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例:
输入: 1->1->2
输出: 1->2
输入: 1->1->2->3->3
输出: 1->2->3
我的解法
用两个指针对应两个while去遍历,注意:
1)初始时p=q=head
2)第一个while的条件为p!=null
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode p =head;
while(p!=null){
ListNode q=p;
while(q.next!=null){
if(p.val==q.next.val){
q.next=q.next.next;
}else{
q=q.next;
}
}
p=p.next;
}
return head;
}
}
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
官方解法
要注意到链表已经排序好,实际上只需要一个while遍历即可。我的解法适用于未排序的情况。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
ListNode curNode=head;
while(curNode!=null && curNode.next!=null){
if(curNode.val==curNode.next.val){
curNode.next=curNode.next.next;
}else{
curNode=curNode.next;
}
}
return head;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
题82. 删除排序链表中的重复元素 II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
输入: 1->1->1->2->3
输出: 2->3
思路:递归
递归函数做的事:删除所有头部的重复节点,返回不重复的第一个节点。
以[1,1,1,1,2,3,3,4]为例:
- 递归函数deleteDuplicates先作用在[1,1,1,1,2,3,3,4]上,返回deleteDuplicates([2,3,3,4])
- deleteDuplicates([2,3,3,4])上返回2,并设2的next为deleteDuplicates([3,3,4])
- deleteDuplicates([3,3,4])返回deleteDuplicates([4])
- 特殊情况当headnull或head.nextnull,直接返回head
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if(head==null || head.next== null){
return head;
}
if(head.val==head.next.val){ //开头就遇到重复的
while(head!=null && head.next!=null && head.val==head.next.val){
head=head.next;
}
return deleteDuplicates(head.next);
}else{ //开头遇到不是重复的
head.next=deleteDuplicates(head.next);
return head;
}
}
}
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
题86. 分隔链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
输入:head = [2,1], x = 2
输出:[1,2]
思路:
声明两个子链表small和large,遍历原始链表,将<x的添加到small,>=x的添加到large,最后设large.next=null,合并small和large
class Solution {
public ListNode partition(ListNode head, int x) {
ListNode small=new ListNode();
ListNode smallHead=small;
ListNode large=new ListNode();
ListNode largeHead=large;
while(head!=null){
if(head.val<x){
small.next=head;
small=small.next;
}else{
large.next=head;
large=large.next;
}
head=head.next;
}
large.next=null;
small.next=largeHead.next;
return smallHead.next;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
题206. 反转链表
反转一个单链表。
进阶: 你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
思路一:迭代
假设链表为1→2,我们想要把它改成∅←1←2。
在遍历链表时,将当前节点1的next 指针改为指向前一个节点null。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点2。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode curNode=head;
ListNode preNode=null;
ListNode nextNode=head;
while (curNode!=null){
nextNode=curNode.next;
curNode.next=preNode;
preNode=curNode;
curNode=nextNode;
}
return preNode;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
思路二:递归
考虑链表的后面其余的部分已被反转,现在该如何反转它前面的部分?
reverseList( n k n_k nk)里应该实现 n k n_k nk.next.next= n k n_k nk同时做到:
- n k n_k nk.next=null
- return
n
m
n_m
nm
也就是说除了(head== null || head.next==null)情况,每次reverseList返回的都是 n m n_m nm
class Solution {
public ListNode reverseList(ListNode head) {
if(head==null || head.next==null){
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next=head;
head.next=null;
return newHead;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
题92.反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
思路一:迭代
1)用之前反转链表的方法反转m到n部分;
2)原始链表被m和n截成3段,让curNod先移动到m处,记录下第一段到尾部segment1Tail和第二段到头部segment2Head
3)最后注意判断segment1Tail是否为null(m=0),segment2Head为null没关系(反正链表最后也要添加一个null)
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
if(head==null || head.next==null){
return head;
}
ListNode curNode=head;
ListNode preNode=null;
ListNode nextNode=null;
for (int i=1;i<m;i++){
preNode=curNode;
curNode=curNode.next;
}
ListNode segment1Tail=preNode;
ListNode segment2Head=curNode;
for(int k=m;k<n+1;k++){
nextNode=curNode.next;
curNode.next=preNode;
preNode=curNode;
curNode=nextNode;
}
if(segment1Tail==null){
head=preNode;
}else{
segment1Tail.next=preNode;
}
segment2Head.next=curNode;
return head;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
题141. 环形链表
给定一个链表,判断链表中是否有环。
如果链表中存在环,则返回 true 。 否则,返回 false 。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
进阶: 你能用 O(1)(即,常量)内存解决此问题吗?
示例:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
思路一:用HashSet
遍历链表,将每个节点添加进哈希表,如果不能成功添加则表示存在重复节点
public class Solution {
public boolean hasCycle(ListNode head) {
HashSet<ListNode> nodeSet =new HashSet<ListNode>();
while(head!=null){
if(!nodeSet.add(head)){
return true;
}
head=head.next;
}
return false;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n),最坏情况下我们需要将每个节点插入到哈希表中一次,占用的空间内存为
O
(
n
)
O(n)
O(n)
思路二:龟兔赛跑法
原理
假想乌龟和兔子在链表上移动,兔子跑得快,乌龟跑得慢。
当乌龟和兔子从链表上的同一个节点开始移动时,如果该链表中没有环,
那么兔子将一直处于乌龟的前方;如果该链表中有环,那么兔子会先于乌龟进入环,并且一直在环内移动。
等到乌龟进入环时,由于兔子的速度快,它一定会在某个时刻与乌龟相遇,即套了乌龟若干圈。
具体地,定义两个指针,慢指针每次只移动一步, 快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。
注意:如果将两个指针初始都置于 head,那么 while 循环就不会执行。因此,假想一个在 head 之前的虚拟节点,慢指针从虚拟节点移动一步到达 head,快指针从虚拟节点移动两步到达 head.next,再进入while 循环。
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null || head.next==null){
return false;
}
ListNode slow=head;
ListNode fast=head.next;
while(slow!=fast){
if(fast==null || fast.next==null){
return false;
}
slow=slow.next;
fast=fast.next.next;
}
return true;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
题142. 环形链表 II:找到环的入口
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明: 不允许修改给定的链表。
进阶: 你是否可以使用 O(1) 空间解决此题?
示例:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
思路一:用HashSet
遍历链表中的每个节点,并将它记录到哈希表中国呢;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。
public class Solution {
public boolean hasCycle(ListNode head) {
HashSet<ListNode> nodeSet =new HashSet<ListNode>();
while(head!=null){
if(!nodeSet.add(head)){
return true;
}
head=head.next;
}
return false;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n),最坏情况下我们需要将每个节点插入到哈希表中一次,占用的空间内存为
O
(
n
)
O(n)
O(n)
思路二:快慢指针法
设两指针 fast,slow 指向链表头部 head,fast 每轮走 2步,slow 每轮走 1 步;
1.若无环: fast 指针走过链表末端,说明链表无环,直接返回 null;
2.若有环,设未进入环部分长度为a(不包含入口),环的长度为b
1)fast、slow 第一次在环内相遇:
- fast 走的步数是slow步数的 2 倍,即
f=2s
- fast 比 slow多走了 n个环的长度,即
f=s+nb
得到s=nb
2)走到环入口的步数:
k=a+nb
==> 可知slow只需要在走a步即可到达环入口
3)fast、slow 第二次相遇:
- slow位置不变 ,将fast重新指向链表头部节点 ;slow和fast同时每轮向前走 1 步;
- 当 fast 指针走到f=a 步时,slow 指针走到步s=a+nb,此时两指针同时指向链表环入口 。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(true){
if(fast==null || fast.next==null) return null;
fast=fast.next.next;
slow=slow.next;
if (fast==slow) break;
}
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)