最简单的思路肯定是暴力循环。除了暴力循环外,最开始的思路是能否嵌套map一层层地查询,这样每个数组都只需遍历一遍,时间复杂度是O(n)。但是继续想发现除了最后一层,其他层的查询条件全都未知,只能放弃了这个想法。
之后又想能不能对四个数组进行分组,12一组34一组,分别嵌套循环,这样时间复杂度是n^2 + n^2即O(n^2)。好像可行就写了下去,想的是使用set实现,但写着写着发现由于set去重的特性,如果有多个成功匹配值结果只能+1。
看了看解析是使用map,将出现次数放在val中,妙啊。
思路:将四个数组分为两组,使用map记录nums1和nums2的和(key),以及该和出现的次数(val)。
// 使用map记录nums1+nums2,使用nums3+nums4对map进行查询
int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
unordered_map record12;
int ans = 0;
for (int i1 : nums1) {
for (int i2 : nums2) {
auto iter = record12.find(i1 + i2);
if (iter == record12.end())
// 两数的和作为key,其出现的次数作为val存入map
record12.insert({ i1 + i2, 1 });
else
++(iter->second);
iter = record12.find(i1 + i2);
}
}
for (int i3 : nums3) {
for (int i4 : nums4) {
auto iter = record12.find(-(i3 + i4));
if (iter != record12.end() && iter->second > 0) {
// // 一个解的所有实现方法全部加上
ans += iter->second;
}
}
}
return ans;
}
和242.有效的字母异位词几乎完全一样(冒充新题)。
不同点就是两个字符串不需要组成元素完全相同,只需要magazine能够完全包含ransomNote。
bool canConstruct(string ransomNote, string magazine) {
vector ans(26, 0);
for (char c : magazine)
++ans[c - 'a'];
for (char c : ransomNote)
--ans[c - 'a'];
// 和242相比就返回条件稍作修改
for (int i : ans)
if (i < 0)
return false;
return true;
}
(太难了呀)
重点应该是如何去重
初步想法是和454.四数相加一样先筛选出符合条件的解,对于每一个解再像242异位词那样判断是否与之前的解是重复解。结果查询两分钟去重两小时,写了一个多小时写了坨运行不起来的答辩,哈哈:D。
思路:
1、由于不要求返回下标,所以可以先对数组进行排序:排序不仅利于搜索元素,也大幅节省了去重的工作量(相同的数会聚集在一起)
2、使用双指针法。固定一个元素,在该元素之后与数组尾端各置一个指针,移动两个指针逐步判断与该固定元素的三数之和是否为0。(双指针思路有点类似209.长度最短的子数组,大了就缩右端,小了就缩左端)
去重思路重点:
1、剪枝条件是nums[i] == nums[i+1]还是nums[i] == num[i-1]?
nums[i] == nums[i-1]表示如果当前值在之前已经使用过了则跳过,逻辑正确
nums[i] == nums[i+1]会影响到left指针的判断(left的初始值为i+1),逻辑错误
2、双指针如何去重?何时去重?
先记录结果再去重,先去重可能会导致诸如{0, 0, 0, 0}这样的数组丢失可行解(left和right都移动完了还没有完成去重)。
vector> threeSum(vector& nums) {
vector> ans;
int left, right;
std::sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); ++i) {
// nums[i]>0则之后不管怎么加都是>0,所以剪枝
if (nums[i] > 0)
return ans;
// 对i进行去重
if (i > 0 && nums[i] == nums[i - 1])
continue;
left = i + 1;
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 {
ans.push_back({ nums[i], nums[left], nums[right] });
// 对left和right进行去重
while (left < right && nums[left + 1] == nums[left])
++left;
while (left < right && nums[right - 1] == nums[right])
--right;
// 找到解时,left和right同时收缩
++left;
--right;
}
}
}
return ans;
}
思路与三数之和相同,不过变为固定两个数。和454相比难点应该在去重上。
细节有点多,提交了好几次才AC。尤其注意目标值变为了一个可变的target,target可能为负,所以剪枝的条件与三数之和有所不同。
vector> fourSum(vector& nums, int target) {
vector> ans;
int left, right;
std::sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size(); ++i) {
// 注意这里nums[i]还应>=0才进行剪枝(target可能为负数),下面同理
if (nums[i] > target && nums[i] >= 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) {
// 注意这里应该为break,不能为return
break;
}
if (j > i + 1 && nums[j] == nums[j - 1])
continue;
left = j + 1;
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 {
ans.push_back({ nums[i], nums[j], nums[left], nums[right] });
while (left < right && nums[left + 1] == nums[left])
++left;
while (left < right && nums[right - 1] == nums[right])
--right;
++left;
--right;
}
}
}
}
return ans;
}
(哈希表好难啊)
使用重点:
1、何时使用哈希表?
· 根据某种条件进行查询(如异位词查找字母、四数相加查找和为0)
· 有些时候不使用哈希表反而更好,判断何时不用哈希表感觉也是个大难点。
2、使用哈希表的话使用数组、set还是map?
· 查询的大小可确定,且下标不会过于分散(如0, 1, 2, 100000)时,使用数组
· 查询的大小未知,只查找是否存在时,使用set
· 查询的大小未知,除了判断是否存在还需要存储其他信息时,使用map
3、哈希表的查询条件(key)是什么?
· 结合题目判断