代码随想录算法训练营Day7 哈希?双指针!
哈希表和双指针相关题目
Leetcode 454. 四数相加 II
题目链接:https://leetcode.cn/problems/4sum-ii/description/
题目描述:给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
其中$$0 <= i, j, k, l < n$$
题目解析:仿照”两数之和“的做法。原式等价于$$nums1[i] + nums2[j] = -nums3[k] -nums4[l]$$
那么,我们就可以用\(O(n^2)\)的时间获取所有的\(nums1[i] + nums2[j]\),然后用\(O(1)\)的从哈希表中查询\(-nums3[k] -nums4[l]\)
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
std::unordered_map<int, int> cnt;
for (auto& x : nums1) {
for (auto& y : nums2) {
cnt[x + y] ++;
}
}
int ans = 0;
for (auto& x : nums3) {
for (auto& y : nums4) {
ans += cnt[-x - y];
}
}
return ans;
}
};
Leetcode 383. 赎金信
题目链接:https://leetcode.cn/problems/ransom-note/description/
题目描述:给你两个字符串:\(ransomNote\) 和 \(magazine\) ,判断 \(ransomNote\) 能不能由 \(magazine\) 里面的字符构成。如果可以,返回 \(true\) ;否则返回 \(false\) 。\(magazine\) 中的每个字符只能在 \(ransomNote\) 中使用一次。
题目解析:这个题目可以理解成“商店购物”。本题的\(ransomNote\)就是顾客,\(magazine\)就是超市。我们先进货,也就是统计\(magazine\)的字符出现次数;然后遍历所有\(ransomNote\)所需要的字符,我们无条件地卖给他。如果发现卖了之后会出现负数,说明“供不应求”,就不能够满足题意。反之,满足了所有的顾客,说明符合题意。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
// 剪枝 如果顾客太多 一定供不应求
if (ransomNote.size() > magazine.size()) {
return false;
}
int cnt[26]{};
for (auto c : magazine) {
cnt[c - 'a'] ++;
}
for (auto c : ransomNote) {
if (-- cnt[c - 'a'] < 0) {
return false;
}
}
return true;
}
};
Leetcode 15. 三数之和
题目链接:https://leetcode.cn/problems/3sum/description/
题目描述:给定一个整数数组 \(nums\),判断是否存在三元组 \([nums[i], nums[j], nums[k]]\) 满足 \(i \neq j\)、\(i \neq k\) 且 \(j \neq k\),同时还满足 \(nums[i] + nums[j] + nums[k] = 0\)。请你返回所有和为 \(0\) 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
题目解析:根据“两数之和”的思路,我们枚举所有元素,当我们枚举元素\(i\)的时候,相当于固定下来了一个元素,那么我们就可以转化成“两数之和”。
// (法一:哈希)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
std::set<std::vector<int>> st;
int n = nums.size();
std::ranges::sort(nums);
for (int i = 0; i < n; i ++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
unordered_set<int> seen;
for (int j = i + 1; j < n; j ++) {
int target = -nums[i] - nums[j];
if (seen.count(target)) {
std::vector<int> tmp = {nums[i], nums[j], target};
std::ranges::sort(tmp);
st.insert(tmp);
}
seen.insert(nums[j]);
}
}
return std::vector<std::vector<int>>(st.begin(), st.end());
}
};
我们会发现,这样的时间复杂度是\(O(n^2)\)的,这显然在数据量比较大的时候是不优的。
那么我们有什么办法优化呢?双指针。
为了方便双指针以及去重,考虑排序。枚举\(nums[i]\),那么问题转化成另类的“两数之和”。为了避免重复,我们可以跳过相同的那些元素。
上代码,代码里面再解释。
// (法二:双指针)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
size_t n = nums.size();
ranges::sort(nums);
vector<vector<int>> ans;
// 枚举所有的元素
for (int i = 0; i <= n - 3; i ++) {
int x = nums[i];
// 三种剪枝优化
// 去掉重复元素
if (i && x == nums[i - 1]) continue;
// 如果加上最小的还大 必不可能是答案
if (x + nums[i + 1] + nums[i + 2] > 0) break;
// 如果加上最大的还小 必不可能是答案
if (x + nums[n - 1] + nums[n - 2] < 0) continue;
int l = i + 1, r = n - 1; // 从区间[i + 1, n - 1]找
while (l < r) {
int s = x + nums[l] + nums[r]; // 避免反复求和
// 这里的思路是,因为整个数组有序
// 那么 s > 0 -> 和大了 -> 让他变小 -> r左移
// 同理 s < 0 -> 和小了 -> 让他变大 -> l右移
if (s > 0) {
r --;
} else if (s < 0) {
l ++;
} else {
// 因为已经有序,所以这样天然去重
ans.push_back({x, nums[l], nums[r]});
// 跳过相同的元素 避免去重
for (l ++; l < r && nums[l] == nums[l - 1]; l ++);
for (r --; l < r && nums[r] == nums[r + 1]; r --);
}
}
}
return ans;
}
};
这样,时间复杂度就变成了排序的\(O(n*logn)\),这显然是更优的。
Leetcode 18. 四数之和
题目链接:https://leetcode.cn/problems/4sum/description/
题目描述:给定一个由 \(n\) 个整数组成的数组 \(nums\),和一个目标值 \(target\)。请找出并返回满足下述全部条件且不重复的四元组 \([nums[a], nums[b], nums[c], nums[d]]\)(若两个四元组元素一一对应,则认为两个四元组重复)。
\(Note:\)
- \(0 \leq a, b, c, d < n\)
- \(a\)、\(b\)、\(c\) 和 \(d\) 互不相同
- \(nums[a] + nums[b] + nums[c] + nums[d] = target\)
题目分析:有了上一题的经验,我们不难想到:枚举所有元素,就相当于固定下来了一个元素,那么这个问题就变成了一个三数之和。我们只需要再做一次三数之和就行了。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
using i64 = int64_t;
ranges::sort(nums);
int n = nums.size();
vector<vector<int>> ans;
for (int a = 0; a < n - 3; a ++) {
i64 x = nums[a];
if (a > 0 && x == nums[a - 1]) continue;
for (int b = a + 1; b < n - 2; b ++) {
i64 y = nums[b];
if (b > a + 1 && y == nums[b - 1]) continue;
int c = b + 1, d = n - 1;
while (c < d) {
i64 s = x + y + nums[c] + nums[d];
if (s > target) {
d --;
} else if (s < target) {
c ++;
} else {
ans.push_back({(int) x, (int) y, nums[c], nums[d]});
for (c ++; c < d && nums[c] == nums[c - 1]; c ++);
for (d --; c < d && nums[d] == nums[d + 1]; d --);
}
}
}
}
return ans;
}
};

浙公网安备 33010602011771号