算法思想之链表
欢迎拜访:雾里看山-CSDN博客
本篇主题:算法思想之链表
发布时间:2025.4.18
隶属专栏:算法
目录
- 算法介绍
- 常用技巧
- 例题
- 两数相加
- 题目链接
- 题目描述
- 算法思路
- 代码实现
- 两两交换链表中的节点
- 题目链接
- 题目描述
- 算法思路
- 代码实现
- 重排链表
- 题目链接
- 题目描述
- 算法思路
- 代码实现
- 合并 K 个升序链表
- 题目链接
- 题目描述
- 算法思路1
- 代码实现1
- 算法思路2
- 代码实现2
- K 个一组翻转链表
- 题目链接
- 题目描述
- 算法思路
- 代码实现
算法介绍
链表(Linked List
)是一种基于指针或引用的动态数据结构,通过节点间的链接关系实现数据存储。其核心特点是非连续内存分配与高效增删操作,是算法设计中处理动态数据的核心工具之一。
常用技巧
- 画图
画图可以让我们更加直观、形象的理解具体过程。便于我们的理解 - 引入虚拟头结点
便于我们处理边界情况
方便我们对链表进行操作 - 不要吝啬空间,大胆定义变量
在进行链表操作时,多定义几个变量更有利于操作 - 快慢双指针
对于判环、找链表中环的入口、找链表中倒数第n个节点等问题都比较好用
例题
两数相加
题目链接
2. 两数相加
题目描述
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字0
之外,这两个数都不会以0
开头。
示例 1:
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.示例 2:
输入:l1 = [0], l2 = [0]
输出:[0]示例 3:
输入:l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9]
输出:[8,9,9,9,0,0,0,1]提示:
- 每个链表中的节点数在范围
[1, 100]
内0 <= Node.val <= 9
- 题目数据保证列表表示的数字不含前导零
算法思路
两个链表都是逆序存储数字的,即两个链表的个位数、十位数等都已经对应,可以直接相加。
在相加过程中,我们要注意是否产生进位,产生进位时需要将进位和链表数字一同相加。如果产生进位的位置在链表尾部,即答案位数比原链表位数长一位,还需要再 new
一个结点储存最高位。
代码实现
/*** 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* cur = newhead;int num = 0;while(cur1 != nullptr || cur2 != nullptr || num!= 0){if(cur1 != nullptr){num+=cur1->val;cur1 = cur1->next;}if(cur2 != nullptr){num+=cur2->val;cur2 = cur2->next;} ListNode* tmp = new ListNode(num%10);num/=10;cur->next = tmp;cur = tmp;}cur = newhead->next;delete newhead;return cur;}
};
两两交换链表中的节点
题目链接
24. 两两交换链表中的节点
题目描述
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4]
输出:[2,1,4,3]示例 2:
输入:head = []
输出:[]示例 3:
输入:head = [1]
输出:[1]
提示:
- 链表中节点的数目在范围
[0, 100]
内0 <= Node.val <= 100
算法思路
认真画图,进行模拟即可
代码实现
/*** 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 = head, *next = head->next, *nnext = next->next;while(cur!=nullptr && next!=nullptr){prev->next = next;next->next = cur;cur->next = nnext;prev = cur;cur = nnext;if(cur)next = cur->next;if(next)nnext = next->next; }prev = newhead->next;delete newhead;return prev;}
};
重排链表
题目链接
143. 重排链表
题目描述
给定一个单链表
L
的头节点head
,单链表L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例 1:
输入:head = [1,2,3,4]
输出:[1,4,2,3]示例 2:
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]提示:
- 链表的长度范围为
[1, 5 * 104]
1 <= node.val <= 1000
算法思路
画图画图画图,重要的事情说三遍~
- 找中间节点;
- 中间部分往后的逆序;
- 合并两个链表
代码实现
/*** 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. 找到中间节点ListNode *slow = head,*fast = head;while(fast && fast->next){slow = slow->next;fast=fast->next->next;}// 2. 逆序后半部分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 个升序链表
题目链接
23. 合并 K 个升序链表
题目描述
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6示例 2:
输入:lists = []
输出:[]示例 3:
输入:lists = [[]]
输出:[]提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i]
按 升序 排列lists[i].length
的总和不超过10^4
算法思路1
合并两个有序链表是比较简单且做过的,就是用双指针依次比较链表 1
、链表 2
未排序的最小元素,选择更小的那一个加入有序的答案链表中。
合并 K
个升序链表时,我们依旧可以选择 K
个链表中,头结点值最小的那一个。那么如何快速的得到头结点最小的是哪一个呢?用堆这个数据结构就好啦~
我们可以把所有的头结点放进一个小根堆中,这样就能快速的找到每次 K
个链表中,最小的元素是哪个。
代码实现1
/*** 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()(ListNode* l1, ListNode*l2){return l1->val > l2->val;}};
public:ListNode* mergeKLists(vector<ListNode*>& lists) {priority_queue<ListNode*, vector<ListNode*>, cmp> heap;ListNode *newhead = new ListNode(0);ListNode* cur = newhead;for(auto &iter : lists){if(iter)heap.push(iter);}// 合并k个有序链表while(!heap.empty()){ListNode* tmp = heap.top();cur->next = tmp;heap.pop();cur = tmp;if(tmp->next)heap.push(tmp->next);}cur = newhead->next;delete newhead;return cur;}
};
算法思路2
利用递归和链表合并
- 特判,如果题目给出空链表,无需合并,直接返回;
- 返回递归结果。
递归函数设计: - 递归出口:如果当前要合并的链表编号范围左右值相等,无需合并,直接返回当前链表;
- 应⽤二分思想,等额划分左右两段需要合并的链表,使这两段合并后的长度尽可能相等;
- 对左右两段分别递归,合并
[l, r]
范围内的链表; - 再调用
mergeTwoLists
函数进行合并(就是合并两个有序链表)
代码实现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* mergeKLists(vector<ListNode*>& lists) {return mergeSort(lists, 0, lists.size()-1);}ListNode* mergeSort(vector<ListNode*>& lists, int left, int right){if(left>right)return nullptr;if(left == right)return lists[left];// 1. 找中间节点int mid = (left + right) >> 1;// 2.递归排序左右链表ListNode* l1 = mergeSort(lists, left, mid);ListNode* l2 = mergeSort(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 *newhead = new ListNode(0);ListNode* cur = newhead;while(l1 && l2){if(l1->val < l2->val){cur->next = l1;l1 = l1->next;cur = cur->next;}else{cur->next = l2;l2 = l2->next;cur = cur->next;}}if(l1) cur->next = l1;if(l2) cur->next = l2;cur = newhead->next;delete newhead;return cur;}
};
K 个一组翻转链表
题目链接
25. K 个一组翻转链表
题目描述
给你链表的头节点
head
,每k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]提示:
- 链表中的节点数目为
n
1 <= k <= n <= 5000
0 <= Node.val <= 1000
进阶:你可以设计一个只用
O(1)
额外内存空间的算法解决此问题吗?
算法思路
本题的目标非常清晰易懂,不涉及复杂的算法,只是实现过程中需要考虑的细节比较多。
我们可以把链表按 K
个为一组进行分组,组内进行反转,并且记录反转后的头尾结点,使其可以和前、后连接起来。思路比较简单,但是实现起来是比较复杂的。
我们可以先求出一共需要逆序多少组(假设逆序 n
组),然后重复 n
次长度为 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) {if(head == nullptr) return nullptr;// 1. 计算需要翻转多少组int n = 0;ListNode* cur = head;while(cur){n++;cur = cur->next;}n /= k;// 2.完成翻转操作ListNode* newhead = new ListNode(0);ListNode* prev = newhead, *next, *tmp;cur = head;while(n--){tmp = cur;int m = k;while(m--){next = cur->next;cur->next = prev->next;prev->next = cur;cur = next; }prev = tmp;}prev->next = cur;cur = newhead->next;delete newhead;return cur; }
};
⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!