链表⛓
1. 合并两个有序链表🥑
👽思路解析:
- 双指针
p1
p2
分别指向两个链表头结点 - 创建新链表指针
p
维护(技巧:虚拟头结点dummy
) - 遍历两个链表,较小的结点优先给
p
指针,终止条件存在链表(任意一个或两个都)遍历完成 - 判断
p1
p2
是否非空,非空则追加给p
指针
/**
* 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* list1, ListNode* list2) {
// 虚拟头结点
ListNode* dummy = new ListNode(-1);
ListNode* p = dummy;
ListNode* p1 = list1;
ListNode* p2 = list2;
while(p1!=nullptr && p2!=nullptr){
// 比较 p1 和 p2 两个指针
// 将值较小的的节点接到 p 指针
if (p1->val <= p2->val){
p->next = p1;
p1 = p1->next;
}
else{
p->next = p2;
p2 = p2->next;
}
p = p->next;
}
if (p1!=nullptr){
p->next = p1;
}
if (p2!=nullptr){
p->next = p2;
}
return dummy->next;
}
};
- 「虚拟头结点」技巧,也就是
dummy
节点 :
如果不使用dummy
虚拟节点,代码会复杂很多,而有了dummy
节点这个占位符,可以避免处理空指针的情况,降低代码的复杂性。
当你需要创造一条新链表的时候,可以使用虚拟头结点简化边界情况的处理。
2. 链表的分解🥓
👽思路解析:
p
指针遍历原链表,比较分区,终止条件原链表遍历完成- 双指针
p1
p2
分别维护两个分区新链表(技巧:虚拟头结点dummy1
dummy2
) - 断开原链表中每个节点
/**
* 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* partition(ListNode* head, int x) {
// 存放小于 x 的链表的虚拟头结点
ListNode* dummy1 = new ListNode(-1);
// 存放大于等于 x 的链表的虚拟头结点
ListNode* dummy2 = new ListNode(-1);
// p1, p2 指针负责生成结果链表
ListNode* p1 = dummy1;
ListNode* p2 = dummy2;
// p 负责遍历原链表,类似合并两个有序链表的逻辑
// 这里是将一个链表分解成两个链表
ListNode* p = head;
while (p!=nullptr){
if (p->val < x){
p1->next = p;
p1 = p1->next;
}
else{
p2->next = p;
p2 = p2->next;
}
// 断开原链表中的每个节点的 next 指针
ListNode* tmp = p->next;
p->next = nullptr;
p = tmp;
}
// 连接两个链表
p1->next = dummy2->next;
return dummy1->next;
}
};
3. 合并 k 个有序链表🍒
👽思路解析:
- 创建新链表指针
p
维护(技巧:虚拟头结点dummy
) - 优先级队列 priority_queue (最小堆)实现较小节点优先给
p
指针
/**
* 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* mergeKLists(vector<ListNode*>& lists) {
if (lists.size() == 0) return nullptr;
// 虚拟头结点
ListNode* dummy = new ListNode(-1);
ListNode* p = dummy;
// 优先级队列,最小堆
auto cmp = [](ListNode* a, ListNode* b) { return (a->val) > (b->val); };
priority_queue < ListNode*, vector<ListNode*>,decltype(cmp)> pq(cmp);
// 将 k 个链表的头结点加入最小堆
for (ListNode* head : lists) {
if (head != nullptr)
pq.push(head);
}
while (!pq.empty()) {
// 获取最小节点,接到结果链表中
ListNode* node = pq.top();
pq.pop();
p->next = node;
if (node->next != nullptr) {
pq.push(node->next);
}
// p 指针不断前进
p = p->next;
}
return dummy->next;
}
};
- 优先队列
pq
中的元素个数最多是k
,所以一次pop
或者push
方法的时间复杂度是O(logk)
;
所有的链表节点都会被加入和弹出pq
,所以算法整体的时间复杂度是O(Nlogk)
,其中k
是链表的条数,N
是这些链表的节点总数。
4. 链表的倒数第 k 个节点🍩
先让一个指针 p1
指向链表的头节点 head
,然后走 k
步
再用一个指针 p2
指向链表头节点 head
让 p1
和 p2
同时向前走,p1
走到链表末尾的空指针时前进了 n - k
步,p2
也从 head
开始前进了 n - k
步,停留在第 n - k + 1
个节点上,即恰好停链表的倒数第 k
个节点上
👽思路解析:
- 封装>>>链表的倒数第 k 个节点方法
- 建立虚拟头结点
- 找到倒数第
n+1
个节点,删除第n
个节点
/**
* 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* FindNthFromEnd(ListNode* head, int n) {
ListNode* p1 = head;
// p1 先走 k 步
for(int i=0;i<n;i++){
p1 = p1->next;
}
ListNode* p2 = head;
// p1 和 p2 同时走 n - k 步
while(p1!=nullptr){
p1 = p1->next;
p2 = p2->next;
}
// p2 现在指向第 n - k + 1 个节点,即倒数第 k 个节点
return p2;
}
ListNode* removeNthFromEnd(ListNode* head, int n) {
// 虚拟头结点
ListNode* dummy = new ListNode(-1);
dummy->next = head;
// 删除倒数第 n 个,要先找倒数第 n + 1 个节点
ListNode* x = FindNthFromEnd(dummy, n+1);
// 删掉倒数第 n 个节点
x->next = x->next->next;
return dummy->next;
}
};
- 虚拟头结点技巧,防止删除头结点时出错,因为需要找到倒数第
n+1
个节点
5. 链表的中点🥚
👽思路解析:
【🎫】 快慢指针
每当慢指针 slow
前进一步,快指针 fast
就前进两步,这样,当 fast
走到链表末尾时,slow
就指向了链表中点
/**
* 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* middleNode(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=nullptr && fast->next!=nullptr){
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
- 当链表长度为偶数时,返回中点两个节点靠后的节点
6. 链表是否包含环🍟
👽思路解析:
【🎫】 快慢指针
每当慢指针 slow
前进一步,快指针 fast
就前进两步
如果 fast
最终遇到空指针,说明链表中没有环
如果 fast
最终和 slow
相遇,那肯定是 fast
超过了 slow
一圈,说明链表中含有环
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=nullptr && fast->next!=nullptr){
fast = fast->next->next;
slow = slow->next;
// 快慢指针相遇,说明含有环
if (fast==slow)
return true;
}
return false;
}
};
👽思路解析:
【🎫】 快慢指针
当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=nullptr && fast->next!=nullptr){
fast = fast->next->next;
slow = slow->next;
// 快慢指针相遇,说明含有环
if (fast==slow)
break;
}
if (fast==nullptr || fast->next==nullptr){
return nullptr;
}
// 重新指向头结点
slow = head;
// 快慢指针同步前进,相交点就是环起点
while(fast!=slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
7. 链表是否相交🍏
👽思路解析:
【📌】双指针
让 p1
遍历完链表 A
之后开始遍历链表 B
,让 p2
遍历完链表 B
之后开始遍历链表 A
最后让 p1
和 p2
能够同时到达相交节点 c1
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode* p1 = headA;
ListNode* p2 = headB;
while(p1!=p2){
if (p1==nullptr)
p1 = headB;
else
p1 = p1->next;
if (p2==nullptr)
p2 = headA;
else
p2 = p2->next;
}
return p1;
}
};
- 链表不相交最后会停止在"拼接"链表尾部
nullptr
,包含返回null
的情况
✍️ 小方块xfk
📅 写于 2023年1月