LeetCode链表总结
以下所有的试题存出自LeetCode,每个题的解法思路均有参考和对比LeetCode上官方及各位大神们的题解(非常感谢平台和大家无私的分享),有些题的解法较为繁多,此处并未全部写出思路或者解题代码。
目录
我们默认定义链表结构如下(LeetCode链表的统一定义类型):
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
1哑结点(假结点)
设置一个结点指向原始链表的头结点,方便更改链表结构,也方便返回新的头结点
ListNode dummy(-1);
dummy.next = head;
ListNode* Pre = &dummy;
ListNode* PCur = head;
..........
return dummy.next;
2两种删除方法
删除链表中的某个结点,有两种方法,第一种方法,得到要删除结点的前一个结点指针,将其指向要删除结点的后一个结点,完成。
这是最常规也是最常用的方法,但是在有些情况下,我们无法得到要删除结点的前一个结点指针,那么就指针使用第二种方法。
覆盖内容,将要删除结点的后一个结点的值,赋给要删除的结点,然后将要删除的结点的next指针指向下下一个结点(也就是跳过了
要删除结点的后一个结点)。覆盖内容有一个缺点,就是无法删除尾结点,一旦涉及尾结点的删除,只能使用常规结点进行操作。
LeetCode中关于链表的基本概念和常见题型介绍:https://leetcode-cn.com/explore/learn/card/linked-list/193/singly-linked-list/
下面部分附图也是出自LeetCode
第一种方法:常规操作
第二种方法:覆盖操作
代码如下(以面试题 02.03. 删除中间节点为例):
class Solution {
public:
void deleteNode(ListNode* node) {
ListNode* Nnext = node->next;
node->val = Nnext->val;
node->next = Nnext->next;
}
};
关于利用第二种方法进行结点删除的题目如下:
相关题目:
面试题 02.03. 删除中间节点:https://leetcode-cn.com/problems/delete-middle-node-lcci/
删除链表中的节点:https://leetcode-cn.com/problems/delete-node-in-a-linked-list/
面试题18. 删除链表的节点:https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/
而关于面试题18,题目要求删除结点,没有特指中间结点,所以该结点包换收尾结点,使用双指针和哑结点会很轻易的完成题解
当然,如果面试要求不能使用哑结点,也要知道应该如何入手。
3两种插入方法:
尾插法:最常见的元素插入方法,如图所示
头插法:在特定场合,比如反转输出链表,使用栈,或者头插法都是很好的选择,如图所示
4双指针:
单向链表中大部分操作,核心都是在遍历中完成各项操作,删除,插入,替换,反转,都是在遍历中完成的。
那么对于单向链表来说,因为单向,所以前一个结点是最为宝贵的,很多操作都牵扯到前结点,由此,也就有了双指针的解题方法。
设置两个步长差1的指针,Pre和PCur,Pre指向nullptr,dummy或者头结点head,pCur指向head或者head->next;
然后开始遍历操作,在遍历过程中,完成各项操作,下面看看设计双指针的例题都有哪儿些。
4.1删除结点
4.1.1移除链表元素:
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
//双指针
if(head == nullptr)
return head;
ListNode dummy(-1);
dummy.next = head;
ListNode* Pre = &dummy;
ListNode* PCur = head;
while(PCur)
{
if(PCur->val == val)
{
Pre->next = PCur->next;
PCur->next = nullptr;
PCur = Pre->next;
}
else
{
Pre = PCur;
PCur = PCur->next;
}
}
return dummy.next;
//递归
if(head == nullptr)
return nullptr;
if(head->val == val)
{
head = removeElements(head->next,val);
}
else
{
head->next = removeElements(head->next,val);
}
return head;
}
};
非常典型的双指针的应用操作,同样的使用递归也是非常好的办法。
4.1.2删除排序链表中的重复元素
方法一:使用Hash表,在表中寻找元素,没有找到,放入表中,找到了,删除结点
方法二:注意,这是一个排序链表,数字都是按照顺序出现的,那么相邻两个元素不断比较,就知道重复不重复了,重复的删除即可。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//Hash表
if(head == nullptr)
return head;
unordered_map<int,int>M;
ListNode* Pre = nullptr;
ListNode* PCur = head;
while(PCur)
{
if(M.find(PCur->val) != M.end())//找到了
{
Pre->next = PCur->next;
PCur = Pre->next;
}
else
{
M[PCur->val] = PCur->val;
Pre = PCur;
PCur = PCur->next;
}
}
return head;
//双指针
if(head == nullptr)
return head;
ListNode* Pre = head;
ListNode* PCur = head->next;
while(PCur)
{
if(Pre->val == PCur->val)
{
Pre->next = PCur->next;
PCur->next = nullptr;
PCur = Pre->next;
}
else
{
Pre = PCur;
PCur = PCur->next;
}
}
return head;
}
};
4.1.3删除排序链表中的重复元素 II
那么关于删除元素的进阶版,又该如何呢?
Hash表显然不好用了,还是要抓住链表是排序链表的特点,利用双指针。但是要注意细节,合理利用dummy结点,前指针保存前置结点,后指针不断loop,直到删除完全部的重复元素,然后再继续,一定要注意,重复的个数不一定是2。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//易读部分
if(head == nullptr)
return head;
ListNode dummy(-1);
dummy.next = head;
ListNode* Pre = &dummy;
ListNode* PCur = head;
while(PCur)
{
if(PCur->next&&PCur->val == PCur->next->val)
{
int val = PCur->val;
Pre->next = PCur->next->next;
PCur = PCur->next->next;
while(PCur&&PCur->val == val)
{
Pre->next = PCur->next;
PCur = PCur->next;
}
}
else
{
Pre = PCur;
PCur = PCur->next;
}
}
return dummy.next;
//高质量
if(head == nullptr)
return nullptr;
ListNode dummy(-1);
dummy.next = head;
ListNode* Pre,* Pcur = &dummy;//设置一个临时的头指针
while(Pcur)
{
Pre = Pcur;
Pcur = Pcur->next;//放在前面就不用过多的判断了
while(Pcur&&Pcur->next&&Pcur->val == Pcur->next->val)
{
int temp = Pcur->val;
Pcur = Pcur->next;
while(Pcur&&Pcur->val == temp)
{
Pcur = Pcur->next;
}
}
Pre->next = Pcur;
}
return dummy.next;
}
};
4.1.4非排序链表删除重复元素
那么如果失去了排序这个最大的特征,同时要求不使用额外的空间(比如Hash表,Hash表做这个题依旧秒杀),该如何应对 ?
那么只能使用比较暴力的解法,一个指针track指向第一个结点,设置两个游标指针,遍历剩下的结点找相同的点,完成遍历后,track再后移。时间复杂度O(N^2),牺牲时间去换取空间。
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) {
if(head == nullptr)
return nullptr;
ListNode * Pre = nullptr;
ListNode * PCur = nullptr;
ListNode * Track = head;
while(Track!=nullptr)
{
Pre = Track;
PCur = Track->next;
while(PCur != nullptr)
{
if(PCur->val == Track->val)//相等,进行删除操作
{
Pre->next = PCur->next;
PCur = PCur->next;
}
else
{
Pre = PCur;
PCur = PCur->next;
}
}
Track = Track->next;
}
return head;
}
};
4.2反转链表
反转一个单链表,递归是一个非常合适的方法,如果不使用递归,有什么好的方法?
双指针遍历,让后指针的next指向前指针,最终返回尾结点也就是新链表的头结点,注意细节,头结点的next记着指向nullptr,否则会循环输出。
那么如果是领一个链表的n到m个结点反转呢?
同理,注意保留关键结点即可。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
if(head == nullptr) return head;
ListNode * NewHead = head;
int delta = n - m + 1;
if(m == 1) return ReverseList(NewHead,delta);//从第一个位置就开始反转
while(m > 2)//找到反转开始的前一个结点
{
m--;
if(NewHead) NewHead = NewHead->next;
}
NewHead->next = ReverseList(NewHead->next,delta);
return head;
}
ListNode * ReverseList(ListNode* head, int delta)//反转链表部分,返回反转后的新头部
{
if(head == nullptr) return head;
cout<<delta<<endl;
ListNode* Pre = nullptr;
ListNode* temp = head;
ListNode * Next;
while(temp&&delta)
{
delta--;
Next = temp->next;
temp->next = Pre;
Pre = temp;
temp = Next;
}
head->next = Next;
return Pre;
}
};
5快慢指针(精准定位):
一旦牵扯到精准定位,比如找环的起点,找倒数某个结点,两个链表的公共结点,一个链表的中间结点等
即精准的找到链表中的某个结点的题,基本都是快慢指针
5.1关于环
关于链表中的环,示意图如上图所示,单向链表中有一个环,常见的考查内容:
- 判断是否有环
- 计算环的大小
- 确定环开始的位置
环形链表 https://leetcode-cn.com/problems/linked-list-cycle/
都是精准定位的问题,毫无疑问,快慢指针,fast和slow指针都是从head出发,一个一次走一步,一个一次走两步,如果有环,fast指针必定会追上slow,必定会在环中相遇,假设相遇点为C点,没有追上,那就是单向链表中没有环。(解决问题1)。
既然在环中的某个结点相遇,也就是找到了环中的某个结点,那么循环一周再次回到这个结点时,也就就计算出了环的大小。
(解决问题2)
因为fast的步长是slow的二倍,又在C点相遇,假设示意图中A到B结点距离为X,B到C结点距离为Y,C到B结点的距离为Z。
X+Y+Z+Y= 2*(X+Y);等号左边是fast指针走的路程,等号右边是slow指针走的路程。
显然得出,Z = X;
那么从相遇点到环的起点,和从head到环的起点,路程相等,那么不断迭代指针进行前进操作,相遇点,就是环的起点(解决问题3)
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr||head->next == nullptr)
return nullptr;
ListNode * slow = head;
ListNode * fast = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
break;
}
ListNode * PCur = head;
while(PCur&&fast&&fast->next)
{
if(PCur == fast)
return fast;
PCur = PCur->next;
fast = fast->next;
}
return nullptr;
}
};
除了以上三种典型的环外,还有一些关于环的编写的题目:
旋转链表 https://leetcode-cn.com/problems/rotate-list/
此题,需要把单链表一开始就处理成环(及循环链表)。然后根据题目的意思,去循环找新的头结点,找到头结点了,那么尾结点就在头结点的前面,之后进行断开操作即可。
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr)
return nullptr;
unordered_map<int,int>M;
int Size = 0;
ListNode* PCur = head;
while(PCur->next)
{
PCur = PCur->next;
Size++;
}
PCur->next = head;//循环链表
Size++;
PCur = head;
int Index = k%Size;
for(int i = 0;i<Size - Index -1;++i)
{
PCur = PCur->next;
}
ListNode* Begin = PCur->next;
PCur->next = nullptr;
return Begin;
}
};
5.2两个链表相交结点
面试题52. 两个链表的第一个公共节点 https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/
面试题 02.07. 链表相交 https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/
相交链表 https://leetcode-cn.com/problems/intersection-of-two-linked-lists/
此类题目基本都是如下描述:
使用Hash表,A链表全部放入Hash表,key值为地址,之后在B链表中遍历,每到一个指针就在Hash表中寻找,找到,自然有相遇的结点,找不到,自然是不相遇。
除此之外,不使用额外空间,就使用快慢指针,因为两个链表的长度不等,也有可能相等,如果相等,两个指针均从AB链表的头出发,那么相遇处一定是链表的相交点,但是如果链表长度不相等呢?我们要如何使用指针来消除这个距离上的差值?
方法如下:两个指针分别从A,B链表的头部出发,步长均为1,一旦达到尾结点,将指向另一条链表的头部,比如一开始指向A结点head的指针APointer,随着不断遍历,达到nullptr时,理科让APointer指针指向B链表的头部,继续遍历。
当APointer和BPointer相遇时,就是两个链表的相交点。
假设A链表长度为X,B链表长度是Y。
X-Y = Deta;
又因为相交之后的链表长度相对,那么Deta只会体现在相交之前的这部分链表上,最好的办法就是上述办法,完成一次遍历后直接将指针换到另一个链表的头指针上,直接消除了Deta差值,变成了相同长度链表的情况。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == nullptr && headB == nullptr)
return nullptr;
ListNode * PCurA = headA;
ListNode * PCurB = headB;
while(PCurA != PCurB)
{
PCurA = (PCurA == nullptr ? headB:PCurA->next);
PCurB = (PCurB == nullptr ? headA:PCurB->next);
}
return PCurA;
}
};
5.3倒数结点
面试题 02.02. 返回倒数第 k 个节点 https://leetcode-cn.com/problems/kth-node-from-end-of-list-lcci/
删除链表的倒数第N个节点 https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/
面试题22. 链表中倒数第k个节点 https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/
找正数第N个结点并且删除,那实在是太容易了,那么倒数呢?看图说明问题:
slow指针指向Dummy,fast指针和slow相差n个步长,倒数第n个元素也是要操作的元素,之后遍历,直到fast等于nullptr的时候,slow指向了倒数第n个元素。
当然了,有时候是要找到倒数第n个元素,有时候是要删除,那么slow和fast的开始指向,相差步数,都是需要调整的,但是原理都是一样的。都是利用快慢指针想办法制造“倒数”,及从fast往slow数。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head == nullptr || n <= 0)
return nullptr;
ListNode dummy(-1);
dummy.next = head;
ListNode* Ppre = &dummy;
ListNode* Pend = head;
int k = n;
while(Pend!=nullptr)
{
while(k!=0&&Pend != nullptr)
{
k--;
Pend = Pend->next;
}
if(Pend == nullptr)
break;
Pend = Pend->next;
Ppre = Ppre->next;
}
if(Ppre->next!=nullptr)
Ppre->next = Ppre->next->next;
return dummy.next;
}
};
5.4中间结点
链表的中间结点 https://leetcode-cn.com/problems/middle-of-the-linked-list/
链表的中间结点 https://leetcode-cn.com/problems/middle-of-the-linked-list/
寻找链表的中间元素,【1,2,3,4,5】中间值。【1,2,3,4】中间值:3。
slow一次步长为1,fast步长为2。当fast->next或者fast等于nullptr的时候,slow也就是中间值了,这也是利用快慢指针模拟“一半”的状态。
class Solution {
public:
ListNode* middleNode(ListNode* head) {
//典型的快慢指针
if(head == nullptr)
return nullptr;
ListNode* slow = head;
ListNode* fast = head;
while(fast&&slow)
{
if(fast->next==nullptr)
break;
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
6先入后出:
两数相加 II:https://leetcode-cn.com/problems/add-two-numbers-ii/
栈:先进后出,这个性质用好了可不得了,会简化非常多的指针操作步骤,链表中也有部分题目也会借助于栈的结构特点
链表直接入栈,高位栈底低位栈顶,弹出直接加,注意进位即可:(本体务必注意的是进位问题和两个链表长度题目中可没有说相等,不等长的情况务必考虑)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//stack<int>
if(l1 == nullptr&&l2 == nullptr) return nullptr;
if(l1 != nullptr&&l2 == nullptr) return l1;
if(l1 == nullptr&&l2 != nullptr) return l2;
stack<int> Dl1,Dl2,Res;
ListNode* Temp = l1;
while(Temp) {Dl1.push(Temp->val);Temp = Temp->next;}//分别入栈
Temp = l2;
while(Temp) {Dl2.push(Temp->val);Temp = Temp->next;}//分别入栈
int YN = 0;
while(Dl1.size()||Dl2.size())
{
int tempNum = YN;
if(Dl1.size()) {tempNum += Dl1.top();Dl1.pop();}
if(Dl2.size()) {tempNum += Dl2.top();Dl2.pop();}
YN = tempNum/10;//是否进位,进位多少
tempNum = tempNum%10;
Res.push(tempNum);
}
if(YN != 0) Res.push(YN);
//创建新的链表
ListNode* head = new ListNode(-1);
ListNode* pre = nullptr;
// cout<<Res.size()<<endl;
while(Res.size())
{
ListNode* Item = new ListNode();
if(head->val == -1) head = Item;
if(pre == nullptr) pre = Item;
else{pre->next = Item;pre = Item;}
Item->val = Res.top();
cout<<Item->val<<endl;
Res.pop();
}
return head;
}
};
其他求和相关的题目:面试题 02.05. 链表求和:https://leetcode-cn.com/problems/sum-lists-lcci/
两数相加:https://leetcode-cn.com/problems/add-two-numbers/
当然,还有处理回文链表,完全可以使用栈,先将元素全部压入栈中,此时栈顶就是尾结点的val,栈底就是头结点的val,之后从链表的头结点开始遍历,对比栈顶元素和链表指针所指元素是否相等,如果是回文,那么链表正序遍历和逆序遍历元素都是一样的,此时的栈就是充当逆序遍历的一种方式。
回文链表 https://leetcode-cn.com/problems/palindrome-linked-list/
面试题 02.06. 回文链表:https://leetcode-cn.com/problems/palindrome-linked-list-lcci/
class Solution {
public:
bool isPalindrome(ListNode* head) {
stack<int> s;
ListNode *p = head;
while(p)
{
s.push(p->val);
p = p->next;
}
p = head;
while(p)
{
if(p->val != s.top())
{
return false;
}
s.pop();
p = p->next;
}
return true;
}
};
但是如果题目有要求,比如不允许使用额外的空间,那么就没办法使用栈了,只能使用其他方法。
7递归(重复操作)
简单可执行的重复操作,在链表中递归是个非常常用的方法,很多问题都可以利用递归进行解决 。
合并两个有序链表 https://leetcode-cn.com/problems/merge-two-sorted-lists/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == nullptr)
return l2;
else if(l2 == nullptr)
return l1;
ListNode * NewHead = nullptr;
if(l1->val < l2->val)
{
NewHead = l1;
NewHead->next = mergeTwoLists(l1->next,l2);
}
else
{
NewHead = l2;
NewHead->next = mergeTwoLists(l1,l2->next);
}
return NewHead;
}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head == nullptr) return nullptr;
if (head&&head->next == nullptr) //找到链表的最后一个值
{
return head;
}
ListNode* ret = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return ret;//返回反转链表的新头指针
}
};
两两交换链表中的节点 https://leetcode-cn.com/problems/swap-nodes-in-pairs/
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(head == nullptr||head->next == nullptr)
return head;
ListNode* Pnext = head->next;
ListNode* Insethead = Pnext->next;
Pnext->next = head;
head->next = swapPairs(Insethead);
return Pnext;
}
};
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head == nullptr)
return nullptr;
if(head->next != nullptr&&head->val == head->next->val)
{
while(head->next != nullptr&&head->val == head->next->val)
head = head->next;
// return deleteDuplicates(head);//删除重复元素,但是会剩下一个
return deleteDuplicates(head->next);//重复元素全部删除
}
else
{
head->next = deleteDuplicates(head->next);
return head;
}
}
};
8综合:
8.1反转链表
回文链表 https://leetcode-cn.com/problems/palindrome-linked-list/
面试题 02.06. 回文链表:https://leetcode-cn.com/problems/palindrome-linked-list-lcci/
回文:如果不使用栈,那就反转链表。找到中间结点,然后后半段反转,然后一个从头一个从尾,两个指针往中间结点遍历,对比数值
class Solution {
public:
bool isPalindrome(ListNode* head) {
if(head == nullptr||head->next == nullptr)//没有也算
return true;//算回文
//找到中间结点
ListNode* slow = head;
ListNode* fast = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
//后面反转链表
ListNode* pCur = slow;
ListNode* pnext = slow->next;
while(pnext)
{
ListNode* temp = pnext->next;
pnext->next = pCur;
pCur = pnext;
pnext = temp;
}
slow->next = nullptr;
//比较
// ListNode* tempHead = head;
while(head&&pCur)
{
if(head->val != pCur->val)
return false;
head = head->next;
pCur = pCur->next;
}
return true;
}
};
8.2开辟新链表
有时候需要对原版的链表进行分割,比如拆出奇偶项等情况。
有时候需要重新将两个链表合并。
这个需要重新建立一个新的头结点,连接被分割出的链表也好,连接重新排序的链表也好。本质都是一样的,都是对链表的理解和指针的操作提出了高要求。
奇偶链表;https://leetcode-cn.com/problems/odd-even-linked-list/
此时,就是要将原来的链表分割为两个链表,然后重排这两个链表。
与合并链表异曲同工,设置一个新指针,指向偶数部分的第一个结点,然后两个个指针,一个从第一个奇数结点出发,一个从偶数结点开始遍历, 断开旧的链接,建立新的链接,之后得到一组奇数链表,一组偶数链表,然后将链表组合即可。
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head == nullptr||head->next == nullptr)
return head;
ListNode* NewHead = head->next;
ListNode* OldPCur = head;
ListNode* NewPCur = NewHead;
while(NewPCur&&NewPCur->next)
{
OldPCur->next = NewPCur->next;
OldPCur = OldPCur->next;
NewPCur->next = OldPCur->next;
NewPCur = NewPCur->next;
}
OldPCur->next = NewHead;
return head;
}
};
合并两个有序链表 https://leetcode-cn.com/problems/merge-two-sorted-lists/
此题使用递归非常简单,但是不使用递归的话
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// return l2;
if(l1 == nullptr&&l2 != nullptr) return l2;
if(l1 != nullptr&&l2 == nullptr) return l1;
if(l1 == nullptr&&l2 == nullptr) return nullptr;
ListNode* One = l1;
ListNode* Two = l2;
ListNode* head = nullptr;
ListNode* pre = nullptr;
while(One&&Two)
{
ListNode* temp = new ListNode;
if(head == nullptr) head = temp;
if(pre == nullptr) pre = temp;
else {pre->next = temp;pre = temp;}
// cout<<One->val<<" "<<Two->val<<endl;
if(One->val <= Two->val)
{
temp->val = One->val;
One = One->next;
}
else
{
temp->val = Two->val;
Two = Two->next;
}
}
while(One)
{
ListNode* temp = new ListNode;
pre->next = temp;pre = temp;
temp->val = One->val;
One = One->next;
}
while(Two)
{
ListNode* temp = new ListNode;
pre->next = temp;pre = temp;
temp->val = Two->val;
Two = Two->next;
}
return head;
}
};
但是本题可以再做扩展,我们不使用额外的空间,原地合并,不开辟新的空间
我们创建一个Prehead之中,并且让Pre指向Prehe,然后进入循环,比较l1的l2目前所指的内容大小,让pre的next指向小的,如图中,指向了0,然后我们保持l1指针不动,移动l2,再移动pre,此时的pre指向0,l2指向3,l1还指向1。
如果再次比较的话,那么pre指向的显然是1,l1后移。那么此时新的内容也串联了起来,逻辑清晰,代码简单,阅读方便
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1&&!l2) return nullptr;
ListNode* preHead = new ListNode;
ListNode* prev = preHead;
while(l1&&l2)
{
if(l1->val <l2->val)
{
prev->next = l1;
l1 = l1->next;
}
else
{
prev->next = l2;
l2 = l2->next;
}
prev = prev->next;
}
prev->next = l1 == nullptr ? l2 : l1;
return preHead->next;
}
};
如果使用递归,我们该怎么处理?
我们看一下原链表被破坏后,新的链表是如何组成的
小的部分,作为新链表的起点,我们开程序:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(!l1) return l2;
else if(!l2) 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;}
}
};
8.3灵光乍现
以下题解都比较综合,结题方法也都非常多变和灵活,不是很好归类,故放在此处。
面试题35. 复杂链表的复制:https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/
将新结点放在原始结点的后面,第一次遍历赋值内容和插入新结点。
第二次遍历,辅助结点的复杂内容。这个部分一定要注意,在分离新旧链表之前,就要把random指针处理好,因为分离之后,就丢失了random指针的信息。
第三次遍历,分离新链表。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == nullptr) return nullptr;
//拷贝
Node*Cur = head;
while(Cur)
{
Node* Temp = new Node(Cur->val);
Node* Next = Cur->next;
Cur->next = Temp;
Temp->random = Cur->random;
Temp->next = Next;
Cur = Next;
}
//目前已经完成复制,现在分割,一定要注意随意,是指向自己的随机,不是以前老版本的随机
Node*NewHead = new Node(INT_MIN);
Node*NewPre = nullptr;
Cur = head;
while(Cur)
{
Node* Another = Cur->next;
if(Cur->random) Another->random = Cur->random->next;
Cur = Another->next;
}
Cur = head;
while(Cur)
{
Node* Another = Cur->next;
if(NewHead->val == INT_MIN) {NewHead = Another;}
if(NewPre == nullptr) NewPre = Another;
else {NewPre->next = Another;NewPre = Another;}
// cout<<Cur->random<<" "<<Another->random<<endl;
// if(Cur->random) Another->random = Cur->random->next;
Cur->next = Another->next;//恢复原来的样子
Cur = Another->next;
Another->next = nullptr;
}
return NewHead;
}
};
重排链表 https://leetcode-cn.com/problems/reorder-list/
不使用额外的空间,此题如何下手?找规律,不难发现,尾结点都是链表的中间结点,找中间结点岂不容易。
然后发现,中间结点后面的结点,都按照顺序插在前半段链表的结点之间。
那么第一步:找中间结点
第二步:反转后半段链表
第三步:将后半段结点插入到前半段中
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void reorderList(ListNode* head) {
if(head == nullptr) return;
stack<int> Back;
ListNode* slow = head;
ListNode* fast = head;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
while(slow)
{
ListNode* Temp = slow->next;
slow->next = nullptr;
slow = Temp;
if(slow) Back.push(slow->val);
}
slow = head;
while(slow&&Back.size())
{
ListNode* Temp= new ListNode(Back.top());
Back.pop();
ListNode* Next = slow->next;
slow->next = Temp;
Temp->next = Next;
slow = Next;
}
return;
}
};
本题更多的是一道前缀和问题
class Solution {
public:
ListNode* removeZeroSumSublists(ListNode* head) {
if(head == nullptr)
return nullptr;
// 保存前缀和
unordered_map<int, ListNode*> prefixSum;
// 因为头结点也有可能会被消掉,所以这里加一个虚拟节点作为头结点
ListNode* dummy = new ListNode(0), *p = dummy;
dummy->next = head;
prefixSum[0] = p;
int cur = 0, tempCur = 0;
while (p = p->next) {
cur += p->val;
if (prefixSum.find(cur) != prefixSum.end()) {
ListNode* temp = prefixSum[cur]->next;
prefixSum[cur]->next = p->next;
tempCur = cur;
// 还需要从 map 中删除消除区间的前缀和
while (temp != p) {
tempCur += temp->val;
prefixSum.erase(tempCur);
temp = temp->next;
}
} else {
prefixSum[cur] = p;
}
}
return dummy->next;
}
};
合并两个链表的经验我们有,那么n个也是一样的,我们两个两个合并,如图所示:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return nullptr;
int size = lists.size();
if(size == 1) return lists[0];
//两个两个合并
int begin = 0;
ListNode* Two = new ListNode();
while(size>1&&begin<size)
{
if(begin == 0) {Two = List(lists[0],lists[1]);begin+=2;}
else {Two = List(Two,lists[begin]);begin++;}
}
return Two;
}
ListNode* List(ListNode*& L1,ListNode*& L2)
{
if(!L1&&!L2) return nullptr;
ListNode* head = nullptr;
ListNode* Pre = head;
while(L1&&L2)
{
ListNode* Temp = new ListNode( );
if(L1->val<=L2->val)
{
Temp->val = L1->val;
L1 = L1->next;
}
else
{
Temp->val = L2->val;
L2 = L2->next;
}
if(head == nullptr) head = Temp;
if(Pre == nullptr) Pre = Temp;
else {Pre->next = Temp;Pre = Temp;}
}
ListNode* After = L1 == nullptr?L2:L1;
while(After)
{
ListNode* Temp = new ListNode(After->val);
if(head == nullptr) head = Temp;
if(Pre == nullptr) Pre = Temp;
else {Pre->next = Temp;Pre = Temp;}
After = After->next;
}
return head!=nullptr?head:nullptr;
}
};
显示太浪费了,我们采用典型的分治法的思想:
那么我们程序要怎么写呢?利用递归,非常巧妙
ListNode* merge(vector <ListNode*> &lists, int l, int r)
{
if (l == r) return lists[l];
if (l > r) return nullptr;
int mid = l+(r - l)/2;
return List(merge(lists, l, mid), merge(lists, mid + 1, r));
}
当起点终点一样的时候,我们返回原值,起点大于终点,我们返回空,下面我们看看这个递归过程是怎样的
优化时间,分治法完整代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return nullptr;
int size = lists.size();
if(size == 1) return lists[0];
return merge(lists, 0, size - 1);
}
ListNode* merge(vector <ListNode*> &lists, int l, int r)
{
if (l == r) return lists[l];
if (l > r) return nullptr;
int mid = l+(r - l)/2;
return List(merge(lists, l, mid), merge(lists, mid + 1, r));
}
ListNode* List(ListNode* L1,ListNode* L2)
{
if(!L1&&!L2) return nullptr;
ListNode* head = nullptr;
ListNode* Pre = head;
while(L1&&L2)
{
ListNode* Temp = new ListNode( );
if(L1->val<=L2->val)
{
Temp->val = L1->val;
L1 = L1->next;
}
else
{
Temp->val = L2->val;
L2 = L2->next;
}
if(head == nullptr) head = Temp;
if(Pre == nullptr) Pre = Temp;
else {Pre->next = Temp;Pre = Temp;}
}
// cout<<head<<endl;
ListNode* After = L1 == nullptr?L2:L1;
// cout<<After->val<<endl;
while(After)
{
ListNode* Temp = new ListNode(After->val);
if(head == nullptr) head = Temp;
if(Pre == nullptr) Pre = Temp;
else {Pre->next = Temp;Pre = Temp;}
After = After->next;
}
return head!=nullptr?head:nullptr;
}
};
但是每次都要分配空间,实在是不划算,我们原地合并两个链表:
迭代/递归:
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.empty()) return nullptr;
int size = lists.size();
if(size == 1) return lists[0];
return merge(lists, 0, size - 1);
}
ListNode* merge(vector <ListNode*> &lists, int l, int r)
{
if (l == r) return lists[l];
if (l > r) return nullptr;
int mid = l+(r - l)/2;
return List(merge(lists, l, mid), merge(lists, mid + 1, r));
}
//递归
ListNode* List(ListNode* l1,ListNode* l2)
{
if(!l1) return l2;
else if(!l2) return l1;
else if(l1->val<l2->val) {l1->next = List(l1->next,l2);return l1;}
else {l2->next = List(l1,l2->next);return l2;}
}
//迭代
ListNode* List(ListNode* l1,ListNode* l2)
{
if(!l1||!l2) return (l1 == nullptr)?l2:l1;
ListNode NewHead;
ListNode* Pre = &NewHead;
while(l1&&l2)
{
if(l1->val<l2->val) {Pre->next = l1;l1 = l1->next;}
else {Pre->next = l2;l2 = l2->next;}
Pre = Pre->next;
}
Pre->next = (l1 == nullptr)?l2:l1;
return NewHead.next;
}
};
还有更好的办法吗?当然有,使用优先队列:priority_queue <Status> q;
class Solution {
public:
struct Status {
int val;
ListNode *ptr;
bool operator < (const Status &rhs) const {
return val > rhs.val;
}
};
priority_queue <Status> q;
ListNode* mergeKLists(vector<ListNode*>& lists) {
for (auto node: lists) {
if (node) q.push({node->val, node});
}
ListNode head, *tail = &head;
while (!q.empty()) {
auto f = q.top(); q.pop();
tail->next = f.ptr;
tail = tail->next;
if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
}
return head.next;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists/solution/he-bing-kge-pai-xu-lian-biao-by-leetcode-solutio-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
双向链表
struct DLinkedNode {
int key, value;
DLinkedNode* prev;//每个结点有两个指针,一个指向前,一个指向后
DLinkedNode* next;
//默认构造函数
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
//构造函数
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
public:
//本题的核心意图是,链表头结点的地方,是经常访问的字段,链表的尾部是不经常访问的字段
//利用Hash去快速定位链表中的点,以此达到O(1)
unordered_map<int, DLinkedNode*> cache;//保留key值和链表结点
DLinkedNode* head;//链表的虚拟头结点
DLinkedNode* tail;//链表的虚拟尾结点
int Size;//时刻统计链表的长度
int Capacity;//真实的容器大小
LRUCache(int capacity) {
Capacity = capacity;//容器大小赋值
Size = 0;//目前链表额大小
//设置虚拟头结点和尾结点
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
}
int get(int key) {
if (!cache.count(key)) {//没有找到
return -1;
}
// 如果 key 存在,先通过哈希表定位O(1),再移到头部
DLinkedNode* node = cache[key];
moveToHead(node);//一旦有访问,就移动到链表的首部,表示已经访问,更新访问字段
return node->value;
}
void put(int key, int value) {
if (!cache.count(key)){
DLinkedNode* node = new DLinkedNode(key, value);
cache[key] = node;
addToHead(node);
++Size;
if (Size > Capacity){
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode* removed = removeTail();//删除尾部结点
// 删除哈希表中对应的项
cache.erase(removed->key);
delete removed;
--Size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
//操作双向链表的相应内容
//添加到头部
void addToHead(DLinkedNode* node) {
if(node == nullptr) return;
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
//移除结点
void removeNode(DLinkedNode* node) {
if(node == nullptr) return;
node->prev->next = node->next;
node->next->prev = node->prev;
}
//移除尾部结点
DLinkedNode* removeTail() {
DLinkedNode* node = tail->prev;
removeNode(node);
return node;
}
//首部增加结点
void moveToHead(DLinkedNode* node) {
removeNode(node);
addToHead(node);
}
};