代码随想录day7 | 第三章 哈希表part02

四数相加

题目关键是不用去重,并且要遍历4次数组(时间复杂度是n^4),如果用哈希表map来分开两组两组来遍历则能将时间复杂度降低为n的二次方
主要思想在于用map来存储前两组遍历完后的和的次数,然后在下一组用target-(c+d)来遍历剩下的两组,所以能满足不用去重的要求。

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> umap;
        int count = 0;
        for(int a : nums1)
        {   
            for(int b : nums2)
            {
                umap[a+b]++;
            }
        }

        for(int c : nums3)
        {
            for(int d : nums4)
            {
                if(umap.find(0 - (c+d)) != umap.end())
                {
                    count += umap[0 - (c+d)];
                }
            }
        }

        return count;
    }
};

赎金信

关键判断b是否能包含a就行了,b是大集合,a是小集合
用哈希表来记数,key是字符串的元素,value是出现的次数,先遍历一遍大集合把所有字符出现的次数给统计一遍,再去小集合相减,当a出现了b不存在的字符时,用[key]来访问会默认给value为0,此时在判断前先–就能保证满足了当a出现了b不存在的字符时这种情况,并且也能满足a是小集合b是大集合的情况。
需要判断的两种情况:

  1. 小集合a中出现了b不存在的字符
  2. 小集合的字符多于大集合b中的字符
    最优雅的解法:
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> umap;
        for(int i=0; i < magazine.size(); i++)
        {
            umap[magazine[i]]++;
        }
        for(int i = 0; i < ransomNote.size(); i++) {
            if (--umap[ransomNote[i]] < 0) {
                return false;
            }
        }
        return true;
    }
};

接下来讲一个反面教材,应该直接用 [key] 来访问value而不是先find再用 [key] 来访问,因为find会先检查[key] 是否存在; 而直接用[key]来访问,当 [key] 不存在的时候则会初始化value的int为, 所以find的不满足当a出现了b中不存在的字符的情况,直接用[key]然后先 – 来判断就好了

!!!反面教材!!!
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> umap;
        for(int i = 0; i < magazine.size(); i++) {
            umap[magazine[i]]++;
        }
        for(int i = 0; i < ransomNote.size(); i++) {
        	//当ransomNote出现了magazine不存在的字符的时候就失灵了
            if(umap.find(ransomNote[i]) != umap.end()) {
                umap[ransomNote[i]]--;
            }
        }
        for(const auto& pair : umap) {
            if(pair.second < 0) {
                return false;
            }
        }
        return true;
    }
};

正确解法

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> umap;
        for(int i = 0; i < magazine.size(); i++) {
            umap[magazine[i]]++;
        }
        for(int i = 0; i < ransomNote.size(); i++) {
            umap[ransomNote[i]]--;
        }
        for(const auto& pair : umap) {
            if(pair.second < 0) {
                return false;
            }
        }
        return true;
    }
};

卡哥的代码

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        //add
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        for (int i = 0; i < magazine.length(); i++) {
            // 通过record数据记录 magazine里各个字符出现次数
            record[magazine[i]-'a'] ++;
        }
        for (int j = 0; j < ransomNote.length(); j++) {
            // 遍历ransomNote,在record里对应的字符个数做--操作
            record[ransomNote[j]-'a']--;
            // 如果小于零说明ransomNote里出现的字符,magazine没有
            if(record[ransomNote[j]-'a'] < 0) {
                return false;
            }
        }
        return true;
    }
};

三数之和

用三个指针来表示位置,关键是去重

  1. 看a的去重,举个例子,-2,-1,-1,0 ,当a指向下标0的时候-2值是合法的,指向下标1的时候-1值也是合法的,但是指向下标2的时候第二个-1值不合法,此时是出现了第二个-1了,而第一个-1已经参与使用了,因此第二个-1要和第一个-1来对比做去重,因此是当前的i要和i-1做去重。
  2. 看b和c的去重,b是左指针要往右移,c是右指针要往左移,所以需要判断b要和b+1对比,c要和c-1对比,一直移动到最后一个去重的元素,然后b++,c–指向下一个不同的元素。

还有一些细节的判断,类似于安全判断需要注意,请看代码。
比如a去重的时候要判断 i>0 ,不然就会检查nums[0] == nums[-1],nums[-1]超出了数组的范围,这会导致未定义行为(Undefined Behavior),可能会导致程序崩溃或其他错误。
b c去重的时候while要判断right>left

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        for(int i=0; i<nums.size(); i++)
        {
            if(nums[i] > 0)
            {
                return result;
            }

            if(i>0 && nums[i] == nums[i-1])    //a的去重
            {
                continue;
            }

            int left = i + 1;
            int right = nums.size() - 1;

            while(left < right)
            {
                if(nums[i] + nums[left] + nums[right] > 0)
                {
                    right--;
                }else if(nums[i] + nums[left] + nums[right] < 0)
                {   
                    left++;
                }else if(nums[i] + nums[left] + nums[right] == 0)
                {
                    result.push_back(vector<int>{nums[i],nums[left],nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while(right > left && nums[left]==nums[left+1])
                    {
                        left++;
                    }
                    while(right > left && nums[right] == nums[right-1])
                    {
                        right--;
                    }
                    left++;
                    right--;
                }
            }
        }
        return result;
    }
};

四数之和

多了一层for循环,并且target可能是负数的,所以剪枝操作不太一样,并且第二层的剪枝需要注意一下(要用前两个的和),毕竟剪枝是为了方便left和right的判断,要是符合剪枝条件了则没有必要再循环下去。
还有去重操作值得注意
第一层的 k>0是为了防止刚开始的nums[0]和nums[-1] ,访问-1就是越界了,正确写法 if(k >0 && nums[k] == nums[k-1])
第二层的 i>k+1 ,那么在内层第一次迭代时(即 i = k + 1),会比较nums[i]和nums[k]。这是不必要的,因为在i = k + 1时,我们没有前面的i可以比较。通过 i>k+1 确保了在 i 层循环时避免了nums[i]和nums[k]这种不必要的检查。
最后,四数之和记得转为long,不然提交的用例很大的话则通过不了。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>>result;
        sort(nums.begin(), nums.end());
        for(int k=0; k<nums.size(); k++)
        {
        	//确保为正数才能剪枝
            if(nums[k]>target && nums[k]>=0)
            {
                break;
            }
			//k>0是为了防止nums[0]和nums[-1]
            if(k >0 && nums[k] == nums[k-1])
            {
                continue;
            }

            for(int i=k+1; i<nums.size(); i++)
            {
            	//这里的剪枝是两数之和
                if(nums[k] + nums[i] > target && nums[k] + nums[i] >= 0)
                {
                    break;
                }

                if(i>k+1 && nums[i] == nums[i-1])
                {
                    continue;
                }

                int left = i+1;
                int right = nums.size() - 1;

                while(left < right)
                {
                    if((long) nums[k] + nums[i] + nums[left] + nums[right] > target)
                    {
                        right--;
                    }else if((long) nums[k] + nums[i] + nums[left] + nums[right] < target)
                    {
                        left++;
                    }else if((long) nums[k] + nums[i] + nums[left] + nums[right] == target)
                    {
                        result.push_back(vector<int>{nums[k],nums[i],nums[left],nums[right]});
                        while(right > left && nums[right] == nums[right-1])
                        {
                            right--;
                        }
                        while(right > left && nums[left] == nums[left+1])
                        {
                            left++;
                        }

                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }
};
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值