Leetcode刷题日记:16-20题篇

简介

这个系列将是Leetcode的刷题记录贴,按照题库顺序,每五题为一贴,主要目的做自己的记录、总结之用,但若有同学或同好看到此贴,并有所帮助,也算额外之喜,共勉!

题目:

16. 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

题解:

class Solution {
public:
    int threeSumClosest(vector<int>& nums, int target) {
        int len=nums.size(),min_distance=INT_MAX,ans;
        sort(nums.begin(),nums.end());
        for(int i=0;i<len;i++){
            int head=i+1,tail=len-1;
            while(head<tail){
                int sum=nums[i]+nums[head]+nums[tail];
                int tmp=sum-target;
                //双指针的核心代码
                if(tmp>0)
                    tail--;
                if(tmp<0)
                    head++;
                if(tmp==0)
                    return sum;
                if(abs(tmp)<abs(min_distance)) {
                    min_distance=tmp;
                    ans=sum;
                }
            }
        }
        return ans;
    }
};

思路:双指针的解法很多题解都已经讲过了,笔者在这里就着重说一下对于双指针这块的核心代码的理解:

//这是双指针的写法
while(head<tail){
    if(tmp<0)
        head++;
    if(tmp>0)
        tail--;
    if(tmp==0)
        //...
}
//这是普通写法
for(int i=0;i<m;i++)
    for(int j=0;j<n;j++){
        if(tmp==0)
            //...
}

看出区别了吗?

双指针将一个两层循环转化成了一层循环,时间复杂度也从n^2变成了n,那么什么时候会想到要用双指针的方法呢?又如何实现将普通写法到双指针写法的转换呢?
一般来讲,当遇到需要对一个数组进行重复遍历时,可以想到使用双指针法
说是指针,其实是设置两个int变量分别赋值为数组的首和尾,在一层循环内,每次只对其中一个指针进行移动,找到判断指针移动的条件是双指针的核心,在该题中,是一个求距离最近的问题,那么当tmp=sum-target>0时,显然说明和的值比target大了,应该让其更小,故对于排序后的数组将尾指针左移,显然,sum和target的大小便是此题中判断指针移动的条件

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例:

输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

题解:

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if(digits.size()==0) return {};
        unordered_map<char,string> map{
            {'2',"abc"},
            {'3',"def"},
            {'4',"ghi"},
            {'5',"jkl"},
            {'6',"mno"},
            {'7',"pqrs"},
            {'8',"tuv"},
            {'9',"wxyz"},
        };
        
        vector<string> ans;
        string tmpans;
        f(0,digits,tmpans,ans,map);
        return ans;
    }
    //注意参数必须加&
    void f(int layer,string& digits,string& tmpans,vector<string>& ans,unordered_map<char,string>& map){
        if(layer==digits.size()){
            ans.push_back(tmpans);
        }
        else{
            for(char tmp:map[digits[layer]]){
                tmpans.push_back(tmp);
                f(layer+1,digits,tmpans,ans,map);
                //回溯,将最后一位弹出
                tmpans.pop_back();
            }
        }    
    }

};

思路:当我们在题目中看到找出所有组合的字眼时,可以考虑使用回溯法
可以将回溯法想象成树的遍历:

可以看到,对树中任意两个节点的操作是完全相同的,都是取其下一层的三个字符分别连接在后面,于是我们可以用递归的思想:


//对第i层某节点a的操作
void f(第i层,其他参数){
    节点a.push_back(节点b);
    //对第i+1层节点b的操作
    f(第i+1,其他参数);
}

除去递归的思想,在使用回溯法时,还需要注意每次回溯都要将回溯掉的值“弹出”,即需要添上一行:

    第i层节点.pop_back();

如何判断何时进行回溯呢?本题中,由于需要找出所有组合,故在到达最后一层,也就是layer==digits.size()时,进行回溯:

    if(layer==digits.size())
            ans.push_back(底层某结点);
    //此时递归进行到最底部,结束,开始回退:
    (底层-1)层节点.pop_back();
    //将底层的第二个节点连接到对应的(底层-1)层节点的后面:
    对应的(底层-1)层节点.push_back(底层的第二个节点);

易错点:
1.注意!使用诸如string等类型为函数形参时,必须加&。不加引用的话,str则被复制一份,函数中对str的操作实质上是对其复制品的操作,所以即使在函数中修改了str,调用层的原str并不会被改变。
2.回溯时未进行弹出操作

18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] :

  • 0 <= a, b, c, d < n
  • a、b、c 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

题解:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        int len=nums.size();
        for(int i=0;i<len-3;i++)
            for(int j=i+1;j<len-2;j++){
                int head=j+1,tail=len-1;
                while(head<tail){
                    //注意这里的和可能会溢出int的范围,必须在左右侧都添加longlong
                    long long sum=(long long)nums[i]+nums[j]+nums[head]+nums[tail];
                    if(sum<target)
                        head++;
                    if(sum>target)
                        tail--;
                    if(sum==target){
                        bool ifrepeat=false;
                        for(int k=0;k<ans.size();k++){
                            if(ans[k][0]==nums[i] && ans[k][1]==nums[j] && ans[k][2]==nums[head] && ans[k][3]==nums[tail])
                                ifrepeat=true;
                        }
                        if(ifrepeat==true)
                            head++;
                        
                        else{
                            //注意这里的括号要用{}
                            ans.push_back( {nums[i],nums[j],nums[head],nums[tail]} );      
                            head++;
                        }
                    }
                }       
            }
        return ans;
    }
};

思路:
使用双指针法的思路不再赘述,这里写两个易错点:
1.求和那一步,笔者一开始左边的类型使用int sum,运行报错,提示integer overflow,超出整数范围,于是改写成long long sum,依然报错,这是因为等号左边的类型不会影响等号右边,右边由两个int相加,得到的类型还是int,但是数值却超出了int的范围,故需要将右边的至少任一个数的类型改为long long
2.在对一个vector<vector>.pusu_back时,使用{}而非[]

19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

题解:

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* h=head,*t=head;
        for(int i=0;i<n;i++){
            t=t->next;
        }
        if(t == nullptr) return head->next;
        while(t->next != nullptr){
            h=h->next;
            t=t->next;
        }
        ListNode* tmp=h->next;
        h->next=tmp->next;

        return head;
    }
};

思路:
快慢指针的解法,在这里讲一个易错点,就是容易对空指针进行操作,从而报错:

    for(int i=0;i<n;i++){
            t=t->next;
        }
    while(t->next != nullptr){}

这里,当输入数组的长度和倒数的n相等时,t会在for循环的最后被赋值为末尾的空指针,再进入while循环时对空指针求->next,会报错,所以需要在中间加一句:


    if(t == nullptr) return head->next;

20. 有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

示例:

输入:s = “()[]{}”
输出:true

题解:

class Solution {
public:
    bool isValid(string s) {
        stack<char> sta;
        for(int i=0;i<s.size();i++){
            if(!sta.empty() && ( (s[i]==')'&&sta.top()=='(') || (s[i]==']'&&sta.top()=='[') || (s[i]=='}'&&sta.top()=='{') ) )
                sta.pop();
            else sta.push(s[i]);      
        }
        return sta.empty()?true:false;
    }
};

思路:
使用栈,正好满足题目要求,这里提一个易错点:
stack.top()函数不能对空栈使用,否则会报错,故第一个if语句里先写了!sta.empty()

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值