文章目录
本文写于笔者的另一篇文章 算法学习-数组的相关操作中,那篇还没更完,这篇就开始挖坑了。在数组上删除重复项是一种重要的题型,同样,在链表上也会进行相关的操作。因此,本文就开始将链表的相关题目进行整理汇总,希望能够记录自己的刷题路径。
链表操作基础知识
本文参考Carl的代码随想录。
1. 虚拟头节点
在面对链表的创建、删除等操作的时候,需要灵活使用,即就在链表前插入一个不存值的节点,不影响取值但是可以简化很多指针操作。
ListNode dummyHead=new ListNode(0);
ListNode cur=dummyHead;
cur.next=head;
2. 链表从零开始的创建操作:
- 尾插法,从
dummyHead.next
开始往后不断增加,虽然按照后面中间插入中的循环插入写法也可以,但是这里直接插入,不用考虑后面的节点,可以简化很多代码
ListNode dummyHead=new ListNode(0);
ListNode cur=dummyHead;
cur.next=new ListNode(newVal);
cur=cur.next;
- 头插法,先定义
head=null
,newNode.next=head
插入,不断更新head
ListNode head=null;
ListNode newNode=new ListNode(newVal);
newNode.next=head;
head=node;
3. 链表的中间插入操作:
找到要插入节点的前一个节点pre
ListNode newNode=new ListNode(newVal);
newNode.next=pre.next;
pre.next=newNode;
如果是要在原链表中循环插入的话,需要记录插入位置的下一个位置
ListNode temp=pre.next; // 记录插入位置的下一个位置
ListNode newNode=new ListNode(newVal); //正常插入操作
newNode.next=pre.next;
pre.next=newNode;
pre=temp;
4. 链表的删除操作:
找到要删除节点的前一个节点pre
pre.next=pre.next.next;
5. 链表的指针改变
很多题目中,对链表结构的改变就只需要修改「原链表指针指向」
//反转链表伪代码
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
6. 针对while遍历
条件的编写,需要因题而异
while(cur!=null)... 最后停止在链表后,常出现在遍历全部链表、反转链表的题中
while(cur.next!=null)... 最后停止在链表最后一个节点,当面对删除操作时,这样子的定义会更有利于删除
while(fast!=null&&fast.next!=null)... 出现在去除重复值或者走两步的题中
while(fast.next!=null&&fast.next.next!=null)... 常出现在走两步找中心的题目中,奇偶情况下都可以slow.next找到后半部分
while(ptr->next != head)...循环链表的题目
6. 当前工作指针cur
初始化不一样,需要因题而异
ListNode cur=dummyHead;
或者
ListNode cur=head;
相关题目
21.删除链表的倒数第n个结点
快慢指针法,看到删除链表,先建立dummyhead; 快指针先提前走n步,然后快慢指针同步走,结束条件需要脑中模拟一下,如果fast和slow都初始化为原先的head,快指针走到null,slow刚好走到倒数第n个节点,因此再有个pre记录一下倒数第n个节点的前一个节点就可以了。
/**
* 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(-1);
dummyhead.next=head;
ListNode fast=head;
ListNode slow=head;
ListNode pre=dummyhead;
while(n-->0){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
pre=pre.next;
}
pre.next=slow.next;
return dummyhead.next;
}
}
876.链表的中间结点
参考题解,尤其注意while(fast!=null&&fast.next!=null)
的编写。遍历时,快指针走两步,慢指针走一步。
while(fast!=null&&fast.next!=null)
在偶数节点情况下找到的是后面的中间节点,while(fast.next!=null&&fast.next.next!=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 middleNode(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
}
143.重排链表
这题涉及到的知识点非常多,参考题解,总体思路是中间分割,后半部分反转链表,然后合并链表。
先中间分割链表,while(fast.next!=null&&fast.next.next!=null)
在偶数节点情况下找到的是前面的中间节点,奇数情况下是正中间节点,在偶数找到前面的中间的节点的情况下,我们都可以通过mid.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 void reorderList(ListNode head) {
ListNode mid= split(head);
ListNode headb= reverse(mid.next);
//这一步很关键,要将两个链表分开来,这样后面重新插入结尾才不会出错
mid.next=null;
merge(head,headb);
}
public ListNode split(ListNode head){
ListNode slow=head;
ListNode fast=head;
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
public ListNode reverse(ListNode head){
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
public void merge(ListNode heada,ListNode headb){
ListNode cura=heada;
ListNode curb=headb;
while(curb!=null){
ListNode nexta=cura.next;
ListNode nextb=curb.next;
curb.next=nexta;
cura.next=curb;
cura=nexta;
curb=nextb;
}
}
}
77.链表排序
链表的归并排序,需要实现「找中间节点」以及两个排序链表的合并函数,数组的归并排序需要O(N)
的空间,链表改变指向不需要额外的空间。
/**
* 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 sortList(ListNode head) {
if(head==null) return null;
if(head.next==null) return head;
// 划分成两半
ListNode mid=split(head);
ListNode list2=mid.next;
mid.next=null;
//归并排序
ListNode left=sortList(head);
ListNode right=sortList(list2);
return merge(left,right);
}
// 找到中间节点进行划分
public ListNode split(ListNode head){
ListNode fast=head;
ListNode slow=head;
while(fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
// 合并两个已经排序的列表
public ListNode merge(ListNode list1,ListNode list2){
ListNode dummyhead=new ListNode(-1);
ListNode cur=dummyhead;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
cur.next=list1;
list1=list1.next;
}else{
cur.next=list2;
list2=list2.next;
}
cur=cur.next;
}
cur.next=list1==null?list2:list1;
return dummyhead.next;
}
}
27.回文链表
从后向前找到链表中心点,偶数找到左边中心点,然后将后半部分链表反转,最后比较后小半部分是否和从head
开始的前半部分相同。
/**
* 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 boolean isPalindrome(ListNode head) {
ListNode fast=head;
ListNode slow=head;
//找到中心点
while(fast.next!=null&&fast.next.next!=null){
slow=slow.next;
fast=fast.next.next;
}
//后半部分反转
ListNode pre=null;
ListNode cur=slow.next;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
//后半部分偏小,比较反转的后半部分和前半部分是否回文
while(pre!=null){
if(pre.val!=head.val) return false;
pre=pre.next;
head=head.next;
}
return true;
}
}
23.两个链表的第一个重合节点
参考题解,本质上就是要一直找到两个链表「地址相同的两个节点」,但两链表节点数不一样,没法对应。可以尝试拼接的做法,让p1遍历完链表A之后开始遍历链表B,让p2遍历完链表B之后开始遍历链表A,这样相当于「逻辑上」两条链表接在了一起,这样子就让相交节点前面长度补齐了。即使最后两链表没有相交,则p1=p2=null
退出循环。两条拼接链表都只遍历一次。
/**
* 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 p1=headA;
ListNode p2=headB;
while(p1!=p2){
p1=p1==null?headB:p1.next;
p2=p2==null?headA:p2.next;
}
return p1;
}
}
24.反转链表
/**
* 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 reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
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 {
public ListNode reverseList(ListNode head) {
return dfs(head,null);
}
// 返回值都是链表头节点,只是在回溯的过程中改变指向
// dfs表示反转后面的节点,同时返回最终的答案
public ListNode dfs(ListNode head,ListNode pre){
if(head==null) return pre;
ListNode res=dfs(head.next,head);
head.next=pre;
return res;
}
}
28.展平多级双向链表
遍历第一级节点,将后面的节点逐层拼接到第一级上,做法是将head.child!=null
的节点cur的孩子节点头尾改变指向,放到第一级上去,然后继续遍历cur.next
(已经将第二级拼接上来改变结构了)。
/*
// 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) {
Node cur=head;
while(cur!=null){
//可能后面级插入上来的child也不为null
if(cur.child!=null){
//保存cur的下一个节点,便于插入后的拼接
Node temp=cur.next;
//改变cur的指向,双向改变
Node childnode=cur.child;
cur.next=childnode;
childnode.prev=cur;
cur.child=null;
//找到child的最后一个节点
Node last=cur;
while(last.next!=null){
last=last.next;
}
//将child最后一个节点连接到第一级上
last.next=temp;
if(temp!=null) temp.prev=last;
}
//同时可以查看后面拼接上来的节点的child
cur=cur.next;
}
return head;
}
}
29.排序的循环链表
参考题解,考虑到五种情况,while(ptr->next != head)
是循环链表循环查找的方式。从头开始查找插入的位置ptr
,当查到1.val在当前节点ptr
下一个点中间 2.最大值最小值 3.循环到头结束(1个元素或者都是相同元素),就在ptr
后面插入元素,实际上就是找到链表插入基本操作里的pre
节点了。
/*
// Definition for a Node.
class Node {
public int val;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _next) {
val = _val;
next = _next;
}
};
*/
class Solution {
public Node insert(Node head, int insertVal) {
//列表为空
if(head==null){
Node newNode=new Node(insertVal);
newNode.next=newNode;
return newNode;
}
Node ptr=head;
//循环到头结束
while(ptr.next!=head){
//val在当前节点ptr和下一个点中间
if(insertVal>=ptr.val&&insertVal<=ptr.next.val) break;
//在链表首尾相连处找到最大值最小值
if(ptr.val>ptr.next.val&&(insertVal>=ptr.val||insertVal<=ptr.next.val)) break;
ptr=ptr.next;
}
Node newNode=new Node(insertVal);
newNode.next=ptr.next;
ptr.next=newNode;
return head;
}
}
25.链表中的两数相加
先用栈将两个链表的数字反转存储,然后双对象双指针的加法模板+头插法构建链表
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> st1=new Stack<>();
Stack<Integer> st2=new Stack<>();
while(l1!=null){
st1.push(l1.val);
l1=l1.next;
}
while(l2!=null){
st2.push(l2.val);
l2=l2.next;
}
int carry=0;
ListNode anshead=null;
while(!st1.isEmpty()||!st2.isEmpty()){
int digitA=st1.isEmpty()?0:st1.peek();
int digitB=st2.isEmpty()?0:st2.peek();
int sum=digitA+digitB+carry;
int res=sum%10;
carry=sum/10;
ListNode newNode=new ListNode(res);
newNode.next=anshead;
anshead=newNode;
if(!st1.isEmpty()) st1.pop();
if(!st2.isEmpty()) st2.pop();
}
if(carry!=0){
ListNode newNode=new ListNode(carry);
newNode.next=anshead;
anshead=newNode;
}
return anshead;
}
}
83.删除排序链表中的重复元素
相比于数组去重26.删除有序数组中的重复项中的不重复就是nums[right]
覆盖nums[++left]
,重复就right++
忽略处理;链表中需要把握前一个节点为cur
,同时其后一个节点cur.next
,这两个节点的关系,逐个往后推,cur、cur.next
前后相等则删除后一个节点cur.next
,不同则cur
继续往前不改变。
迭代做法:
/**
* 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 deleteDuplicates(ListNode head) {
if(head==null) return null;
ListNode cur=head;
//只有一个节点时不满足条件直接返回
while(cur!=null&&cur.next!=null){
if(cur.val==cur.next.val){
//前后相同删除cur.next
cur.next=cur.next.next;
}else{
cur=cur.next;
}
}
return head;
}
}
参考删除数组中的重复元素,还做了一种实现,需要将原先链表中重复的值进行替换,而不是直接删除。
/**
* 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 deleteDuplicates(ListNode head) {
if(head==null) return null;
if(head.next==null) return head;
ListNode left;
ListNode right;
left=head;
right=head.next;
while(right!=null){
if(right.val!=left.val){
left=left.next;
left.val=right.val;
}
right=right.next;
}
left.next=null;
return head;
}
}
递归做法:
/**
* 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 deleteDuplicates(ListNode head) {
//base情况
if(head==null||head.next==null) return head;
if(head.next.val!=head.val){
head.next=deleteDuplicates(head.next);
}else{
//head->head.next(move)->...->null
ListNode move=head.next;
//直到move为null或者已经不和head.val一样
while(move!=null&&move.val==head.val){
move=move.next;
}
head.next=deleteDuplicates(move);
}
return head;
}
}
另一种写法:
关键在于递归的base情况,就是链表尾部,以及递归返回的节点,是链表已经处理过重复后的头部节点。
/**
* 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 deleteDuplicates(ListNode head) {
return dfs(head);
}
// 从左到右递归,从右到左递归返回
public ListNode dfs(ListNode head){
if(head==null||head.next==null) return head;
while(head!=null&&head.next!=null&&head.val==head.next.val) // 要一删到底,防止[1,1,1]的情况返回1,1
head.next=head.next.next;
head.next=dfs(head.next);
return head;
}
}
82.删除排序链表中的重复元素II
使用了递归和迭代两种做法,参考负雪明烛大佬的题解。
递归解法:
/**
* 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 deleteDuplicates(ListNode head) {
//base情况
if(head==null||head.next==null) return head;
//递归情况
if(head.next.val!=head.val){
head.next=deleteDuplicates(head.next);
return head;
}else{
//head->head.next(move)->...->null
ListNode move=head.next;
//直到move为null或者已经不和head.val一样
while(move!=null&&move.val==head.val){
move=move.next;
}
//head不保留
return deleteDuplicates(move);
}
}
}
迭代解法:
这里关键是需要将出现重复的节点都删除,像上一题83.删除排序链表中的重复元素,出现重复节点的下面节点删除是容易的,是因为我们用cur
节点可以很方便地找到cur.next
然后改变cur
的指向,但这一题需要连着cur
一起删了,因此引入了pre
节点。
当有重复节点的时候,先cur=cur.next往后走,如果到达最后一个再判断,pre
节点的指向以及移动是本题的难点,当没有重复节点的时候pre=pre.next
,否则改变指向pre.next=cur.next
,如何判断是否有重复节点,则判断是否pre.next==cur
,即中间有没有空档。
/**
* 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 deleteDuplicates(ListNode head) {
ListNode dummyHead=new ListNode(0);
dummyHead.next=head;
ListNode pre=dummyHead;
ListNode cur=head;
while(cur!=null){
//指向重复节点的最后一个
while(cur.next!=null&&cur.next.val==cur.val){
cur=cur.next;
}
//pre节点的移动
if(pre.next==cur) pre=pre.next;
else pre.next=cur.next;
cur=cur.next;
}
return dummyHead.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 deleteDuplicates(ListNode head) {
ListNode dummyHead=new ListNode(-1);
dummyHead.next=head;
ListNode pre=dummyHead;
ListNode cur=head;
while(cur!=null&&cur.next!=null){
if(cur.val==cur.next.val){
while(cur!=null&&cur.next!=null){ // 这里是将重复的元素都找到
if(cur.val==cur.next.val){
cur=cur.next;
}else{
break;
}
}
// 然后进行删除
cur=cur.next; //这里卡了下bug,注意先要移动一下cur
pre.next=cur;
}else{
pre=pre.next;
cur=cur.next;
}
}
return dummyHead.next;
}
}
2.两数相加
和我的另一篇文章算法学习-位运算以及进制表示有关的问题,让脑袋像机器一样思考得到光荣进化采用同样的加法模板,不过这里是在链表上进行了应用,包含了链表的创建操作。这题是逆序存储,链表头寸数字最低位,直接从头往后加就行了。
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
int carry=0;
ListNode dummyHead=new ListNode(0);
ListNode cur=dummyHead;
while(l1!=null||l2!=null){
int digitA=l1!=null?l1.val:0;
int digitB=l2!=null?l2.val:0;
int sum=digitA+digitB+carry;
int digit=sum%10;
carry=sum/10;
cur.next=new ListNode(digit);
cur=cur.next;
if(l1!=null) l1=l1.next;
if(l2!=null) l2=l2.next;
}
if(carry!=0) cur.next=new ListNode(carry);
return dummyHead.next;
}
}
445.两数相加II
这题链表头存的是最高位,为了从最低位开始加起,需要先拿链表反存一下,由于先算出来的是最低位结果,又需要采用头插法存储结果。
/**
* 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 addTwoNumbers(ListNode l1, ListNode l2) {
Stack<Integer> st1=new Stack<>();
Stack<Integer> st2=new Stack<>();
while(l1!=null){
st1.push(l1.val);
l1=l1.next;
}
while(l2!=null){
st2.push(l2.val);
l2=l2.next;
}
int carry=0;
ListNode head=null;
while(!st1.isEmpty()||!st2.isEmpty()){
int digitA=!st1.isEmpty()?st1.peek():0;
int digitB=!st2.isEmpty()?st2.peek():0;
int sum=digitA+digitB+carry;
int digit=sum%10;
carry=sum/10;
ListNode newNode=new ListNode(digit);
newNode.next=head;
head=newNode;
if(!st1.isEmpty())st1.pop();
if(!st2.isEmpty())st2.pop();
}
if(carry!=0){
ListNode newNode=new ListNode(carry);
newNode.next=head;
head=newNode;
}
return head;
}
}
817.链表组件
判断连续段与断开需要掌握。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def numComponents(self, head: Optional[ListNode], nums: List[int]) -> int:
s=set()
for n in nums:
s.add(n)
res=0
# 每次指向当前cur的前一个值,初始化为-1,
# 用于判断当前值和前面的值是否是断开的关系(当前值在nums中,前一个值不nums中)
pre=-1
cur=head
while cur!=None:
if (pre==-1 or pre not in s) and cur.val in s:
res+=1
pre=cur.val
cur = cur.next
return res
21.合并两个有序链表
双指针+链表从零开始创建
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode cura=list1;
ListNode curb=list2;
ListNode dummyhead=new ListNode(-1);
ListNode cur=dummyhead;
while(cura!=null&&curb!=null){
if(cura.val<=curb.val){
cur.next=cura;
cura=cura.next;
}else{
cur.next=curb;
curb=curb.next;
}
cur=cur.next;
}
// 拼接上没插入完的部分
cur.next=cura==null?curb:cura;
return dummyhead.next;
}
}
23.合并K个升序链表
归并的思想,排序已经排好了,每次问题的规模是所有链表的长度加起来为N,但是层数减少了,变为logk,总体时间复杂度为logkN。
/**
* 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 mergeKLists(ListNode[] lists) {
return dfs(lists,0,lists.length-1);
}
public ListNode dfs(ListNode[] lists,int i,int j){
if(i>j) return null;
if(i==j) return lists[i];
int mid=(i+j)/2;
ListNode head1=dfs(lists,i,mid);
ListNode head2=dfs(lists,mid+1,j);
ListNode ans=merge(head1,head2);
return ans;
}
public ListNode merge(ListNode head1, ListNode head2){
ListNode cura=head1;
ListNode curb=head2;
ListNode dummyhead=new ListNode(-1);
ListNode cur=dummyhead;
while(cura!=null&&curb!=null){
if(cura.val<=curb.val){
cur.next=cura;
cura=cura.next;
}else{
cur.next=curb;
curb=curb.next;
}
cur=cur.next;
}
cur.next=cura==null?curb:cura;
return dummyhead.next;
}
}
链表中的环问题
看到别人面试的连环问:
- 怎么判断链表相交
- 怎么判断链表有环
- 怎么找到环入口地址?为什么这么找?怎么证明
- 怎么判断两个有环链表是否相交?
22.链表中环的入口节点
快慢指针的运用,快指针每次走两步,慢指针每次走一步,能相遇则一定有环否则无环。设链表共有 a+b
个节点,其中 链表头部到链表入口 有 a
个节点(不计链表入口节点), 链表环 有 b
个节点,快指针步数fast=2*slow
,fast=slow+n*b
,两者相遇得slow=n*b
。所有指针走到链表入口节点时的步数是k=a+nb
,在慢指针走了nb
步的情况下,再走a
步就可以了,这正是链表头开始走,可以和慢指针slow碰头的步数。
/**
* 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) {
ListNode fast=head;
ListNode slow=head;
ListNode ans=null;
// 当fast能够结束就说明无环,所以如果不存在环,一遍就能结束,
// 如果存在环,自然内部 if(fast==slow)会拦住
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
ans=head;
ListNode cur2=fast;
while(ans!=cur2){
ans=ans.next;
cur2=cur2.next;
}
// ans==cur2
break;
}
}
// 无环返回null
return ans;
}
}
链表相交问题
链表相交
快慢指针+双指针,本质上需要理解,这是两个有穷的链表,两个链表相交说明有一个地址相同的相交链表节点,该节点以后所有的节点这两个链表都是相同的。但是两个链表长度是不同的,相交以后的长度一定相同,所以可以先遍历两个链表找出长度,再让快指针的一方先补齐长度,接着同时移动比较指针。
/**
* 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) {
int len1=0;
int len2=0;
ListNode cura=headA;
ListNode curb=headB;
while(cura!=null){ // 统计长度
len1++;
cura=cura.next;
}
while(curb!=null){
len2++;
curb=curb.next;
}
if(len1<len2){ // 让a是长链,准备查找相交节点
cura=headB;
curb=headA;
int temp=len1;
len1=len2;
len2=temp;
}else{
cura=headA;
curb=headB;
}
for(int i=0;i<len1-len2;i++){
cura=cura.next;
}
for(int i=0;i<len2;i++){
if(cura==curb){
return cura;
}else{
cura=cura.next;
curb=curb.next;
}
}
return null; // 没有链表相交
}
}