LeetCode | 链表

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值