📝前言说明:
- 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录,按专题划分
- 每题主要记录:(1)本人解法 + 本人屎山代码;(2)优质解法 + 优质代码;(3)精益求精,更好的解法和独特的思想(如果有的话)
- 文章中的理解仅为个人理解。如有错误,感谢纠错
🎬个人简介:努力学习ing
📋本专栏:C++刷题专栏
📋其他专栏:C语言入门基础,python入门基础,C++学习笔记,Linux
🎀CSDN主页 愚润泽
你可以点击下方链接,进行该专题内不同子专题的学习
| 点击链接 | 开始学习 |
|---|---|
| 双指针(1) | 双指针(2) |
| 双指针(3) | 双指针(4) |
| 滑动窗口(1) | 滑动窗口(2) |
| 滑动窗口(3) | 滑动窗口(4) |
| 二分查找(1) | 二分查找(2) |
| 前缀和(1) | 前缀和(2) |
| 前缀和(3) | 位运算(1) |
| 位运算(2) | 模拟算法 |
| 快速排序 | 归并排序 |
| 链表 | 哈希表 |
| 字符串 | 栈 |
| 队列 + 宽搜 | 优先级队列 |
| BFS 解决 FloodFill | BFS 解决最短路径 |
| 多源 BFS | BFS 解决拓扑排序 |
题单汇总链接:点击 → 题单汇总(暂时未整理,因为还没刷完)
链表常用技巧和操作
常用技巧:
- 画图模拟
- 使用虚拟头节点
- 不要吝啬空间,大胆去定义变量(可以让我们思路更清晰,且不会出现节点丢失问题)
- 快慢双指针
- 判环
- 找链表环的入口
- 找链表中的倒数第 n 个节点
常用操作:
- new 新节点
- 尾插
- 头插(插出来的是逆序的)
2. 两数相加
题目链接:https://leetcode.cn/problems/add-two-numbers/description/

个人解
屎山代码:
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
{
ListNode dummy;
// 哨兵节点,哨兵节点的 next 会被设置成第一个头结点
// 又因为设置一次以后,dummy->next就不会被改变了,所以dummy.next始终是链表的头结点
// 让 cur 直接等于哨兵节点,统一操作,在第一次设置 cur->next的时候设置的就是 dummy ->next
ListNode* cur = &dummy;
int carry = 0;
while(l1 || l2 || carry)
{
if(l1)
{
carry += l1->val;
l1 = l1->next;
}
if(l2)
{
carry += l2->val;
l2 = l2->next;
}
cur->next = new ListNode(carry % 10);
cur = cur->next;
carry /= 10;
}
return dummy.next;
}
};
时间复杂度:
O
(
m
+
n
)
O(m + n)
O(m+n), m 和 n为两个链表的长度
空间复杂度:
O
(
m
a
x
(
m
,
n
)
)
O(max(m,n))
O(max(m,n))
24. 两两交换链表中的节点
题目链接:https://leetcode.cn/problems/swap-nodes-in-pairs/description/

个人解
思路:
- 注意,每一对交换完以后要连上后面的对
- 步骤一: 0->next = 2;
- 步骤二: 1->next = 2->next(也就是 3 );
- 步骤三: 2->next = 1;
- 然后: 1 的位置变成新的 0, 3 变成新的 1, 3->next 变成新的 2
屎山代码:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head || !head->next) return head;
ListNode dummy(0, head);
ListNode* cur0 = &dummy;
ListNode* cur1 = head;
while(cur1 && cur1->next)
{
ListNode* cur2 = cur1->next;
// 交换
cur0->next = cur2;
cur1->next = cur2->next;
cur2->next = cur1;
// 更新 cur0 和 cur1
cur0 = cur1;
cur1 = cur1->next;
}
return dummy.next;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
143. 重排链表
题目链接:https://leetcode.cn/problems/reorder-list/description/

优质解
代码
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
// 反转一个链表需要 pre, cur, nxt 三个节点
ListNode* pre = nullptr, *cur = head;
while(cur)
{
ListNode* nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre; // pre 指向反转后链表的头结点
}
ListNode* FindMid(ListNode* head) // 后半部分可能更长
{
ListNode* p1 = head, * p2 = head;
while(p2 && p2->next)
{
p1 = p1->next;
p2 = p2->next->next;
}
return p1;
}
void reorderList(ListNode* head)
{
// 1. 找到中间节点
ListNode* mid = FindMid(head);
// 2. 反转后半部分链表
ListNode* head2 = reverseList(mid);
// 3. 在把后半部分链表插入前面的链表
while(head2->next) // 为什么不是 head2 (因为后半部分更长,而原来的前半部分的最后一个节点的next指针:刚好指向的是后半部分反转以后的最后一个节点,因此相当于已经把"长"出来那一部分插入了)
{
ListNode* nxt1 = head->next;
ListNode* nxt2 = head2->next;
head2->next = head->next;
head->next = head2;
head = nxt1;
head2 = nxt2;
}
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
23. 合并 K 个升序链表
题目链接:https://leetcode.cn/problems/merge-k-sorted-lists/description/

优质解
思路:
- 使用堆
priority_queue(默认是大根堆),我们改成小根堆,然后每次pop出一个节点 - 如果插入
priority_queue的是节点,并且还要是小根堆,则我们要自己实现比较器(底层return a > b) - 注意,题目中
lists存储的是各个链表的头结点(因为链表是由头结点组织的) - 解题思路:
- 由于所有链表都是升序的,所以我们可以先把所有链表的头结点加入堆(把元素插入
pq的时候会自动调整堆),然后找里面的最小值作为头结点 - 那么下一个节点有两种选择:要么是其他链表的节点(已经在堆中),要么还是当前链表的下一个节点(因为不知道下一个节点和其他链表节点的大小关系)
- 由于所有链表都是升序的,所以我们可以先把所有链表的头结点加入堆(把元素插入
代码:
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists)
{
auto cmp = [](ListNode* a, ListNode* b){
return a->val > b->val;
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> pq;
// 先加入所有链表的头结点
for(auto head: lists)
{
if(head) // 可能会有空链表
pq.push(head);
}
ListNode dummy; // 哨兵节点(创建对象-> 能分配内存)
ListNode* cur = &dummy;
while(!pq.empty())
{
auto mini = pq.top();
pq.pop();
cur->next = mini;
cur = cur->next;
if(mini->next) // 如果当前的链表后面还有节点
pq.push(mini->next);
}
return dummy.next;
}
};
时间复杂度: O ( N ∗ l o g K ) O (N*logK) O(N∗logK),
- N 是所有链表的节点总数,K 是链表数量(也是堆里面的节点数量),节点插入删除调整堆的时间复杂度是 O ( l o g K ) O(logK) O(logK)
空间复杂度: O ( k ) O(k) O(k)
注意:
priority_queue的原型:
template<
class T, // 元素类型
class Container = vector<T>, // 底层容器
class Compare = less<T> // 比较器类型
> class priority_queue;
- 比较器
Compare:
auto cmp = [](ListNode* a, ListNode* b){
return a->val > b->val;
}
底层提供a > b实现的是小根堆。(默认的是a < b大根堆)
decltype(cmp)的decltype可以自动推导类型
25. K 个一组翻转链表
题目链接:https://leetcode.cn/problems/reverse-nodes-in-k-group/description/

优质解
思路:
// 一个链表反转后 pre 指向头结点,cur 指向下一个要反转的链表的头结点
// 每个区间的链表我们可以翻转了,怎么链接起来呢?
// 我们可以多创建一个节点,用来将每个区间反转后的链表进行链接
// 创建一个 p0 始终指向上一个链表的末尾, 且 p0->next 记录了下一个链表反转前的起点
// 上一个链表的链表尾(p0) 要链接 反转后链表的链表头 pre
// 反转后链表的链表尾(p0->next, 原来的链表头) 要链接 下一个链表的链表头(cur)
代码:
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* count = head;
int n = 0;
while(count != nullptr)
{
n++;
count = count -> next;
}
ListNode dummy(0, head);
ListNode* p0 = &dummy; // 第一次修改时直接设置 dummy, dummy 跟随 p0
ListNode* pre = nullptr;
ListNode* cur = head;
for(int i = 0; i < n / k; i++) // 总共要进行 n/k 次,区间长为 k 的链表的反转
{
for(int j = 0; j < k; j++) // 一组内反转
{
ListNode* nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
ListNode* p0_nxt = p0->next; // 反转后链表的末尾,用于 p0 更新
p0->next->next = cur;
p0->next = pre;
p0 = p0_nxt;
}
return dummy.next;
}
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!


695

被折叠的 条评论
为什么被折叠?



