不要吝啬空间,大胆定义变量。
2. 我们也可以定义next指针,这样就不需要考虑下面四句语句的执行顺序
两数相加
这里给我们逆序存储链表反而有利于我们做题,我们相加时是从最低位开始加,如果题里给我们正常顺序存储的链表的话,我们还需要自己逆置。
解法:模拟两数相加的过程
/**
* 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;
}
};
两两交换链表中的节点
解法:循环迭代:
所以终止循环条件:cur或者next有一个指向空的时候,就终止循环
/**
* 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;
}
};
重排链表
**解法:模拟——**其实很像两个链表合并,一个是正向的1、2,一个是倒向的链表4、3。恰好是原始链表的前半部分,和后半部分逆序。
所以我们的策略是:先找到链表的中点,然后把后面部分的链表逆序;然后把前面的部分和后面的部分一个一个交互链接,合并一起即可。
找到链表的中间节点(⼀定要画图考虑 slow 的落点在哪⾥ )
2. 我们也可以让slow所指的开始翻转(红色笔标注的)。此时无非是把slow所指的节点丢给后半部分,逆序合并后发现结果并不影响![image.png](https://img-blog.csdnimg.cn/img_convert/77049b1c441cb9b3336de5f341cee1c9.png)
2. 当我们选择slow->next位置开始翻转,我们只需让slow所指的位置置空即可,
3. 如果我们只能用slow位置开始翻转的情况时,我们可以引入一个虚拟头结点newhead(哨兵位),去链接head,然后在快慢指针找中间节点,之前是slow位置,就会变成slow->next,这时候也能将两个链表断开。
/**
* 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;
}
};
合并k个升序链表
**解法一:暴力解法:**逐个合并.其中n是链表的长度,k为链表的个数
解法二:优先级队列做优化
我们可以直接定义k个指针,仿照合并k个有序链表,把这些链表中较小的头结点放到newhead后面,放完之后右移,继续比较各个节点最小的,把最小的连接在newhead后面即可。这样只需要遍历一遍链表即可。O(n*k)是理想状态下的时间复杂度,我们还要找出k个节点中最小的节点。这里我们可以利用优先级队列
把k个节点丢入小根堆里,经过排序后,堆顶元素即为最小的元素,链接到newhead后面后,将移动后下一个节点放入小根堆里。这里时间复杂度O(nklogk)
解法三:分治——递归
流程:
递归函数设计:
时间复杂度:一共k个链表,每个链表里面有n个节点,每一层我们都平均分,即高度为logk,每一个节点都执行了logk次合并(这棵树的高度次)
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;
}
};
/**
* 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;
}
};
k个一组翻转链表
链表中的节点每k个一组进行翻转,如果剩余的链表不够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* 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;
}
};