代码随想录刷题day7|LeetCode454 四数相加Ⅱ、LeetCode383 赎金信、LeetCode15 三数之和、LeetCode18 四数之和

454 四数相加Ⅱ

力扣题目链接

思考:

像个排列组合的题目(bushi

四个数组中找出四元组使它们的和为0,有点像字符异位相同匹配,也就是两个表元素进行对比,同时为了降低时间复杂度,要考虑将数组合并,比如前两个数组合并、后两个数组合并,这样合并后的循环时间复杂度为O(n^{2}),即两个平方的加和。如果三个数组合并对比剩下一个数组,时间复杂度为O(n^{3}),不是最优合并。

题目有几个要注意的地方:

1、只需考虑满足条件的四元组的个数,不用输出具体的四元组;

2、四元组中每个位置上的数可以是重复的,不需要去重;

3、先将前两个数组求和(num1+num2),每次求和得到的结果存进哈希表中,便于后两个数组求和(num3+num4)后在哈希表中匹配是否存在四数之和为0。由于num1+num2+num3+num4=0,所以num3+num4=-(num1+num2),注意在后两个数组和与前两个数组和匹配时要将(num3+num4)取反。

我的代码:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        //四个数组中找出四元组使它们的和为0,有点像字符串异位相同匹配
        //为了降低时间复杂度,要考虑将数组合并,比如前两个数组合并、后两个数组合并
        std::unordered_map<int,int> myMap;
        //合并前两个数组,并统计两数组中数字之和以及它们的出现次数,存进map中
        for(auto num1:nums1)
            for(auto num2:nums2){
                if(myMap.find(num1+num2)==myMap.end())
                    myMap.insert({num1+num2,1});
                else {
                    auto cur=myMap.find(num1+num2);
                    cur->second++;
                }
            }
        //关于后两个数组的和,查找map有无(a+b)能与之结合为0,并统计和为0的四元组的数量
        int count=0;
        for(auto num3:nums3)
            for(auto num4:nums4){
                if(myMap.find(-num3-num4)!=myMap.end()){
                    auto cur=myMap.find(-num3-num4);
                    count+=(cur->second);
                }
            }
        return count;

    }
};
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2

383 赎金信

力扣题目链接

思考:这个和字符异位那题思路差不多,也是 有限个字符 这样的一个范围条件,所以还是用数组解决最简洁。

题目是判断ran的字符是否能够用mag中的字符完全表示,用集合来表示就是ran是mag的子集,mag的内容包含ran的内容。所以还是要先将ran的字符保存进26个小写字母的数组result中,记录ran中的字符。

然后再遍历mag,并判断遍历到的字符是否在result中有所记录,若有记录则使记录的数量减一。
最后判断所有记录都被清零的话,意味着ran就是mag的子集,即ran能由mag里的字符构成
这样设计也能满足题目“magazine 中的每个字符只能在 ransomNote 中使用一次”的要求。

我的代码:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int result[26]={0};
        int count=0;
        //题目是判断ran的字符是否能够用mag中的字符完全表示,用集合来表示就是ran是mag的子集,mag的内容包含ran的内容
        //所以还是要先将ran的字符保存进26个小写字母的数组result中,记录ran中的字符
        for(int i=0;i<ransomNote.size();i++)
                result[ransomNote[i]-'a']++;
        //再遍历mag,并判断遍历到的字符是否在result中有所记录,若有记录则使记录的数量减一
        for(int i=0;i<magazine.size();i++){
            if(result[magazine[i]-'a']>0){
                result[magazine[i]-'a']--;
            }
        }
        //最后判断所有记录都被清零的话,意味着ran就是mag的子集,即ran能由mag里的字符构成
        for(int i=0;i<26;i++){
            if(result[i])
                return false;
        }
        return true;
    }
};

15 三数之和

力扣题目链接

思考:

哈希表写多了,看到这道题一上来就想用哈希表解决,然后bug一改就老实了。

这道题由于要求答案不能包含重复三元组,所以和四数相加不一样。必须要考虑去重问题。但是用哈希表写关于去重的细节很多,很难一下子考虑完全(反正我没完全通过所有样例)。

看了卡哥的解法给了一个解题的新思路,双指针法居然还能这么用.jpg

双指针法

(其实也不一定是严格的两个指针,也要灵活考虑加入一到两个指针)

三数之和,可以想到用三个指针指向数组中不重复的三个元素,来判断三者相加和是否为0。首先先将数组排为升序,第一个数的指针i可以看作数组的动态边界起点,不断前进,剩下两个指针left和right可以看作移动的滑动窗口

当第一个数当前取某个值时,由于数组已变为有序数组,数组左边的元素一定较小,右边的元素一定较大,三数之和大于0时则right向左移动、小于0则left向右移动,left和right就不断向内缩小,直到取完所有合适的三元组,然后再迭代第一个数,重复滑动窗口的操作。

其中去重的要点,一定要先将数组排序,这样一来数值相同的元素就会集中一起,便于相同元素跳过。当指针指向的元素周围有相同的元素,进行去重跳过判断的条件是:

        //第一个数字去重    
        if(i>0&&nums[i]==nums[i-1]) continue;

        //left和right去重
        while(right>left&&nums[right]==nums[right-1]) right--;
        while(right>left&&nums[left]==nums[left+1]) left++;

对于第一个数,判断位置是i和i-1的含义是,当前元素和前一个已遍历过的元素是否相同,意味着上一个元素或已被作为有效三元组push_back进结果数组中,或已被当作重复元素跳过,也就是上一个相同元素已经被操作过了,因此无需再考虑当前元素,可跳过。

滑动窗口两端的两个数则是指针与窗口内未访问的元素进行比较,相同则继续向内缩小范围。

注意不能有重复的三元组是指,不能出现所含元素完全相同的三元组,即不能为{[-1,0,1],[-1,1,0]},但是三元组中可以有数据重复(但下标不同)的元素,即{[0,0,0],[-1,-1,2]}。所以第一个数去重的判断不是i和i+1原因为:如果逻辑是 下一个元素和当前元素相同则不将当前元素放入结果数组考虑中,则不满足“三元组中可以有数据重复但下标不同的y”

我的代码:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //梦碎了。。。
        std::vector<vector<int>> result={};
        //用指针法,必须先将数组排序
        sort(nums.begin(),nums.end());
        for(int i=0;i<nums.size();i++){
            if (nums[0] > 0)
                break;
            //对第一个数去重
            if(i>0&&nums[i]==nums[i-1]) continue;
            int left=i+1;
            int right=nums.size()-1;
            while(right>left){
                if(nums[i]+nums[left]+nums[right]>0) right--;
                else if(nums[i]+nums[left]+nums[right]<0) left++;
                else {
                    result.push_back({nums[i],nums[left],nums[right]});
                    //对left和right指向的两个数去重
                    while(right>left&&nums[right]==nums[right-1]) right--;
                    while(right>left&&nums[left]==nums[left+1]) left++;
                    right--;
                    left++;
                }
            }
                
        }
        return result;
    }
};
/*
    没法实现去重的哈希版本:
    vector<vector<int>> threeSum(vector<int>& nums) {
        std::multimap<int,int> myMap;
        std::vector<vector<int>> result={};
        for(int i=0;i<nums.size();i++)
            myMap.insert({nums[i],i});
        for(int i=0;i<nums.size();i++)
            for(int j=i+1;j<nums.size();j++){
                int sum=nums[i]+nums[j];
                //sumMap.insert({sum,(i,j)});
                auto Find=myMap.find(-sum);
			    auto Count = myMap.count(-sum);
                while (Count) {
                    if (Find != myMap.end() && Find->second > j)
                        result.push_back({ nums[i],nums[j],Find->first });
                    Find++;
                    Count--;
                }
            }
        return result;

    vector<vector<int>> threeSum(vector<int>& nums) {
        std::multimap<int,pair<int,int>> sumMap;
        std::vector<vector<int>> result;
        for(int i=0;i<nums.size();i++)
            for(int j=i+1,j<nums.size(),j++){
                int sum=nums[i]+nums[j];
                sumMap.insert({sum,(i,j)});
            }
        for(auto sums:sumMap){
            if(sums.find())

        }
    }*/

*由于第一个数的遍历必须占用一个循环,后两个数又可以优化为双指针法,也只用占用一个循环,因此可以将时间复杂度从三重循环嵌套的O(n^{3})降低为O(n^{2})。

  • 时间复杂度: O(n^2)
  • 空间复杂度: O(1)

18 四数之和

力扣题目链接

思考:

这题在熟悉三数之和以后就很容易想到嵌套循环+双指针法:双指针遍历部分必定是多元组的最后两个数,且双指针法的时间复杂度都是O(n),即一层循环。但是既然是四数之和,自然迭代的指针也从三数之和的一个自由指针变成两个,所以也很容易可以得到四数之和的时间复杂度为O(n^{3})。

有个易错点:自然迭代指针的去重操作是continue,直接跳过本次循环,而不是指针++。

我的代码:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        std::vector<vector<int>> result = {};
        sort(nums.begin(), nums.end());
        //和三数之和有点相似的地方就是,数组上仍然设置一个区域使用双指针遍历,且仍然指向四元组中的后两个数
        //第一个指针仍然从头开始自然迭代遍历整个数组
        for (int s1 = 0; s1 < nums.size();s1++) {
            //if (nums[0] > target)
               // break;
            //第一个数的去重操作,注意是continue直接跳过本次循环,不是s1++
            if (s1 > 0 && nums[s1] == nums[s1 - 1]) continue;
            //第二个指针尚未进行双指针遍历,所以还是和s1一样自然迭代
            for (int s2 = s1+1; s2 < nums.size();s2++) {
                //第二个数的去重操作
                if (s2>s1+1 && nums[s2] == nums[s2 - 1]) continue;
                int b1= s2 + 1, b2 = nums.size() - 1;
                //从这里开始双指针遍历
                while (b2>b1) {
                    int sum1=nums[s1] + nums[s2];
                    long sum2=(long)target-nums[b1] - nums[b2];
                    if ( sum1>sum2) b2--;
                    else if (sum1<sum2) b1++;
                    else {
                        result.push_back({ nums[s1],nums[s2],nums[b1],nums[b2] });
                        while (b2 > b1 && nums[b2] == nums[b2 - 1]) b2--;
                        while (b2 > b1 && nums[b1] == nums[b1 + 1]) b1++;
                        b2--;
                        b1++;
                    }
                }
            }
        }
        return result;
    }
};
  • 时间复杂度: O(n^3)
  • 空间复杂度: O(1)

 *文章是本人刷题过程中的一些笔记和理解,记录的解析不一定足够清晰,也可能存在本人暂未意识到的错误,如有问题欢迎大家指出。文章中学习到的解法来自代码随想录的B站视频(哈希表4~6)以及代码随想录的学习网站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值