链表(典型算法思想)—— OJ例题算法解析思路
目录
一、2. 两数相加 - 力扣(LeetCode)
算法代码:
1. 初始化
2. 遍历链表并相加
3. 返回结果
举例说明
二、24. 两两交换链表中的节点 - 力扣(LeetCode)
算法代码:
代码思路
举例说明
初始状态
第一次交换
第二次交换
最终结果
三、143. 重排链表 - 力扣(LeetCode)
算法代码:
代码思路
举例说明
初始状态
1. 找到中间节点
2. 逆序后半部分链表
3. 合并两个链表
最终结果
代码总结
执行步骤总结
四、23. 合并 K 个升序链表 - 力扣(LeetCode)
解法一:算法代码(利用堆)
代码思路
举例说明
初始状态
执行过程
最终结果
代码总结
执行步骤总结
解法二:算法代码(递归/分治)
代码分析
1. 类的结构
2. 主函数 - mergeKLists
3. 分治法 - merge
4. 平分数组与递归
5. 合并两个有序链表 - mergeTowList
6. 合并过程
7. 合并逻辑
8. 处理剩余节点
举例说明
合并过程
五、25. K 个一组翻转链表 - 力扣(LeetCode)
算法代码:
代码分析
1. 链表节点结构定义
2. 主函数 - reverseKGroup
3. 逆序过程
4. 处理不需要翻转的部分
举例说明
处理过程
总结
一、2. 两数相加 - 力扣(LeetCode)

算法代码:
/*** 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* addTwoNumbers(ListNode* l1, ListNode* l2) {ListNode *cur1 = l1, *cur2 = l2;ListNode* newhead = new ListNode(0); // 创建⼀个虚拟头结点,记录最终结果ListNode* prev = newhead; // 尾指针int t = 0; // 记录进位while (cur1 || cur2 || t) {// 先加上第⼀个链表if (cur1) {t += cur1->val;cur1 = cur1->next;}// 加上第⼆个链表if (cur2) {t += cur2->val;cur2 = cur2->next;}prev->next = new ListNode(t % 10);prev = prev->next;t /= 10;}prev = newhead->next;delete newhead;return prev;}
};

1. 初始化
-
创建两个指针
cur1和cur2,分别指向两个链表的头节点l1和l2。 -
创建一个虚拟头节点
newhead,用于记录最终的结果链表。这个虚拟头节点的作用是简化链表操作,避免处理头节点的特殊情况。 -
创建一个尾指针
prev,指向当前结果链表的最后一个节点。 -
初始化一个变量
t,用于记录进位。
2. 遍历链表并相加
-
使用
while循环遍历两个链表,直到两个链表都遍历完且没有进位为止。 -
在循环中:
-
如果
cur1不为空,将cur1的值加到t上,并将cur1指向下一个节点。 -
如果
cur2不为空,将cur2的值加到t上,并将cur2指向下一个节点。 -
将
t % 10的值作为新节点的值,并将其添加到结果链表的末尾。 -
更新
prev指针,使其指向新添加的节点。 -
更新
t为t / 10,即计算进位。
-
3. 返回结果
-
循环结束后,结果链表的头节点是
newhead->next。 -
删除虚拟头节点
newhead,避免内存泄漏。 -
返回结果链表的头节点。
举例说明
假设有两个链表 l1 和 l2,分别表示数字 342 和 465,即:
-
l1: 2 -> 4 -> 3 -
l2: 5 -> 6 -> 4
按照代码的逻辑,相加过程如下:
-
初始化:
-
cur1指向2,cur2指向5。 -
newhead是一个虚拟头节点,prev指向newhead。 -
t = 0。
-
-
第一次循环:
-
t = 0 + 2 + 5 = 7。 -
创建新节点
7,prev->next指向7,prev指向7。 -
t = 7 / 10 = 0。 -
cur1指向4,cur2指向6。
-
-
第二次循环:
-
t = 0 + 4 + 6 = 10。 -
创建新节点
0,prev->next指向0,prev指向0。 -
t = 10 / 10 = 1。 -
cur1指向3,cur2指向4。
-
-
第三次循环:
-
t = 1 + 3 + 4 = 8。 -
创建新节点
8,prev->next指向8,prev指向8。 -
t = 8 / 10 = 0。 -
cur1和cur2都为nullptr,循环结束。
-
-
返回结果:
-
结果链表为
7 -> 0 -> 8,表示数字807,即342 + 465 = 807。
-
二、24. 两两交换链表中的节点 - 力扣(LeetCode)

算法代码:
/*** 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* swapPairs(ListNode* head) {if (head == nullptr || head->next == nullptr)return head;ListNode* newHead = new ListNode(0);newHead->next = head;ListNode *prev = newHead, *cur = prev->next, *next = cur->next,*nnext = next->next;while (cur && next) {// 交换结点prev->next = next;next->next = cur;cur->next = nnext;// 修改指针prev = cur; // 注意顺序cur = nnext;if (cur)next = cur->next;if (next)nnext = next->next;}cur = newHead->next;delete newHead;return cur;}
};
代码思路

-
检查边界条件:
-
如果链表为空(
head == nullptr)或只有一个节点(head->next == nullptr),直接返回head,因为不需要交换。
-
-
创建虚拟头节点:
-
创建一个虚拟头节点
newHead,并将其next指向链表的头节点head。虚拟头节点的作用是简化头节点的交换操作,避免特殊处理。
-
-
初始化指针:
-
prev:指向当前节点对的前一个节点(初始为newHead)。 -
cur:指向当前节点对中的第一个节点(初始为head)。 -
next:指向当前节点对中的第二个节点(初始为cur->next)。 -
nnext:指向next的下一个节点(初始为next->next)。
-
-
遍历链表并交换节点:
-
使用
while循环遍历链表,直到cur或next为空。 -
在循环中:
-
交换节点:
-
将
prev->next指向next。 -
将
next->next指向cur。 -
将
cur->next指向nnext。
-
-
更新指针:
-
将
prev指向cur(当前节点对中的第一个节点)。 -
将
cur指向nnext。 -
如果
cur不为空,将next指向cur->next。 -
如果
next不为空,将nnext指向next->next。
-
-
-
-
返回结果:
-
将
cur指向newHead->next(交换后的链表头节点)。 -
删除虚拟头节点
newHead,释放内存。 -
返回
cur(交换后的链表)。
-
举例说明
假设链表为 1 -> 2 -> 3 -> 4,交换过程如下:
初始状态
-
newHead -> 1 -> 2 -> 3 -> 4 -
prev = newHead -
cur = 1 -
next = 2 -
nnext = 3
第一次交换
-
交换节点:
-
prev->next指向2。 -
next->next指向1。 -
cur->next指向3。
-
-
更新指针:
-
prev = 1。 -
cur = 3。 -
next = 4。 -
nnext = nullptr(因为next->next为空)。
-
第二次交换
-
交换节点:
-
prev->next指向4。 -
next->next指向3。 -
cur->next指向nullptr。
-
-
更新指针:
-
prev = 3。 -
cur = nullptr(因为nnext为空)。 -
循环结束。
-
最终结果
-
交换后的链表为
2 -> 1 -> 4 -> 3。 -
删除虚拟头节点
newHead。 -
返回
2作为链表的头节点。
三、143. 重排链表 - 力扣(LeetCode)

算法代码:
/*** 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 || head->next == nullptr ||head->next->next == nullptr)return;// 1. 找到链表的中间节点 - 快慢双指针(⼀定要画图考虑 slow// 的落点在哪⾥)ListNode *slow = head, *fast = head;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;}// 2. 把 slow 后⾯的部分给逆序 - 头插法ListNode* head2 = new ListNode(0);ListNode* cur = slow->next;slow->next = nullptr; // 注意把两个链表给断开while (cur) {ListNode* next = cur->next;cur->next = head2->next;head2->next = cur;cur = next;}// 3. 合并两个链表 - 双指针ListNode* ret = new ListNode(0);ListNode* prev = ret;ListNode *cur1 = head, *cur2 = head2->next;while (cur1) {// 先放第⼀个链表prev->next = cur1;cur1 = cur1->next;prev = prev->next;// 再放第⼆个链表if (cur2) {prev->next = cur2;prev = prev->next;cur2 = cur2->next;}}delete head2;delete ret;}
};
代码思路

-
处理边界情况:
-
如果链表为空(
head == nullptr)、只有一个节点(head->next == nullptr)或只有两个节点(head->next->next == nullptr),直接返回,因为不需要重排。
-
-
找到链表的中间节点:
-
使用 快慢双指针:
-
slow指针每次移动一步。 -
fast指针每次移动两步。
-
-
当
fast到达链表末尾时,slow指向链表的中间节点。 -
将链表从中间节点断开,分为前半部分和后半部分。
-
-
逆序后半部分链表:
-
使用 头插法 将后半部分链表逆序:
-
创建一个虚拟头节点
head2。 -
遍历后半部分链表,将每个节点插入到
head2的后面。
-
-
最终,
head2->next指向逆序后的后半部分链表。
-
-
合并两个链表:
-
使用 双指针 将前半部分链表和逆序后的后半部分链表合并:
-
cur1指向前半部分链表的头节点。 -
cur2指向逆序后的后半部分链表的头节点。 -
依次交替连接两个链表的节点。
-
-
最终,链表被重排为所需顺序。
-
-
释放临时节点:
-
删除虚拟头节点
head2和ret,释放内存。
-
举例说明

假设链表为 1 -> 2 -> 3 -> 4 -> 5,重排过程如下:
初始状态
-
链表:
1 -> 2 -> 3 -> 4 -> 5。
1. 找到中间节点
-
slow指向3,fast指向5。 -
将链表从
3断开:-
前半部分:
1 -> 2 -> 3。 -
后半部分:
4 -> 5。
-
2. 逆序后半部分链表
-
逆序后的后半部分链表:
5 -> 4。
3. 合并两个链表
-
交替连接节点:
-
1 -> 5 -> 2 -> 4 -> 3。
-
最终结果
-
重排后的链表为
1 -> 5 -> 2 -> 4 -> 3。
代码总结
-
快慢双指针:
-
用于找到链表的中间节点,确保链表被正确分为两部分。
-
-
头插法逆序链表:
-
将后半部分链表逆序,便于后续合并。
-
-
双指针合并链表:
-
交替连接两个链表的节点,完成重排。
-
-
边界条件处理:
-
确保代码在链表节点数较少时也能正常运行。
-
执行步骤总结
-
找到中间节点并断开链表。
-
逆序后半部分链表。
-
合并前半部分和逆序后的后半部分链表。
-
释放临时节点,返回重排后的链表。
四、23. 合并 K 个升序链表 - 力扣(LeetCode)

解法一:算法代码(利用堆)
/*** 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 {struct cmp {bool operator()(const ListNode* l1, const ListNode* l2) {return l1->val > l2->val;}};public:ListNode* mergeKLists(vector<ListNode*>& lists) {// 创建⼀个⼩根堆priority_queue<ListNode*, vector<ListNode*>, cmp> heap;// 让所有的头结点进⼊⼩根堆for (auto l : lists)if (l)heap.push(l);// 合并 k 个有序链表ListNode* ret = new ListNode(0);ListNode* prev = ret;while (!heap.empty()) {ListNode* t = heap.top();heap.pop();prev->next = t;prev = t;if (t->next)heap.push(t->next);}prev = ret->next;delete ret;return prev;}
};
代码思路

-
定义小根堆的比较器:
-
使用
struct cmp定义一个小根堆的比较器,确保堆顶始终是最小的节点。
-
-
初始化小根堆:
-
创建一个优先队列
heap,用于存储所有链表的当前节点。 -
将所有链表的头节点加入小根堆(如果链表不为空)。
-
-
合并 K 个有序链表:
-
创建一个虚拟头节点
ret,用于简化结果链表的构建。 -
使用指针
prev指向结果链表的最后一个节点。 -
循环从堆中取出最小的节点
t,并将其连接到结果链表中。 -
如果
t->next不为空,将t->next加入小根堆。 -
重复上述步骤,直到小根堆为空。
-
-
返回结果链表:
-
返回
ret->next,即合并后的有序链表的头节点。 -
删除虚拟头节点
ret,释放内存。
-
举例说明
假设有以下 3 个有序链表:
-
链表 1:
1 -> 4 -> 5 -
链表 2:
1 -> 3 -> 4 -
链表 3:
2 -> 6
初始状态
-
小根堆中包含所有链表的头节点:
[1, 1, 2]。
执行过程
-
第一次循环:
-
取出堆顶节点
1(来自链表 1 或链表 2)。 -
将
1连接到结果链表。 -
将
1->next(4或3)加入小根堆。 -
堆状态:
[1, 2, 3]或[1, 2, 4]。
-
-
第二次循环:
-
取出堆顶节点
1(来自另一个链表)。 -
将
1连接到结果链表。 -
将
1->next(3或4)加入小根堆。 -
堆状态:
[2, 3, 4]。
-
-
第三次循环:
-
取出堆顶节点
2(来自链表 3)。 -
将
2连接到结果链表。 -
将
2->next(6)加入小根堆。 -
堆状态:
[3, 4, 6]。
-
-
第四次循环:
-
取出堆顶节点
3(来自链表 2)。 -
将
3连接到结果链表。 -
将
3->next(4)加入小根堆。 -
堆状态:
[4, 4, 6]。
-
-
第五次循环:
-
取出堆顶节点
4(来自链表 1 或链表 2)。 -
将
4连接到结果链表。 -
将
4->next(5或nullptr)加入小根堆(如果存在)。 -
堆状态:
[4, 5, 6]。
-
-
第六次循环:
-
取出堆顶节点
4(来自另一个链表)。 -
将
4连接到结果链表。 -
将
4->next(nullptr)不加入小根堆。 -
堆状态:
[5, 6]。
-
-
第七次循环:
-
取出堆顶节点
5(来自链表 1)。 -
将
5连接到结果链表。 -
将
5->next(nullptr)不加入小根堆。 -
堆状态:
[6]。
-
-
第八次循环:
-
取出堆顶节点
6(来自链表 3)。 -
将
6连接到结果链表。 -
堆为空,循环结束。
-
最终结果
-
合并后的链表为:
1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6。
代码总结
-
小根堆的作用:
-
小根堆用于高效地找到当前最小的节点,确保每次都能将最小的节点连接到结果链表中。
-
-
虚拟头节点的使用:
-
虚拟头节点
ret简化了结果链表的构建过程。
-
-
时间复杂度:
-
假设 K 是链表的数量,N 是所有链表的总节点数。
-
每个节点被插入和弹出小根堆一次,时间复杂度为
O(N log K)。
-
-
空间复杂度:
-
小根堆最多存储 K 个节点,空间复杂度为
O(K)。
-
执行步骤总结
-
将所有链表的头节点加入小根堆。
-
循环从小根堆中取出最小节点,连接到结果链表。
-
将取出的节点的下一个节点加入小根堆。
-
重复上述步骤,直到小根堆为空。
-
返回合并后的有序链表。
解法二:算法代码(递归/分治)
/*** 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) {return merge(lists, 0, lists.size() - 1);}ListNode* merge(vector<ListNode*>& lists, int left, int right) {if (left > right)return nullptr;if (left == right)return lists[left];// 1. 平分数组int mid = left + right >> 1;// [left, mid] [mid + 1, right]// 2. 递归处理左右区间ListNode* l1 = merge(lists, left, mid);ListNode* l2 = merge(lists, mid + 1, right);// 3. 合并两个有序链表return mergeTowList(l1, l2);}ListNode* mergeTowList(ListNode* l1, ListNode* l2) {if (l1 == nullptr)return l2;if (l2 == nullptr)return l1;// 合并两个有序链表ListNode head;ListNode *cur1 = l1, *cur2 = l2, *prev = &head;head.next = nullptr;while (cur1 && cur2) {if (cur1->val <= cur2->val) {prev = prev->next = cur1;cur1 = cur1->next;} else {prev = prev->next = cur2;cur2 = cur2->next;}}if (cur1)prev->next = cur1;if (cur2)prev->next = cur2;return head.next;}
};
代码分析

1. 类的结构
// 链表节点结构
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) {}
};
-
ListNode是单链表节点的定义,包含一个整数值val和指向下一个节点的指针next。
2. 主函数 - mergeKLists
ListNode* mergeKLists(vector<ListNode*>& lists) {return merge(lists, 0, lists.size() - 1);
}
-
mergeKLists函数接受一个链表数组lists,调用merge函数来合并这些链表,传入左边界和右边界(0 和lists.size() - 1)。
3. 分治法 - merge
ListNode* merge(vector<ListNode*>& lists, int left, int right) {if (left > right)return nullptr; // 边界情况if (left == right)return lists[left]; // 只有一个链表时返回该链表
-
merge函数通过递归将链表数组分成两半,继续合并,直到每个子数组只包含一个链表。 -
如果
left大于right,则返回nullptr;如果left等于right,则返回对应的链表。
4. 平分数组与递归
int mid = left + right >> 1; // 平分数组
ListNode* l1 = merge(lists, left, mid); // 合并左半部分
ListNode* l2 = merge(lists, mid + 1, right); // 合并右半部分
-
使用索引
mid将链表数组平分,分别递归调用merge函数处理左半部分和右半部分。
5. 合并两个有序链表 - mergeTowList
return mergeTowList(l1, l2);
-
在左右两部分排序完成后,调用
mergeTowList函数合并这两个有序链表。
6. 合并过程
ListNode* mergeTowList(ListNode* l1, ListNode* l2) {if (l1 == nullptr)return l2; // 如果 l1 为空,返回 l2if (l2 == nullptr)return l1; // 如果 l2 为空,返回 l1
-
mergeTowList函数用于合并两个单独的有序链表。 -
首先处理边界情况,如果某个链表为空,直接返回另一个链表。
7. 合并逻辑
ListNode head;
ListNode *cur1 = l1, *cur2 = l2, *prev = &head;
head.next = nullptr;
while (cur1 && cur2) {if (cur1->val <= cur2->val) {prev = prev->next = cur1; // 将较小的节点加入合并链表cur1 = cur1->next;} else {prev = prev->next = cur2;cur2 = cur2->next;}
}
-
使用两个指针
cur1和cur2遍历链表l1和l2,通过比较值将较小的节点添加到新的合并链表中,并移动相应的指针。 -
prev指向合并后的链表的最后一个节点,初始时指向一个虚拟的头节点head。
8. 处理剩余节点
if (cur1)prev->next = cur1; // 如果 l1 有剩余,连接到合并链表
if (cur2)prev->next = cur2; // 如果 l2 有剩余,连接到合并链表
return head.next; // 返回合并后的链表
-
如果
cur1或cur2中还有未遍历的节点,将其连接到合并链表的末尾。 -
返回
head.next,即合并后链表的头节点。
举例说明
假设我们有三个链表如下:
-
链表 1: 1 -> 4 -> 5
-
链表 2: 1 -> 3 -> 4
-
链表 3: 2 -> 6
合并过程
-
初始调用:
mergeKLists([{1, 4, 5}, {1, 3, 4}, {2, 6}])-
调用
merge,此时left = 0,right = 2,进行分割。
-
-
第一次分割:
-
中间索引
mid = 1。分为[0, 1]和[2]。 -
对
[0, 1]继续分割。
-
-
第二次分割:
-
对
[0, 1],中间索引mid = 0,分为[0]和[1]。 -
递归返回链表 1 和链表 2。
-
-
合并链表 1 和 链表 2:
-
合并结果为: 1 -> 1 -> 3 -> 4 -> 4 -> 5。
-
-
合并结果与链表 3:
-
最后调用
mergeTowList合并结果与链表 3。 -
合并后的最终结果为: 1 -> 1 -> 2 -> 3 -> 4 -> 4 -> 5 -> 6。
-
五、25. K 个一组翻转链表 - 力扣(LeetCode)

算法代码:
/*** 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* reverseKGroup(ListNode* head, int k) {// 1. 先求出需要逆序多少组int n = 0;ListNode* cur = head;while (cur) {cur = cur->next;n++;}n /= k;// 2. 重复 n 次:⻓度为 k 的链表的逆序即可ListNode* newHead = new ListNode(0);ListNode* prev = newHead;cur = head;for (int i = 0; i < n; i++) {ListNode* tmp = cur;for (int j = 0; j < k; j++) {ListNode* next = cur->next;cur->next = prev->next;prev->next = cur;cur = next;}prev = tmp;}// 把不需要翻转的接上prev->next = cur;cur = newHead->next;delete newHead;return cur;}
};
代码分析

1. 链表节点结构定义
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) {}
};
-
ListNode是单链表节点的定义,包含一个整数值val和指向下一个节点的指针next。
2. 主函数 - reverseKGroup
ListNode* reverseKGroup(ListNode* head, int k) {// 1. 先求出需要逆序多少组int n = 0;ListNode* cur = head;while (cur) {cur = cur->next;n++;}n /= k; // 需要逆序的完整组数
-
该函数接受链表头节点
head和一个整数 ( k ) 作为输入。 -
首先,计算链表的长度 ( n ),并通过
n /= k计算需要逆序的完整组数。
3. 逆序过程
ListNode* newHead = new ListNode(0); // 创建一个虚拟头节点
ListNode* prev = newHead; // prev 指向新链表的尾部
cur = head; // cur 指向原链表的头
for (int i = 0; i < n; i++) {ListNode* tmp = cur; // 暂存当前组的头节点for (int j = 0; j < k; j++) {ListNode* next = cur->next; // 暂存下一个节点cur->next = prev->next; // 将当前节点插入到 prev 后面prev->next = cur; // 更新 prevcur = next; // 移动到下一个节点}prev = tmp; // 更新 prev 为当前组的头节点
}
-
创建一个虚拟头节点
newHead,方便处理链表的插入和返回。 -
使用
prev指针来构建新的链表,初始时指向newHead。 -
通过一个外层循环遍历需要逆序的组,每次逆序 ( k ) 个节点。
-
内层循环负责逆序当前 ( k ) 个节点。具体步骤为:
-
暂存当前节点的下一个节点
next。 -
将当前节点
cur插入到prev后面,使得cur成为逆序链表的一部分。 -
更新
cur指向下一节点。
-
4. 处理不需要翻转的部分
// 把不需要翻转的接上
prev->next = cur;
cur = newHead->next; // 更新 cur 为新的头节点
delete newHead; // 删除虚拟头节点
return cur; // 返回新的头节点
-
当所有需要翻转的组都处理完后,将不需要翻转的部分直接连接到
prev的后面。 -
更新
cur为新链表的头节点,删除虚拟头节点newHead,并返回链表的新头节点。
举例说明
假设有一个链表如下:
-
输入链表: 1 -> 2 -> 3 -> 4 -> 5
-
k = 2
处理过程
-
计算链表长度:
-
链表长度 ( n = 5 ),计算得到 ( n / k = 2 )。因此,需要逆序 2 组。
-
-
第一次逆序:
-
当前节点
cur指向 1,暂存为tmp。 -
逆序 2 个节点:
-
1 -> 2 当前为: 2 -> 1 -> (后续)
-
2 -> 3 更新: 1 -> 2 -> (后续)
-
逆序后链表: 2 -> 1 (指向 3)
-
-
-
第二次逆序:
-
cur现在指向 3,暂存为tmp。 -
逆序 2 个节点:
-
3 -> 4 当前为: 4 -> 3 -> (后续)
-
3 -> 5 更新: 4 -> 3 -> (后续)
-
逆序后链表: 4 -> 3 (指向 5)
-
-
-
处理剩余节点:
-
此时
cur指向 5,prev指向 3,将 5 直接连接到最后: -
最终链表为:2 -> 1 -> 4 -> 3 -> 5
-
总结
-
最终输出的链表是:2 -> 1 -> 4 -> 3 -> 5。
-
当链表的节点数不足 ( k ) 时(例如以下情况1 -> 2 -> 3 -> 4 -> 5 -> 6,当 ( k = 4 ) 时),只会逆序前 4 个节点,后面的节点保持原顺序,结果为:4 -> 3 -> 2 -> 1 -> 5 -> 6。
相关文章:
链表(典型算法思想)—— OJ例题算法解析思路
目录 一、2. 两数相加 - 力扣(LeetCode) 算法代码: 1. 初始化 2. 遍历链表并相加 3. 返回结果 举例说明 二、24. 两两交换链表中的节点 - 力扣(LeetCode) 算法代码: 代码思路 举例说明 初始状…...
【C++指南】解锁C++ STL:从入门到进阶的技术之旅
💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 一、STL 是什么 二、STL 的核心组件 2.1 容器(Containers) 2.2 算法&…...
LeetCode刷题---字符串---859
亲密字符串 859. 亲密字符串 - 力扣(LeetCode) 题目: 给你两个字符串 s 和 goal ,只要我们可以通过交换 s 中的两个字母得到与 goal 相等的结果,就返回 true ;否则返回 false 。 交换字母的定义是&…...
数据处理中多线程功能的设计逻辑,及python的多线程实现
数据处理中多线程功能的设计逻辑主要是通过并发编程模型来提高程序的执行效率和响应速度。多线程允许在同一进程中创建多个线程,每个线程独立执行任务,同时共享进程的资源(如内存空间)。这种机制特别适用于I/O密集型任务ÿ…...
DeepSeek-R1技术革命:用强化学习重塑大语言模型的推理能力
引言:低成本高性能的AI新范式 在2025年1月,中国AI公司DeepSeek发布了两个标志性模型——DeepSeek-R1-Zero与DeepSeek-R1,以仅600万美元的训练成本实现了与OpenAI O1系列(开发成本约5亿美元)相当的推理性能,…...
python中的深度学习框架TensorFlow 和 PyTorch 有什么区别?
TensorFlow 和 PyTorch 是目前最流行的两个深度学习框架,它们在设计理念、使用方式和社区支持等方面存在一些显著的区别。以下是它们的主要区别: 1. 设计理念 TensorFlow: 静态计算图:TensorFlow 使用静态计算图,即在运行模型之前需要先定义整个计算图。这使得 TensorFlo…...
用 Python 实现 DeepSeek R1 本地化部署
DeepSeek R1 以其出色的表现脱颖而出,不少朋友想将其本地化部署,网上基于 ollama 的部署方式有很多,但今天我要带你领略一种全新的方法 —— 使用 Python 实现 DeepSeek R1 本地化部署,让你轻松掌握,打造属于自己的 AI…...
Spreadjs与GcExcel
GcExcel VS SpreadJS 前言 报表系统前端化,释放后端压力,调高前端研发产能,但随着报表系统的数据量的增加,浏览器的限制,前端报表已达到瓶颈,用户使用体验逐渐不友好,为解决这一问题,找到新的解决方案,所以写下此篇对比 两者分别是什么? SpreadJS 是一款基于 HTML5…...
vue中使用lodash的debounce(防抖函数)
1、安装 npm i --save lodash.debounce2、引入 import debounce from lodash.debounce3、使用 <van-search v-model"searchValue" placeholder"输入姓名或工号" inputhandleInput />第一种: handleInput: debounce(function (val) {c…...
什么是耐环境环形光源
耐环境环形光源是一种专为工业视觉系统设计的光源,能够在恶劣环境下稳定工作。以下是其主要特点和应用: 特点 耐用性:外壳坚固,通常采用金属或高强度塑料,能承受冲击、振动和温度变化。 防护等级:具备高IP防…...
3dtiles——Cesium ion for Autodesk Revit Add-In插件
一、说明: Cesium已经支持3dtiles的模型格式转换; 可以从Cesium官方Aesset中上传gltf等格式文件转换为3dtiles; 也可以下载插件(例如revit-cesium插件)转换并自动上传到Cesium官方Aseet中。 Revit转3dtiles插件使用…...
Edge浏览器清理主页
我们都知道,Microsoft Edge浏览器是微软创造的搜索浏览器,Windows10、11自带。但是你可以看到,每次你打开Edge浏览器的时候都可以看到许多的广告,如图: 导致打开Edge浏览器的时候会遭受卡顿,广告骚扰&#…...
leetcode刷题第十天——栈与队列Ⅱ
本次刷题顺序是按照卡尔的代码随想录中给出的顺序 1047. 删除字符串中的所有相邻重复项 char* removeDuplicates(char* s) {int len strlen(s);char* tmp malloc(sizeof(char) * (len 1));int top -1, idx 0;while(idx < len) {if(top -1) tmp[top] s[idx];else {i…...
硬修复(hPPR)与软修复(sPPR)
什么是PPR? PPR(Post Package Repair)是一种内存修复技术,主要用于修复DRAM(包括LPDDR4、DDR4等)中的存储单元故障。它通过在封装后对内存芯片进行修复,提高良率和可靠性,减少因制造缺陷导致的内存失效。 想象一下,你买了一袋苹果,有些苹果表面可能有个小斑点或者磕…...
filebeat抓取nginx日志
目录 一、抓取普通的应用输出日志到elasticsearch 二、抓取nginx日志输出到ElasticSearch 2.1、nginx.conf设定日志输出为JSON格式 2.2、nginx.conf设定日志按天输出文件 2.3、抓取Nginx JSON到ElasticSearch配置 一、抓取普通的应用输出日志到elasticsearch - type: log…...
TLQ-CN10.0.2.0 (TongLINK/Q-CN 集群)部署指引 (by lqw)
文章目录 安装准备虚拟机部署部署zk集群安装zk集群启动zk集群初始化元数据(zk)关闭zk集群 部署BookKeeper集群安装BookKeeper集群初始化元数据(bk)启动BookKeeper停止 BookKeeper 部署Brokers集群安装Brokers集群启动 broker停止 …...
第 14 天:UE5 C++ 与蓝图(Blueprint)交互!
🎯 目标: ✅ 了解 C 与蓝图(Blueprint)交互的方式 ✅ 在 C 中调用蓝图函数 ✅ 让蓝图访问 C 变量和方法 ✅ 使用 UFUNCTION、UPROPERTY 进行蓝图暴露 ✅ 提高开发效率,让 C 和蓝图开发者高效协作 1️⃣ 为什么要让 C…...
小初高各学科教材,PDF电子版下载
链接:https://pan.quark.cn/s/7c2125f648e2 小初高中电子课本资料pdf合集 高中各科教材 (部分举例) - 语文:新人教版、旧人教版、苏教版等 - 数学:人教A版、沪教版、鄂教版等 - 英语:重大版、人教版…...
Trader Joe‘s EDI 需求分析
Trader Joes成立于1967年,总部位于美国加利福尼亚州,是一家独特的零售商,专注于提供高质量且价格合理的食品。公司经营范围涵盖了各类杂货、冷冻食品、健康食品以及独特的本地特色商品。 EDI需求分析 电子数据交换(EDIÿ…...
python class详解
在 Python 中,class 是用来创建新的数据类型,即对象的蓝图。类可以包含属性(变量)和方法(函数),它们定义了对象的状态和行为。以下是 Python 类的基本概念和用法的详细解释: 定义类…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
Objective-C常用命名规范总结
【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名(Class Name)2.协议名(Protocol Name)3.方法名(Method Name)4.属性名(Property Name)5.局部变量/实例变量(Local / Instance Variables&…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
VM虚拟机网络配置(ubuntu24桥接模式):配置静态IP
编辑-虚拟网络编辑器-更改设置 选择桥接模式,然后找到相应的网卡(可以查看自己本机的网络连接) windows连接的网络点击查看属性 编辑虚拟机设置更改网络配置,选择刚才配置的桥接模式 静态ip设置: 我用的ubuntu24桌…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
