参考链接:代码随想录
思路:本题和两数之和感觉类似,四个数我第一想到的是将其拆分成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,空间占用大。在需要去重,十分麻烦的时候,使用双指针法有时会比哈希法更有效。