算法-链表

算法题-链表相关

链表

  链表众多操作可以分解为几个基础操作:反转链表,链表删除,21. 合并两个有序链表 - 力扣(Leetcode)

  借用链表考察数据结构:实现LRU 使用双链表+哈希表实现

  链表实现整数和:++2. 两数相加 - 力扣(LeetCode)

  由基础操作衍生出来的题目:((20230615144645-vetevju ‘重排链表’)) 包括反转与合并

反转链表

使用双指针来进行反转,pre和cur指针

据此可以写出代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 * 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* reverseList(ListNode* head) {
ListNode* tmp=nullptr;
ListNode* pre=nullptr;
ListNode* cur=head;
while(cur){
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
};

  ‍

链表内指定区间反转_牛客题霸_牛客网 (nowcoder.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode* dummy=new ListNode(0);
dummy->next=head;
ListNode* pre=nullptr;//它后面需要拼上反转后的区间
ListNode* cur=dummy;
//先定位到要反转的区间的开始
for(int i=0;i<m;i++){
pre=cur;
cur=cur->next;
}
//反转区间中的
ListNode*curPre=nullptr,*tmp=nullptr;
ListNode*after=cur;//它后面需要拼上区间后的链表
for(int i=0;i<n-m+1;i++){
tmp=cur->next;
cur->next=curPre;
curPre=cur;
cur=tmp;
}
//反转后区间链表的开头是curPre
//下一个待拼上的开头是cur
pre->next=curPre;
after->next=cur;
return dummy->next;
}

++链表中的节点每k个一组翻转_牛客题霸_牛客网 (nowcoder.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ListNode* reverseKGroup(ListNode* head, int k) {
// write code here
ListNode* tail=head; //每次翻转的尾部,下一次的头部
for(int i=0;i<k;i++){
if(tail==nullptr) return head;
tail=tail->next;
}
ListNode* pre=nullptr;
ListNode* cur=head;
while(cur!=tail){
ListNode* tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
head->next=reverseKGroup(tail,k);
return pre;
}

链表删除

删除倒数第n个节点 19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

注意点:

  1. 采用虚拟头节点
  2. 快慢指针,双指针的思想快慢指针移除元素 面试题 02.07. 链表相交 - 力扣(LeetCode) 142. 环形链表 II - 力扣(LeetCode)

删除有序链表中重复的元素-II_牛客题霸_牛客网 (nowcoder.com)

给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ListNode* deleteDuplicates(ListNode* head) {
// write code here
ListNode* cur=head;
ListNode* dummyNode=new ListNode(10001);
dummyNode->next=head;
ListNode* pre=dummyNode;
while(cur&&cur->next){
bool flag=false;
while(cur->val==cur->next->val&&cur->next){
flag=true;
ListNode* tmp=cur->next;
cur->next=cur->next->next;
delete tmp;
}
if(flag) {
pre->next=cur->next;
ListNode* tmp=cur;
cur=pre->next;
delete tmp;

}else{
pre=cur;
cur=cur->next;
}
}
return dummyNode->next;
}

链表合并

合并k个已排序的链表_牛客题霸_牛客网 (nowcoder.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 struct CompareNodes {
bool operator()(const ListNode* a, const ListNode* b) {
return a->val > b->val; // ">" for ascending order
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*,vector<ListNode*>,CompareNodes> pq;
for(auto list:lists){
if(list)
pq.emplace(list);
}
ListNode* dummy=new ListNode(0);
ListNode* tail=dummy;
while(!pq.empty()){
auto cur=pq.top();
pq.pop();
tail->next=cur;
tail=cur;
if(cur->next){
pq.emplace(cur->next);
}
}
return dummy->next;
}

  ‍

另一种解法,采用递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public:
ListNode *mergeKLists(vector<ListNode *> &lists) {
if(lists.size() == 0)
return nullptr;
ListNode* head = nullptr;
for(int i = 0; i < lists.size(); ++i){
head = connectList(head, lists[i]);
}
return head;
}

ListNode* connectList(ListNode* p1, ListNode* p2){ //两两比较
if(p1 == nullptr || p2 == nullptr)
return p1 == nullptr ? p2 : p1;
if(p1->val < p2->val){
p1->next = connectList(p1->next, p2);
return p1;
}
else{
p2->next = connectList(p1, p2->next);
return p2;
}
}
};

  由此方法退出单纯地合并两个链表其实递归就行:

  比如合并两个有序递增链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
if(pHead1==nullptr&&pHead2==nullptr) return nullptr;
if(pHead1==nullptr||pHead2==nullptr){
return pHead1==nullptr?pHead2:pHead1;
}
if(pHead1->val<pHead2->val){
pHead1->next=Merge(pHead1->next, pHead2);
return pHead1;
}
else{
pHead2->next=Merge(pHead1,pHead2->next);
return pHead2;
}
return nullptr;
}

链表求交

两个链表的第一个公共结点_牛客题霸_牛客网 (nowcoder.com)

  • 有公共节点的时候,N1和N2必会相遇,因为长度一样嘛,速度也一定,必会走到相同的地方的,所以当两者相等的时候,则会第一个公共的节点
  • 无公共节点的时候,此时N1和N2则都会走到终点,那么他们此时都是null,所以也算是相等了。

  下面看个动态图,可以更形象的表示这个过程~

  ​36

  代码:

1
2
3
4
5
6
7
8
  ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode* p1=pHead1,*p2=pHead2;
while(p1!=p2){
p1=(p1==nullptr)?pHead2:p1->next;
p2=(p2==nullptr)?pHead1:p2->next;
}
return p1;
}

重排链表

143. 重排链表 - 力扣(Leetcode)

  ​image

  ‍

  这题卡住的地方在于合并链表处 如何合并到原来的链表里

  两个链表l1,l2合并 返回l1

1
2
3
4
5
6
7
8
9
10
11
12
void mergeList(ListNode* l1,ListNode* l2){
ListNode *tmp1,*tmp2;
while(l1&&l2){
tmp1=l1->next;
tmp2=l2->next;

l1->next=l2;
l1=tmp1;
l2->next=l1;
l2=tmp2;
}
}

  完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public:
void reorderList(ListNode* head) {
ListNode* slowIndex=head,*fastIndex=head;
while(fastIndex&&fastIndex->next){
slowIndex=slowIndex->next;
fastIndex=fastIndex->next->next;
}
ListNode* l1=head;
ListNode* l2=slowIndex->next;
slowIndex->next=nullptr;
l2=reverseList(l2);
mergeList(l1, l2);
}
ListNode* reverseList(ListNode* root){
ListNode* pre=nullptr,*cur=root;
ListNode* tmp;
while(cur){
tmp=cur->next;
cur->next=pre;
pre=cur;
cur=tmp;
}
return pre;
}
void mergeList(ListNode* l1,ListNode* l2){
ListNode *tmp1,*tmp2;
while(l1&&l2){
tmp1=l1->next;
tmp2=l2->next;

l1->next=l2;
l1=tmp1;
l2->next=l1;
l2=tmp2;
}
}
};

  ‍

  ‍

题目总结

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummpyHead=new ListNode(0);
dummpyHead->next=head;
ListNode *slow=dummpyHead,*fast=dummpyHead;
while(n--&&fast!=nullptr) fast=fast->next;
fast=fast->next;
while(fast!=nullptr){
slow=slow->next;
fast=fast->next;
}
slow->next=slow->next->next;
return dummpyHead->next;
}
};

  ‍

面试题 02.07. 链表相交 - 力扣(LeetCode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA=0,lenB=0;
ListNode *curA=headA,*curB=headB;
while(curA){
curA=curA->next;
lenA++;
}
while(curB){
curB=curB->next;
lenB++;
}
int len=abs(lenA-lenB);
if(lenA<lenB) {
swap(headA,headB);
swap(lenA,lenB);
}
curA=headA;
curB=headB;
while(len--) curA=curA->next;
while(curA&&curB){
if(curA==curB) return curA;
curA=curA->next;
curB=curB->next;
}
return NULL;

}
};

  ‍

142. 环形链表 II - 力扣(LeetCode)

思路: 定义快慢指针 fast一次走两步,slow一次走一步 然后相遇就是有环

有环后 从head处和相遇处分别再开始双指针移动 相遇点就是环的入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode *slow=head,*fast=head;
bool flag=false;
while(fast&&fast->next){
fast=fast->next->next;
slow=slow->next;
if(fast==slow){
flag=true;
break;
}
}
if(flag==false) return NULL;
ListNode *start1=head;
while(start1!=slow){
start1=start1->next;
slow=slow->next;
}
return slow;
}
};

++2. 两数相加 - 力扣(LeetCode)

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int left=0;
ListNode* root1=l1;
ListNode* root2=l2;
ListNode* pre=nullptr;
while(root1!=nullptr&&root2!=nullptr){
root1->val+=(root2->val+left);
left=root1->val/10;
root1->val%=10;
pre=root1;
root1=root1->next;
root2=root2->next;
}
if(root2!=nullptr){
pre->next=root2;
root1=pre->next;
}
while(root1!=nullptr&&left!=0){
root1->val+=left;
left=root1->val/10;
root1->val%=10;
pre=root1;
root1=root1->next;
}
if(left>0){
ListNode* node=new ListNode(left);
pre->next=node;
}

return l1;
}
};

  ‍

21. 合并两个有序链表 - 力扣(Leetcode)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummpyHead=new ListNode(0);
ListNode* root=dummpyHead;
while(list1&&list2){
if(list1->val<list2->val)
{
dummpyHead->next=list1;
list1=list1->next;
}else{
dummpyHead->next=list2;
list2=list2->next;
}
dummpyHead=dummpyHead->next;
}
if(list1){
dummpyHead->next=list1;
}
else if(list2){
dummpyHead->next=list2;
}
return root->next;
}
};

  ‍

  ‍


算法-链表
https://shanhainanhua.github.io/2023/08/25/算法-链表/
作者
wantong
发布于
2023年8月25日
许可协议