454.四数相加
题目描述:
给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例 1:
输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1.(0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2.(1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例 2:
输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1
类似两数之和的思路,使用一个HashMap记录任意两个数组所有可能数组合之和,将和作为key并在该和每次出现时将其对应value加一。之后遍历另外两个数组所能形成的所有组合,若0减去当前之和在map中存在则表示4个数和为0的组合。此时前两个数出现的次数就是当前组合出现的次数,从map中获取对应value并继续。代码如下:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> bucket1;
int res = 0;
for (int i : nums1) {
for (int j : nums2) {
++bucket1[i + j];
}
}
for (int i : nums3) {
for (int j : nums4) {
int target = 0 - (i + j);
auto it = bucket1.find(target);
if (it != bucket1.end()) {
res += it->second;
}
}
}
return res;
}
};
383.赎金信
题目描述:
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
输入:ransomNote = “a”, magazine = “b”
输出:false
示例 2:
输入:ransomNote = “aa”, magazine = “ab”
输出:false
示例 3:
输入:ransomNote = “aa”, magazine = “aab”
输出:true
使用Hash表记录magazine中字符出现的次数,接着对ransomNote进行遍历,若当前字符在Hash表中出现的次数为0则代表magazine中不存在该字符或者该字符已被前面的字符所使用,返回false,否则在遍历结束时返回true。代码如下:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
if (ransomNote.size() > magazine.size()) return false;
int record[26] = {};
for (char c : magazine) {
++record[c - 'a'];
}
for (char c : ransomNote) {
if (--record[c - 'a'] < 0) {
return false;
}
}
return true;
}
};
15.三数之和
题目描述:
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请
你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:
输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:
输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。
思路是从第一个元素开始固定当前元素,使用双指针在后续子数组中查找使和为0的数,双指针使用的前提是数组是已经被排序的,这样可以使根据结果的大小移动left或者right使结果逼近target。
当固定的元素完成从第0个到倒数第3个的遍历时表示搜索完成,在遍历时有两种特殊场景:当前元素大于0时,由于数组已经被排序则后续之和一定大于0,跳过后续循环;当前元素等于上一个元素时,代表被固定的当前值已经被记录到结果中,为避免重复跳过后续指针的搜索继续遍历。
元素固定后开始双指针的搜索,三个位置和大于0时,将right向左移动使和减小,同理大于0时使left向右移动使和增大。若和为0则记录结果并分别跳过左右指针处值重复的数,接着移动两指针继续搜索直到相遇。代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 2; ++i) {
if (nums[i] > 0) break;
if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1;
int right = nums.size() - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
--right;
} else if (sum < 0) {
++left;
} else {
res.push_back({nums[i], 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;
}
};
18.四数之和
题目描述:
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
和三数之和思路一样,使用两层for循环进行元素的固定,再使用双指针检索。
需要额外注意的是与上题类似特殊场景的处理:由于这里是target而非0,在跳过后续循环的场景下需额外确保当前循环中已固定元素之和大于等于0,以确保后续之和总会大于target时跳过;对重复元素的跳过以及后续双指针的逻辑与上题保持一致。
需要额外注意的是int运算的溢出问题,需将运算类型转换为long或longlong。代码如下:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
if (nums.size() < 4) return res;
sort(nums.begin(), nums.end());
for (int i = 0; i < nums.size() - 3; ++i) {
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() - 2; ++j) {
if (nums[j] + nums[i] > target && (nums[j] + nums[i]>= 0)) break;
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
int left = j + 1;
int right = nums.size() - 1;
while (left < right) {
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if (sum > target) {
--right;
} else if (sum < target) {
++left;
} else {
res.push_back({nums[i], nums[j], 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;
}
};