目录
①力扣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
- 题目数据保证列表表示的数字不含前导零
/**
* 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) {
}
};
解析代码
两个链表都是逆序存储数字的,即两个链表的个位数、十位数等都已经对应,可以直接相加。 在相加过程中要注意是否产生进位,产生进位时需要将进位和链表数字一同相加。如果产生进位的位置在链表尾部,即答案位数比原链表位数长一位,还需要再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 *head = new ListNode(0);
ListNode *ret = head;
ListNode *cur1 = l1, *cur2 = l2;
int carry = 0; // 进位
while(cur1 || cur2 || carry)
{
int val = carry;
if(cur1)
{
val += cur1->val;
cur1 = cur1->next;
}
if(cur2)
{
val += cur2->val;
cur2 = cur2->next;
}
head->next = new ListNode(val % 10);
head = head->next;
carry = val / 10;
}
return ret->next;
}
};
②力扣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) {
}
};
解析代码
递归法在下面链接讲过:
Offer必备算法07_递归_五道力扣题详解(由易到难)-CSDN博客
迭代法就是自己画图,不要吝啬定义指针,直接定义四个指针,在前面new一个头结点视为prev,让cur和next1交换,然后四个指针像后走,结束条件是cur或者next1为空。
/**
* 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) {
ListNode *newHead = new ListNode(0);
if(head == nullptr || head->next == nullptr)
return head;
// newHead -> 1 -> 2 -> 3
// 1和2换 -> cur和next1换
// prev -> cur -> next1 -> next2
// cur -> prev -> next1 -> next2
ListNode *prev=newHead, *cur=head, *next1=head->next, *next2=next1->next;
while(cur && next1)
{
prev->next = next1;
next1->next = cur;
cur->next = next2;
prev = cur;
cur = next2;
if(cur)
next1 = cur->next;
if(next1)
next2 = next1->next;
}
cur = newHead->next;
delete newHead;
return cur;
// 递归法
// if(head == nullptr || head->next == nullptr)
// return head;
// ListNode* tmp = swapPairs(head->next->next); // 把两个结点之外的看成另一部分
// head->next->next = head;
// auto ret = head->next; // 保存一下要返回的结点
// head->next = tmp;
// return ret;
}
};
③力扣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 * 10^4]
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) {
}
};
解析代码
在C语言讲的数据结构里讲过这三道OJ:
这道题就是先找到中间结点,分为两部分链表,然后翻转后部分链表,最后合并两个链表:
/**
* 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 *fast = head, *slow = head;
while(fast && fast->next) // 找到中间结点
{
slow = slow->next;
fast = fast->next->next;
}
ListNode* slowNext = slow->next;
slow->next = nullptr; // 两个链表断开
slow = slowNext;
ListNode *reverseList = nullptr;
while(slow) // 中间结点后面的头插到翻转链表
{
slowNext = slow->next;
slow->next = reverseList;
reverseList = slow;
slow = slowNext;
}
ListNode* tmp = head; // 合并两个链表
ListNode *cur1 = head->next, *cur2 = reverseList;
while(cur1 && cur2)
{
tmp->next = cur2;
tmp = tmp->next;
cur2 = cur2->next;
tmp->next = cur1;
cur1 = cur1->next;
tmp = tmp->next;
}
}
};
④力扣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
/**
* 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) {
}
};
解析代码1(小根堆优化)
之前写过合并两个有序链表的题,此题暴力解法就是依次合并两个有序链表,合并K次。时间复杂度是O(N*K^2),是N^3级别的。
在暴力解法基础上可以使用小根堆优化,合并 K 个升序链表时,选择 K 个链表中,头结点值最小的那⼀个的插入到新链表,时间复杂度是O(N*K*logK)。下面是小根堆优化的代码:
/**
* 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& e : lists) // 所有头结点进入小根堆
{
if(e)
heap.push(e);
}
ListNode* newHead = new ListNode(0);
ListNode* tmp = newHead;
while(!heap.empty())
{ // 尾插堆顶结点到新链表,再把旧链表下个结点插入小根堆
ListNode* cur = heap.top();
heap.pop();
tmp->next = cur;
tmp = cur;
if(cur->next)
{
heap.push(cur->next);
}
}
tmp = newHead->next;
delete newHead;
return tmp;
}
};
解析代码2(递归_归并)
- 递归出口:如果当前要合并的链表编号范围左右值相等,无需合并,直接返回当前链表。
- 应用二分思想,等额划分左右两段需要合并的链表,使这两段合并后的长度尽可能相等。
- 对左右两段分别递归,合并[left,right]范围内的链表。
- 最后调用合并两个链表函数进行合并。
/**
* 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];
int mid = (left + right) >> 1;
ListNode* list1 = merge(lists, left, mid);
ListNode* list2 = merge(lists, mid+1, right);
// 上面递归得到一个/两个链表
return mergeTwoLists(list1, list2);
}
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2)
{
if(list1 == nullptr)
return list2;
if(list2 == nullptr)
return list1;
if(list1->val <= list2->val)
{
list1->next = mergeTwoLists(list1->next, list2);
return list1;
}
else
{
list2->next = mergeTwoLists(list2->next, list1);
return list2;
}
}
};
⑤力扣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)
额外内存空间的算法解决此问题吗?
/**
* 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) {
}
};
解析代码
可以把链表按 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) {
int n = 0;
ListNode* cur = head;
while(cur)
{
cur = cur->next;
++n;
}
n /= k;
cur = head;
ListNode* newHead = new ListNode();
ListNode* prev = newHead;
while(n--)
{
ListNode* tmp = cur;
int cnt = k;
while(cnt--)
{
ListNode* prevNext = cur->next;
cur->next = prev->next; // cur头插到prev
prev->next = cur;
cur = prevNext;
}
prev = tmp;
}
prev->next = cur;
cur = newHead->next;
delete newHead;
return cur;
}
};
本篇完。
下一篇动态规划的类型的路径dp类型的OJ。
下下篇数据结构类型的是哈希表类型的OJ。