代码随想录第七天 LeetCode 454、383、15、18 (哈希表、双指针)

454. 四数相加 II

之前做过使用hash表的1. 两数之和,使用的是unordered_map,且只用了一个for循环的遍历,本题是四个数组,所以暴力解法是四个for循环,那么时间复杂度就是O(n^4),很明显是非常耗时的,所以使用unordered_map来牺牲空间换取运算时间。
大体思路就是首先使用一个for循环遍历前两个数组,将其和存入map中,然后使用一个嵌套for循环遍历后两个数组再找到符合条件的答案。下面给出代码:

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        int ans = 0;
        unordered_map<int,int> map;
        for(int i = 0; i < nums1.size(); i++){
            for(int j = 0; j < nums2.size(); j++){
                map[nums1[i] + nums2[j]]++;
            }
        }
        for(int i = 0; i < nums3.size(); i++){
            for(int j = 0; j < nums4.size(); j++){
                if(map.find(0-(nums3[i] + nums4[j])) != map.end()){
                    ans += map.find(0-(nums3[i] + nums4[j]))->second;
                }
            }
        }

        return ans;
    }
};

本题需要注意的是ans += map.find(0-(nums3[i] + nums4[j]))->second;。这段答案计数的代码加的是map中存放的value值,也就是在遍历前两个数组时所有满足条件的两数之和,因为本题是不需要去重的,所以应该加上所有的可能结果。

383. 赎金信

在这里插入图片描述
本题和之前做过的242. 有效的字母异位词有点相似,因为考虑到跟字母是否出现以及出现次数有关,所以用哈希表。跟242有区别的是,在遍历magzine的时候,一旦出现不存在ransomNote中的字母就可以直接跳出循环返回false。下面给出使用数组作为哈希表的代码:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        int record[26] = {0};
        for(char c : magazine){
            record[c - 'a']++;
        }
        for(char c : ransomNote){
            record[c - 'a']--;
            if(record[c - 'a'] < 0) return false; //说明magazine中不含ransom的字母,false
        }
        return true;
    }
};

下面再给出使用unordered_map的代码:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        if(ransomNote.size() > magazine.size()) return false;
        unordered_map<char,int> umap;
        for(char c : magazine){
            umap[c]++;
        }
        for(char c : ransomNote){
            if(umap.find(c) != umap.end() && umap.find(c)->second != 0) umap.find(c)->second--;
        }
        int sum = 0;
        for(auto it = umap.begin(); it != umap.end(); it++){
            sum += it->second;
        }
        if(sum == magazine.size() - ransomNote.size()) return true;
        return false;

    }
};

在这里插入图片描述
上面的是使用数组,下面的是使用hash map,可以发现在数据量较小的时候,数组和map的内存消耗基本差不多。

15.三数之和

本题和下一题四数之和的难度相对于之前上升了不少,一方面是细节,还有一方面是去重和剪枝的操作。
在这里插入图片描述
在一个数组中找到不重复的三元组,就涉及到复杂的去重操作。

  • 哈希法
    如果使用hash法来写,思路大体是:使用一个嵌套for循环遍历数组,然后在内层循环的时候将不满足条件的元素存入hash table中,如果在内层循环找到符合条件的元素,还需要进行去重操作。下面看一下代码:
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        for(int i = 0; i < nums.size(); i++){
            if(nums[i] > 0) break; //剪枝
            if(i > 0 && nums[i] == nums[i - 1]) continue; //第一次去重
            unordered_set<int> set;
            for(int j = i + 1; j < nums.size(); j++){
                if(j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2]){
                    continue; //第二次去重
                }
                int c = 0 - nums[i] - nums[j];
                if(set.find(c) != set.end()){
                    ans.push_back({c , nums[i] , nums[j]});
                    set.erase(c); //第三次去重
                }else{
                    set.insert(nums[j]);
                }
            }
        }
        return ans;
    }
};

这里需要注意的是,在去重操作时,需要考虑元素下标是否有意义,并且应该是if(nums[i] == nums[i - 1])这样比较,与前一个对比,否则会造成漏解,下面的双指针法去重时一样需要注意这点。

  • 双指针法
    需要补充的一点是,所有的剪枝和去重的逻辑都是建立在数组有序的情况下,所以第一件事应该是sort(nums.begin(),nums.end());,给数组排序。
    双指针法的思路大体是:遍历数组时,设置一个left和一个right指针,分别指向i + 1和数组末尾,然后就是根据条件移动两个指针,涉及去重的操作也是通过移动指针来解决。代码如下,添加了部分注释:
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end()); //排序
        for(int i = 0; i < nums.size(); i++){
            if(nums[i] > 0) break; //剪枝
            if(i > 0 && nums[i] == nums[i - 1]) continue; //去重
            int left = i + 1;
            int right = nums.size() - 1;
            //双指针逻辑
            while(left < right){
                if((nums[i] + nums[left] + nums[right]) > 0){
                    right--;
                    continue;
                }else if ((nums[i] + nums[left] + nums[right]) < 0){
                    left++;
                    continue;
                }else{
                    ans.push_back({nums[i] , nums[left] , nums[right]});
                    while(left < right && nums[right] == nums[right - 1]) right--; //去重
                    while(left < right && nums[left] == nums[left + 1]) left++; //去重
                    right--; //记得在去重完成或者添加答案三元组后移动指针
                    left++;
                }
            }
        }
        return ans;
    }
};

下面是两种做法的对比:
在这里插入图片描述
上面的是双指针法,下面的是hash法,可以发现,当数据量以及数据大小增加时,hash法占用的内存和计算hash映射函数的时间大大增加。

18. 四数之和

四数之和和上一题三数之和其实差不多,只不过多了一层循环,多了一次去重操作,并且多了一些细节,直接用代码解释:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ans;
        sort(nums.begin(),nums.end());
        for(int k = 0; k < nums.size(); k++){
            if(nums[k] > target && nums[k] > 0 && target > 0) break;
            if(k > 0 && nums[k] == nums[k - 1]) continue; //k的去重
            for(int i = k + 1; i < nums.size(); i++){
                if(i > k + 1 && nums[i] == nums[i - 1]) continue; //i的去重
                int left = i + 1;
                int right = nums.size() - 1;
                while(right > left){
                    if((long) nums[k]+nums[i]+nums[left]+nums[right] > target){
                        right--;
                        continue;
                    }else if((long) nums[k]+nums[i]+nums[left]+nums[right] < target){
                        left++;
                        continue;
                    }else{
                        ans.push_back({nums[k],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++;
                        left++;
                        right--;
                    }
                    
                    
                }
            }
        }
        return ans;
    }
};

需要注意的点:

  • 对i去重时,判断条件if(i > k + 1 && nums[i] == nums[i - 1])可以看到是i > k + 1
  • 在双指针逻辑中,对条件的判断时if((long) nums[k]+nums[i]+nums[left]+nums[right] > target)发现前面加了一个(long),这样做的目的是防止数据溢出,因为几个大型int型变量相加可能会导致超出int所能表示的数据范围。

其他的点就是,当遇到这种需要频繁循环的题,提交时多多少少会出现bug,所以最笨的办法就是根据自己的逻辑拿一张白纸把循环过程写出来,比如i > k + 1这个条件我自己一开始写的时候没有注意到这个细节,模拟一遍之后才发现了问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值