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个节点时,不翻转,输出链表

解题步骤
我们的解题步骤如下:

  1. 初始化一个prev节点位于整个链表之前
  2. 设置哨兵节点ans等于prev,便于最后输出链表
  3. 判断以prev->next为起点是否存在k个节点,若存在,调用翻转函数进行翻转
  4. 更新prev为第一组节点翻转后的尾节点
  5. 重复3、4步
  6. 输出链表

翻转函数的写法(递归)
解题思路说完了,再讲讲这个翻转函数的写法:
每一组k个节点的翻转,显然可以等价于先对前k-1个节点进行翻转,再将第k个节点放到这k-1个节点的前面,故翻转函数的过程可以表示如下:

  1. 判断k是否大于等于2
  2. 若k不大于2,也就是k=1,一个节点显然是不用翻转的,这也是递归的出口,此节点也就是翻转后的尾节点,直接返回该节点
  3. 若k>=2,调用参数为k-1的翻转函数将前k-1个节点翻转
  4. 将前k-1个节点翻转后的结果再和第k个节点交换
  5. 直到递归的出口,也就是k=1

题目来源:力扣(LeetCode)
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

博客著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值