赞
踩
参考链接:代码随想录
思路:本题和两数之和感觉类似,四个数我第一想到的是将其拆分成a+b和c+d,由于只需要返回次数,我只需要将a+b的次数保存到哈希表m1中,c+d保存到m2中,然后在s2中找-a-b的次数,将两个哈希表的次数相乘得到结果。时间复杂度O(n^2)。
class Solution { public: int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) { unordered_map<int,int> m1,m2; int n=nums1.size(); for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ auto it=m1.find(nums1[i]+nums2[j]); if(it==m1.end()){//没有找到 m1[nums1[i]+nums2[j]]=1; } else{ m1[nums1[i]+nums2[j]]++; } } } for(int i=0;i<n;i++){ for(int j=0;j<n;j++){ auto it=m2.find(nums3[i]+nums4[j]); if(it==m2.end()){//没有找到 m2[nums3[i]+nums4[j]]=1; } else{ m2[nums3[i]+nums4[j]]++; } } } int sum=0; for(auto it1:m1){ auto it2=m2.find(-it1.first);//找-a-b if(it2!=m2.end()){ sum+=it1.second*it2->second; } } return sum; } };
看了标答,发现还有许多简化空间。首先只需要一个map用来存a+b;然后对a+b的second一开始初始化就为0,所以直接用++就行,不需要额外来一个if;然后对数组遍历可以使用 int a:A
更快一些;对c+d的遍历,直接对每次-c-d在map出现的时候进行count的计数,不用额外把所有c+d的结果存放到一个单独哈希表中。
标答:
class Solution { public: int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) { unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数 // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中 for (int a : A) { for (int b : B) { umap[a + b]++; } } int count = 0; // 统计a+b+c+d = 0 出现的次数 // 在遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。 for (int c : C) { for (int d : D) { if (umap.find(0 - (c + d)) != umap.end()) { count += umap[0 - (c + d)]; } } } return count; } };
运行标答后时间从225ms减少到174ms。
思路:本题其实就是ransomNote中的每个字母数必须少于magazine中的每个字母数,可以使用长度为26的数组作为哈希表,用于存储字母数,首先存magazine的,然后再遍历ransomNote,遇到则–,最后查看表中有没有负数,如果没有则说明可以。时间复杂度O(n^2)。
class Solution { public: bool canConstruct(string ransomNote, string magazine) { int record[26]={0}; for(int i=0;i<magazine.size();i++){ record[magazine[i]-'a']++; } for(int i=0;i<ransomNote.size();i++){ record[ransomNote[i]-'a']--; } for(int i=0;i<26;i++){ if(record[i]<0){ return false; } } return true; } };
标答也做了细微简化,首先是一开始如果ransomNote长度更长,直接排除,然后在记录record的过程中就完成了bool值判断。
标答:
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; } };
暴力解法使用erase操作,时间复杂度O(n^2):
class Solution { public: bool canConstruct(string ransomNote, string magazine) { for (int i = 0; i < magazine.length(); i++) { for (int j = 0; j < ransomNote.length(); j++) { // 在ransomNote中找到和magazine相同的字符 if (magazine[i] == ransomNote[j]) { ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符 break; } } } // 如果ransomNote为空,则说明magazine的字符可以组成ransomNote if (ransomNote.length() == 0) { return true; } return false; } };
思路:本题使用哈希法比较困难,主要是去重过程不好处理,而两数之和的题目没有考虑去重问题。我也没想出更好的方法,看了答案才知道需要用到双指针法。
首先将数组排序,从小到大,快排时间复杂度O(nlogn),然后数组头放一个i用于遍历头,然后第二个位置和末尾分别放left和right,当三数之和小于0时,需要右移left,大于0,则左移right,直到两个相遇。时间复杂度O(n^2)。
class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { int i,left,right,sum; vector<vector<int>> ans; sort(nums.begin(),nums.end()); for(i=0;i<nums.size()-2;i++){//三个数不能重复 left=i+1; right=nums.size()-1; while(left<right){ sum=nums[i]+nums[left]+nums[right]; if(sum==0){ ans.push_back({i,left,right}); } else if(sum<0){ left++; } else{ right--; } } } return ans; } };
但写完代码后运行提示超出内存限制,发现是自己没考虑去重问题,题目没看清楚。修改后代码:
class Solution { public: vector<vector<int>> threeSum(vector<int>& nums) { int i,left,right,sum; vector<vector<int>> ans; sort(nums.begin(),nums.end()); for(i=0;i<nums.size()-2;i++){//三个数不能重复 if(nums[i]>0) {//首个元素大于0,不可能再有结果 return ans; } if(i>0&&nums[i]==nums[i-1]){//对i去重,如果有两个相邻的则移动,注意不是nums[i]==nums[i+1] continue; } left=i+1; right=nums.size()-1; while(left<right){ sum=nums[i]+nums[left]+nums[right]; if(sum==0){ while(left<right && nums[left]==nums[left+1]){ left++;//移动到最右边的一个重复left } while(left<right && nums[right]==nums[right-1]){ right--; } ans.push_back({nums[i],nums[left],nums[right]}); left++; right--; } else if(sum<0){ left++; } else{ right--; } } } return ans; } };
注意去重过程就好,尤其是对i的去重,必须是 if(i>0&&nums[i]==nums[i-1])
,我写出的答案和标答几乎没有区别。
思路:本题也是需要判断出全部不重复的组合,所以使用哈希法并不合适,依旧是使用和上一题一样的双指针法,时间复杂度为O(n^3)。
class Solution { public: vector<vector<int>> fourSum(vector<int>& nums, int target) { int i,j,left,right,sum; vector<vector<int>> ans; sort(nums.begin(),nums.end()); for(int i=0;i<nums.size()-3;i++){//实际上这里就写nums.size()也行,反正到不了 if(nums[i]>target && nums[i]>=0){//这里剪枝必须保证nums[i]>=0,所以能剪的少一些 return ans; } if(i>0&&nums[i]==nums[i-1]){ continue; } for(int j=i+1;j<nums.size()-2;j++){ if(nums[i]+nums[j]>target && nums[i]+nums[j]>=0){//剪枝 break; } if(j>i+1&&nums[j]==nums[j-1]){ continue; } left=j+1; right=nums.size()-1; while(left<right){ sum=nums[i]+nums[j]+nums[left]+nums[right]; if(sum==target){ while(left<right && nums[left]==nums[left+1]){ left++; } while(left<right && nums[right]==nums[right-1]){ right--; } ans.push_back({nums[i],nums[j],nums[left],nums[right]}); left++; right++; } else if(sum<target){ left++; } else{ right--; } } } } return ans; } };
运行出现runtime error,发现错误是输入为[0],target=0时出错,错误原因是堆溢出。调试发现输入[0]时,nums.size()虽然为1,但nums.size()-3却变为随机数,经查询得知size()返回的是size_t类型,我们将其转换为int。故以后遇到需要使用size()来进行运算的,我们最好首先进行 n=nums.size()
。
然后还有一个问题是sum大小溢出,我们需要将sum定义成long类型,因为题目定义了nums[i]的范围为10^9,故需要定义long,以后需要注意观察题目要求的取值范围。然后是类型转换的具体写法,必须是 (long)nums[i]+nums[j]+nums[left]+nums[right]
,即将第一个数转换为long,后面的加法long+int会自动转换,如果写的 long(nums[i]+nums[j]+nums[left]+nums[right])
,会报错,因为四个数相加的结果已经溢出了,再转换就晚了。
修改后的答案:
class Solution { public: vector<vector<int>> fourSum(vector<int>& nums, int target) { int i,j,left,right; int n=nums.size(); long sum; vector<vector<int>> ans; sort(nums.begin(),nums.end()); cout << nums.size()-3 << endl; for(int i=0;i<n-3;i++){//实际上这里就写nums.size()也行,反正到不了 if(nums[i]>target && nums[i]>=0){//这里剪枝必须保证nums[i]>=0,所以能剪的少一些 return ans; } if(i>0&&nums[i]==nums[i-1]){ continue; } for(int j=i+1;j<n-2;j++){ if(nums[i]+nums[j]>target && nums[i]+nums[j]>=0){//剪枝 break; } if(j>i+1&&nums[j]==nums[j-1]){ continue; } left=j+1; right=nums.size()-1; while(left<right){ (long)nums[i]+nums[j]+nums[left]+nums[right];//必须这么写 if(sum==target){ while(left<right && nums[left]==nums[left+1]){ left++; } while(left<right && nums[right]==nums[right-1]){ right--; } ans.push_back({nums[i],nums[j],nums[left],nums[right]}); left++; right--; } else if(sum<target){ left++; } else{ right--; } } } } return ans; } };
我写的甚至比标答还简洁些,主要是对两层for循环的边界做了一个缩小。
对于哈希表,主要有数组、set、map三类,其中数组使用于取值范围固定的情况,set则适用于取值范围不固定,而map则需要记录其他信息,当除了记录key外,还需要记录value的时候,数组和set就不够用了,需要map,但能用前两个尽量别用map,空间占用大。在需要去重,十分麻烦的时候,使用双指针法有时会比哈希法更有效。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。