Leetcode刷题日记:21-25题篇
简介
这个系列将是Leetcode的刷题记录贴,按照题库顺序,每五题为一贴,主要目的做自己的记录、总结之用,但若有同学或同好看到此贴,并有所帮助,也算额外之喜,共勉!
题目:
21. 合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
题解:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* ans=new ListNode(0);
ListNode* head=ans;
while(l1!=nullptr && l2!=nullptr){
if( l1->val < l2->val ){
head->next=l1;
l1=l1->next;
head=head->next;
}
else{
head->next=l2;
l2=l2->next;
head=head->next;
}
}
head->next= l1==nullptr ? l2 : l1 ;
return ans->next;
}
};
思路:
显然,我们需要维护一个 head 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程 :如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 head 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作,然后把 head 向后移一位。
注意,由于head会发生移动,这样我们将难以找到输出链表的头指针,故我们需要提前设置一个哨兵节点 ans ,这可以在最后让我们比较容易地返回合并后的链表。
在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
有效括号组合需满足:左括号必须以正确的顺序闭合。
示例:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
题解:
class Solution {
public:
vector<string> generateParenthesis(int n) {
string brackets="()";
string tmp;
vector<string> ans;
f(0,2*n,brackets,tmp,ans);
//去除无效的括号组合
vector<string> finalans;
for(int i=0;i<ans.size();i++)
if(g(ans[i])==true) finalans.push_back(ans[i]);
return finalans;
}
//回溯法找到所有组合----17题的题解
void f(int layer,int n,string& brackets,string& tmp,vector<string>& ans){
if(layer==n) ans.push_back(tmp);
else{
for(char x:brackets){
tmp.push_back(x);
f(layer+1,n,brackets,tmp,ans);
tmp.pop_back();
}
}
}
//使用栈,判断一个字符串是否是有效括号-----20题的题解
bool g(string s){
stack<char> sta;
for(char c:s){
if(!sta.empty() && (c==')'&&sta.top()=='(')) sta.pop();
else sta.push(c);
}
return sta.empty()?true:false;
}
};
思路:
此题的解法采用了之前刷到的两道题的思路的结合,分别是:17.电话号码的字母组合 和 20.有效的括号
建立一个string brackets="()",这里可以对其进行修改以适应于类似问题
写了两个函数:
1.函数f,使用回溯法,找到 brackets 中的字符能构成的所有长度为n的字符串(关于回溯法的理解可以看我的图解17题题解)
2.函数g,使用栈,判断一个字符串是否是有效的括号
23. 合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例:
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
题解:
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode* tmp=nullptr;
for(int i=0;i<lists.size();i++){
tmp=merge(tmp,lists[i]);
}
return tmp;
}
//第21.合并两个有序链表题的同款代码
ListNode* merge(ListNode* a,ListNode* b){
ListNode* head=new ListNode(0);
ListNode* tmp=head;
while(a!=nullptr && b!=nullptr){
if(a->val < b->val){
tmp->next=a;
a=a->next;
}
else{
tmp->next=b;
b=b->next;
}
tmp=tmp->next;
}
tmp->next= b ? b : a;
return head->next;
}
};
思路:对21. 合并两个有序链表的解体思路的扩展,前两个链表先合并,结果再跟第三个合并,结果再跟第四个合并…以此类推直到结束
24. 两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
输入:head = [1,2,3,4]
输出:[2,1,4,3]
题解:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode* start=new ListNode(0);
start->next=head;
//哨兵节点 ans,用于记录链表的开头,方便输出
ListNode* ans=start;
while(start->next!=nullptr && start->next->next!=nullptr){
swapTwo(start);
//不能忘了更新 start
start=start->next->next;
}
return ans->next;
}
//start是两个交换节点的前一个结点
void swapTwo(ListNode* start){
ListNode* a=start->next;
ListNode* b=start->next->next;
start->next=b;
a->next=b->next;
b->next=a;
}
};
看到这个题大家都能想到要写一个两个节点的交换函数,但是写对并不容易,笔者一开始的思路是写一个神似于swap的函数,如下:
void swapTwo(ListNode* a,ListNode* b){
ListNode* tmp;
tmp->next=b;
a->next=b->next;
b->next=a;
}
是不是和入门写的交换整数函数swap()很像?但是笔者自信的提交后悲催了,对于{1,2,3,4}得到的结果是{1,3},经过一番推导后发现代码运行过程如下:
显然,这样的做法只考虑了交换的两个节点和后面的节点之间的连接,没有考虑到和之前的结点的连接,那么这个交换函数如何对之前的节点施加影响呢?
考虑到链表是单向向后的数据结构,这个swapTwo函数的参数显然应该包含两个交换节点的前一个结点,故而笔者写下了新的交换函数:
void swapTwo(ListNode* start){
ListNode* a=start->next;
ListNode* b=start->next->next;
start->next=b;
a->next=b->next;
b->next=a;
}
运行,成功,在所有c++提交中击败100%的用户,end!
25. K 个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
题解:
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* prev=new ListNode(0);
prev->next=head;
//设置哨兵节点 ans
ListNode* ans=prev;
//当剩下的节点数不足 k 时,退出循环
while(swapK(prev,k)!=nullptr){
swapK(prev,k);
//将前节点 prevv 更新为前一组 k 个节点交换后的尾节点
prev=swapK(prev,k);
}
return ans->next;
}
//交换 k 个节点的函数,以每一组k个节点的前一个节点,记为前节点 prev,和 k 为参数
ListNode* swapK(ListNode* prev,int k){
//将每一个 k 个节点划分成 前 k-1 个节点和尾节点
//这里的 tail 是每一组 k 个节点的尾节点
ListNode* tail;
//这里的 head 是 前 k-1 个节点 经过翻转后的尾节点,因他在 k 个节点的尾节点之前,命名head
ListNode* head;
if(k>=2){
tail=prev;
//此循环使 tail 指向本组尾节点
for(int i=0;i<k;i++){
tail=tail->next;
//判断本组节点够不够 k 个
if(!tail) return nullptr;
}
//递归的思想,每一组 k 个节点的翻转共两步:1)对前 k-1 个节点进行翻转,2)再把尾节点放到这 k-1 个节点的前面
//这是 1)步
head=swapK(prev,k-1);
//这是 2)步
head->next=tail->next;
tail->next=prev->next;
prev->next=tail;
//返回尾节点,用作下一组的 prev 节点
return head;
}
else{
//递归的出口
return prev->next;
}
}
};
解题思路
本题目的核心显然是解决对每一组k个节点的翻转,以及每组之间的连接,试想:
对每一组k个节点写一个翻转函数,以这一组的之前一个节点 prev 以及 k 作为参数,最终返回翻转后这一组的尾节点,此尾节点将作为下一组的prev节点,当某一组不足k个节点时,不翻转,输出链表
解题步骤
我们的解题步骤如下:
- 初始化一个prev节点位于整个链表之前
- 设置哨兵节点ans等于prev,便于最后输出链表
- 判断以prev->next为起点是否存在k个节点,若存在,调用翻转函数进行翻转
- 更新prev为第一组节点翻转后的尾节点
- 重复3、4步
- 输出链表
翻转函数的写法(递归)
解题思路说完了,再讲讲这个翻转函数的写法:
每一组k个节点的翻转,显然可以等价于先对前k-1个节点进行翻转,再将第k个节点放到这k-1个节点的前面,故翻转函数的过程可以表示如下:
- 判断k是否大于等于2
- 若k不大于2,也就是k=1,一个节点显然是不用翻转的,这也是递归的出口,此节点也就是翻转后的尾节点,直接返回该节点
- 若k>=2,调用参数为k-1的翻转函数将前k-1个节点翻转
- 将前k-1个节点翻转后的结果再和第k个节点交换
- 直到递归的出口,也就是k=1
注
题目来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
博客著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。