● 454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ● 18. 四数之和 ● 总结

四数相加II

将四层for循环暴力优化成2次2层遍历,第一次2层遍历将a+b的值存入map(重复则次数+1),第二次二层遍历将-(c+d)的值进行查找,如果匹配,则cnt += map存的次数值,注意是n次数不是+1,对应n种组合方式。

C++

C+往map里插入元素不用先Insert,直接访问索引+就好。

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int,int> res;
        for(int a : nums1) {
            for(int b : nums2) {
                res[a + b]++;
            }
        }
        
        int cnt = 0;
        for(int c : nums3) {
            for(int d : nums4) {
                if(res.find(-(c + d)) != res.end()) {
                    cnt += res[-(c + d)];
                }
            }
        }
        return cnt;
    }
};

C#

添加Dic新纪录的时候记得判断一下存不存在,C+可以不判断,C#需要。

public class Solution {
    public int FourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Dictionary<int, int> dic = new Dictionary<int, int>();
        foreach(int a in nums1) {
            foreach(int b in nums2) {
                if(dic.ContainsKey(a + b)) {
                    dic[a + b] ++;
                } else {
                    dic.Add(a + b, 1);
                }
            }
        }
        int cnt = 0;
        foreach(int c in nums3) {
            foreach(int d in nums4) {
                if(dic.ContainsKey(-(c + d))) {
                    cnt += dic[-(c + d)];
                }
            }
        }
        return cnt;
    }
}

赎金信

和昨天有效字母的异位词差不多,有点不同在于只要遍历待匹配string出现单词不用就return false。

字母就26个匹配值,数组更优。

alt

C++

数组。

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int res[26] = {0};
        for(int i  = 0; i < magazine.size(); i++) {
            res[magazine[i] - 'a']++;
        }
        for(int i = 0; i < ransomNote.size(); i++) {
            if(res[ransomNote[i] - 'a'] == 0) {
                return false;
            } else {
                res[ransomNote[i] - 'a']--;
            }
        }
        return true;
    }
};

C#

Dic。

public class Solution {
    public IList<IList<int>> ThreeSum(int[] nums) {
        List<IList<int>> res = new List<IList<int>>();
        Array.Sort(nums);
        for (int i = 0; i< nums.Length - 2; i++) {
            if(nums[i] > 0) break;
            if(i > 0 && nums[i] == nums[i - 1]) continue;
            int left = i + 1;
            int right = nums.Length - 1;
            while(right > left) {
                if(nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                } else if(nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                } else {
                    res.Add(new List<int>{nums[i], nums[left], nums[right]});
                    while(left < right && nums[left] == nums[left + 1]) left++;
                    while(left < right && nums[right] == nums[right - 1] ) right--;
                    right--;
                    left++;
                }
            }
        }
        return res;
    }
}
public class Solution {
    public bool CanConstruct(string ransomNote, string magazine) {
        Dictionary<int, int> dic = new Dictionary<int, int>();
        for(int i = 0; i < magazine.Length; i++) {
            if(dic.ContainsKey(magazine[i] - 'a')) {
                dic[magazine[i] - 'a']++;
            } else {
                dic.Add(magazine[i] - 'a', 1);    
            }
        }
        for(int i = 0; i < ransomNote.Length; i++) {
            if(!dic.ContainsKey(ransomNote[i] - 'a')) {
                return false;
            } else {
                if(dic[ransomNote[i] - 'a'] == 0) return false;
                dic[ransomNote[i] - 'a']--;
            }
        }
        return true;
    }
}

三数之和

掌握不是很好,二刷注意。

  • 遍历指针i去重是向前(上次遍历的方向)去。
  • left,right找到结果后在去重,不要提前去重,会漏掉-1,-1,2这种情况。
  • 双指针要求数组是排序的,所以二数之和不能用双指针,哈希法去重很麻烦不适用这题。
  • 注意下如果循环条件剪了- 2, -1,C++里nums.size()是无符号整型, 如果nums.size()小于2, nums.size()- 2变成了负数,就会进入循环,然后越界,要单独if(nums.size() < 4) return res;C#就不用。

C++

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]) 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(vector<int>{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;
    }
};

四数之和

在三数的基础上再套一层循环,注意剪枝由>0变成>target&& n1>= 0,尤其是后面限定正数是很重要的,因为如果下一个数如果是负数,和反而可能变小接近target,要限定当前和>=0,由于排序后面的数肯定也>=0,=0是可以的,因为即使四个数都是0相加等于0,target <0也不符合,剪掉即可。

  • 两层循环分两次剪枝。
  • 双指针法一定要先对数组排序。
  • 四数相加可能会溢出,注意long处理一下。
class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size() - 1; i++) {
            int n1 = nums[i];
            if (n1 > target && n1 >= 0) {
                break;
            }
            if (i > 0 && n1 == nums[i - 1]) {
                continue;
            }
            for (int j = i + 1; j < nums.size(); j++) {
                int n2 = nums[j];
                if ((long)n1 + n2 > target && (long)n1 + n2 >= 0) {
                    break;
                }
                if (j > i + 1 && n2 == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = nums.size() - 1;
                while (left < right) {
                    long sum = (long)n1 + n2 +nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        res.push_back(vector<int>{n1, n2, nums[left], nums[right]});
                        while (left < right && nums[left] == nums[left + 1]) left++;
                        while (left < right && nums[right] == nums[right - 1]) right--;
                        left++;
                        right--;
                    }
                }
            }
        }
        return res;
    }
};

C#

List/<IList/>的定义方式注意一下,不允许List/<List/<int//>>强转。

public class Solution {
    public IList<IList<int>> FourSum(int[] nums, int target) {
        List<IList<int>> res = new List<IList<int>>();
        Array.Sort(nums);
        for (int i = 0; i < nums.Length - 3; i++) {
            int n1 = nums[i];
            if (n1 > target && n1 >= 0) break;
            if (i > 0 && n1 == nums[i - 1]) continue;
            for (int j = i + 1; j < nums.Length - 2; j++) {
                int n2 = nums[j];
                if (n1 + n2 > target && n1 + n2 >= 0) break;
                if (j > i + 1 && n2 == nums[j - 1]) continue;
                int left = j + 1;
                int right = nums.Length - 1;
                while (left < right) {
                    long sum = (long)n1 + n2 + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        res.Add(new List<int>{n1, n2, nums[left], nums[right]});
                        while (left < right && nums[left] == nums[left + 1]) left++;
                        while (left < right && nums[right] == nums[right - 1]) right--;
                        left ++;
                        right--;
                    }
                }
            }
        }
        return res;
    }
}

二刷

四数之和2

本题是独立的四个数组排列组合,并不需要考虑重复问题,与四数之和1不同,这是使用哈希方便的大前提。

注意两次双层循环,第二次找到目标ans累加的是map的value,对应n种组合。

  class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> data;
        for (int a : nums1) {
            for (int b : nums2) {
                data[a + b]++;
            }
        }
        int ans = 0;
        for (int c : nums3) {
            for (int d : nums4) {
                if (data.find(-(c + d)) != data.end()) {
                    ans += data[-(c + d)];
                }
            }
        } 
        return ans;
    }
};

赎金信

先记录一下magazine的字符出现次数,在遍历ransomeNote,如果没出现或者次数用光了都返回false。

  class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map<char, int> data;
        for (char it : magazine) {
            data[it]++;
        }
        for (char it : ransomNote) {
            if (data.find(it) != data.end()) {
                data[it]--;
                if (data[it] == -1) {
                    return false;
                }
            } 
            else {
                return false;
            }
        }
        return true;
    }
};

15 三数之和

  • 排序是为了适用双指针移动,对于每个i(第一个数),排序后left和right有且只有一种相加=0的情况(可能有重复,但去重了)

  • a去重要向前去,否则会把元素重复的情况剪去了,而题目只要求没有重复得三元组。

  • b和c去重要放在收获结果之后,否则有可能直接left和right相遇结果还没有收获。

  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) return ans;
            //a去重
            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--;
                }
                if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                }
                if (left < right && nums[i] + nums[left] + nums[right] == 0) {
                    ans.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    left++;
                    right--;
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    while (left < right && nums[right] == nums[right + 1]) right--;  
                }
            }
        }
        return ans;
    }
};

四数之和

注意一下剪枝 >0 变成 >target;剪枝b的时候是> i + 1(向前剪枝留一位);四数相加会溢出用long。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> res;
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] > target && (nums[i] > 0 || target > 0)) break;
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            for (int j = i + 1; j < nums.size(); j++) {
                if (nums[i] + nums[j] > target && (nums[i] + nums[j] > 0 || target > 0)) break;
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                int left = j + 1;
                int right = nums.size() - 1;
                while (left < right) {
                    if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target) right--;
                    else if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target) left++;
                    else {
                        res.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
                        left++;
                        right--;
                        while (left < right && nums[left] == nums[left - 1]) left++;
                        while (left < right && nums[right] == nums[right + 1]) right--;
                    }
                }
            }
        }
        return res;
    }
};

总结

哈希之前用C#的Dictionary挺多,哈希法有三类:数组,set,map,当题目有判断元素是否在集合中出现过,就要想到用哈希。

  • 数组,哈希值范围确定,比如字母26个哈希值,相当于模拟map,优先选择。
  • set,只需要存是否有key,比如判断一个集合是否包含一个数。
  • map,不仅需要存key,还要存value,比如判断一个集合收包含一个数并存他出现的次数或者下标。

map,set各有三类,使用也有考究,如果是无序,不重复key用unordered_(map/set),有序不重复用set/map,有序重复用multi_set/map,优先考虑使用unordered_(map/set)效率最高。实际上这两天的题只用了unordered,其他还需要练。

alt

alt

三数之和,四数之和并不适用哈希,因为要对结果数组进行去重(哈希的话会很慢超时),双指针去重两步,先去遍历指针,再去left,right,二刷还需要多理解,同时看一下哈希去重到底麻烦在哪(三刷看吧hhhh)。