系列文章目录
前言
哈希表第二部分
454.四数相加II
Source: 题目
Note:
题目给定四个数组,要求满足
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
这里要用巧妙的方法:
-
我们将nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
分解为 nums1[i] + nums2[j] = 数字1
如果nums3[k] + nums4[l]是数字1的相反数,那么我们就满足了条件 -
所以如果把nums1和nums2的和作为key,和出现的次数作为value
-
这样遍历nums3+num4加和结果的时候在unordered_map中寻找如果出现加和结果的相反数,那么就说明有value个组合满足题意,count+=value
如此循环找到所有的目标组合!
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> sum1;
for (int i : nums1) {
for (int j :nums2) {
sum1[i + j]++;
}
}
int count = 0;
for (int k : nums3) {
for (int l :nums4) {
if (sum1.find(-(k + l)) != sum1.end()){
count = count + sum1[-(k + l)];
}
}
}
return count;
}
};
Tips:
383. 赎金信
Source: 题目
Note:题目给定两字符串ransomNote magazine
- 如果ransomNote可以被magazine内的字符表示(每个字符只能用一次)则返回true 反之为false
- 容易想到使用字典法,int record[26];来表示26个小写英文字母
使用[c - ‘a’]的相对值来获取数组的下标 - 我们很容易想到遍历ransomNote统计每个用到的字母数量,然后再遍历magazine给对应字符-1, 最后检查record如果有>0的字母个数,说明magazine没有满足ransomNote的需求,返回false
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26];
for (char c : ransomNote) {
record[c - 'a']++;
}
for (char c : magazine) {
record[c - 'a']--;
}
for (int num : record) {
if (num > 0) {
return false;
}
}
return true;
}
};
Tips:
15. 三数之和
Source: 题目
Note:题目给定一个数组,返回所有的nums[i] + nums[j] + nums[k] == 0.
这道题使用双指针法减少运算量非常典型
这道题非常值得仔细琢磨,特别是对于下标的处理的部分
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
int len = nums.size();
// 这里先排序非常重要!!!
sort(nums.begin(), nums.end());
if (len < 3 || nums[len-1] < 0 || nums[0] > 0) {
return res;
}
for (int i = 0 ; i < nums.size() - 2; i++) {
// 注意下面这个剪枝操作
if (i > 0 && nums[i] == nums[i-1]) continue;
int left = i + 1;
int right = nums.size() - 1;
// i不变,移动left right
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (nums[i] + nums[left] + nums[right] < 0) {
left++;
} else if(nums[i] + nums[left] + nums[right] > 0) {
right--;
}else{
res.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 res;
}
};
Tips:
18. 四数之和
Source: 题目
Note:题目基本同上一道题,但是是4个数字的和 = target而不是0
相比上一道题,这道题多了更多细节的控制:
内外循环的双重剪枝
剪枝操作的不同(注意三数之和为0 与 四数之和为target 的区别)
四数加和会溢出,应使用long强制转型
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
int len = nums.size();
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
// 这里的检查和后面的双层循环范围互相制约,以防下标越界
if (len < 4) {
return ans;
}
for (int i = 0 ; i < nums.size() - 3; i++) {
// 注意这里的*剪枝处理* 当遍历到某位正数大于target时候,说明此后永远sum>target不存在目标组合
if (nums[i] > target && nums[i] >= 0) {
break; // 这里使用break,统一通过最后的return返回
}
if( i > 0 && nums[i-1] == nums[i]) continue;
for (int j = i + 1; j < nums.size() - 2 ; j++) {
// 这里注意j的初始范围和循环保持一致 j > i + 1
if( j > i + 1&& nums[j-1] == nums[j]) continue;
int left = j + 1;
int right = nums.size() - 1;
// 要小心数组越界 这个while循环 既能在i j确定情况下移动l r寻找答案
// 又能避免数组下标越界,一旦越界,就进入下一次for j内循环
while (left < right){
// 注意注释的操作会导致溢出 原因是赋值之前就已经溢出
// long int sum = nums[i] + nums[j] + nums[left] + nums[right];
// 这里使用sum 而不是条件里面判断计算 可以减少计算量,
long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
if ( sum > target && left < right) {
right--;
}else if (sum < target && left < right){
left++;
}else{
ans.push_back({nums[i], nums[j], nums[left], nums[right]});
left++;
right--;
while (left < right && nums[left - 1] == nums[left]) left++;
while (left < right && nums[right] == nums[right + 1]) right--;
}
}
}
}
return ans;
}
};
Tips:
总结
今天主要详细理解了unordered_map的用法, 三数之和, 四数之和。加深理解双指针法的应用!要严格控制数组下标谨防越界!
DAY 7 Finished 撒花~