目录
1、2. 两数相加
2、24. 两两交换链表中的节点
3、143. 重排链表
4、23. 合并 K 个升序链表
5、25. K 个一组翻转链表
解决链表题目常用方法:
1、画图
2、引入虚拟"头”结点
- 便于处理边界情况
- 方便我们对链表操作
3、大胆定义变量,减少连接节点时出现错误。
4、快慢双指针
- 判环
- 找链表中环的入口
- 找链表中倒数第 n个结点
1、2. 两数相加
思路:模拟相加,注意进位问题。
/**
* 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* newhead=new ListNode(0);
ListNode* cur1=l1,*cur2=l2;
ListNode* cur=newhead;
int c=0;//进位
while(cur1||cur2||c){
if(cur1){
c+=cur1->val;
cur1=cur1->next;
}
if(cur2){
c+=cur2->val;
cur2=cur2->next;
}
cur->next=new ListNode(c%10);
cur=cur->next;
c/=10;
}
cur=newhead->next;
delete newhead;
return cur;
}
};
2、24. 两两交换链表中的节点
思路:循环、迭代(模拟)。定义一个带有虚拟头结点的链表统计结果,接着定义出包括虚拟头结点在内和它后三个节点方便直接插入新链表。
/**
* 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 = prev->next;
if (cur)
next = cur->next;
if (next)
nnext = next->next;
}
cur = newHead->next;
delete newHead;
return cur;
}
};
- 首先创建一个新的头节点
newHead
,其值为0,并将其指向原链表的头节点head
。 - 使用指针
prev
指向newHead
,指针cur
指向prev
的下一个节点(即原链表的头节点),指针next
指向cur
的下一个节点,指针nnext
指向next
的下一个节点。 - 进入循环,条件是
cur
和next
都不为空。在循环中:- 将
prev
的next
指针指向next
,实现交换相邻节点。 - 将
next
的next
指针指向cur
,完成节点交换。 - 将
cur
的next
指针指向nnext
,恢复链表连接。 - 更新
prev
为cur
,cur
为prev
的下一个节点,next
为cur
的下一个节点,nnext
为next
的下一个节点。
- 将
- 循环结束后,重新定位
cur
指向新链表的头部,即newHead
的下一个节点。 - 删除创建的头节点
newHead
,并返回交换后链表的头节点cur
。
3、143. 重排链表
思路:快慢双指针找到中间节点,逆序后半部分,利用双指针合并两个链表。
/**
* 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;
}
ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
ListNode* rhead = new ListNode(0);
ListNode* cur = slow->next;//逆序不要中间节点
slow->next = nullptr;//分离逆序部分
while (cur) {
ListNode* next = cur->next;
cur->next = rhead->next;
rhead->next = cur;
cur = next;
}
ListNode *cur1 = head, *cur2 = rhead->next;
ListNode* ret = new ListNode(0);
ListNode* prev = ret;
while (cur1) {
prev->next = cur1;
cur1 = cur1->next;
prev = prev->next;
if (cur2) {
prev->next = cur2;
cur2 = cur2->next;
prev = prev->next;
}
}
delete rhead;
delete ret;
}
};
4、23. 合并 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:
struct cmp {
bool operator()(const ListNode* a, const ListNode* b) {
return a->val > b->val;
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<ListNode*, vector<ListNode*>, cmp> heap;
for (auto l : lists) {
if (l)
heap.push(l);
}
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;
}
};
- 定义一个比较结构体
cmp
,用于比较两个节点的值大小,确保优先级队列是一个最小堆。 - 遍历输入的链表数组
lists
,将每个链表的头节点(如果不为空)加入到优先级队列heap
中。 - 创建一个哑节点
ret
,它将作为返回的链表的头节点的前驱,用于简化链表操作。同时,使用一个指针prev
来跟踪当前链表的最后一个节点。 - 当
heap
不为空时,循环执行以下操作:- 从
heap
中取出最小值节点t
(即优先级队列的顶部元素),这是当前所有链表头节点中值最小的节点。 - 将
prev
的next
指向t
,将t
接入到当前构建的链表中。 - 更新
prev
指向t
,即将prev
移动到链表的最末端。 - 如果
t
还有下一个节点(t->next
不为空),则将这个下一个节点加入到heap
中,以便继续参与后续的比较和选择过程。
- 从
- 循环结束后,所有输入的链表已经完全合并到了由
ret->next
开始的链表中。 - 在返回结果之前,首先保存
ret->next
到一个临时变量prev
(这里重新使用prev
变量来简化代码,其实是返回链表的头节点),然后删除哑节点ret
。 - 返回
prev
,即合并后的链表的头节点。
思路二:递归/分治
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
return merge(lists, 0, lists.size() - 1);
}
ListNode* merge(vector<ListNode*>& lists, int left, int right) {
int mid = left + right >> 1;
if (left > right)
return nullptr;
if (left == right)
return lists[left]; // 只有一个链表
ListNode* l1 = merge(lists, left, mid);
ListNode* l2 = merge(lists, mid + 1, right);
return mergeTwoList(l1, l2);
}
ListNode* mergeTwoList(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;
}
};
5、25. K 个一组翻转链表
思路:先求出需要逆序的组数n,重复n次长度为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) {
int n = 0;
ListNode* cur = head;
while (cur) {
cur = cur->next;
n++;
}
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;
}
};