454.四数相加II
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> map;
for (int a: nums1) { //将全部nums1和nums2中的元素和放到map的key中,并在map的value中记录重复的次数
for (int b: nums2) {
map[a+b]++; //因为使用[]访问map会自动创建key值,并且value值会使用默认值来赋值
}
}
int count = 0;
for (int c: nums3) {
for (int d: nums4) {
if (map.find(0-c-d) != map.end()) { //如果map中存在符合条件的元素和,则将它的value值加到count上
count += map[0-c-d];
}
}
}
return count;
}
这道题采用map来进行哈希表的建立。由于这道题目要求四个数组中相加为零的个数,首先要先计算前两个数组的元素和,将元素和放到map中的key中,用value记录等于该元素和的个数,然后再遍历后两个数组,寻找有没有符合条件的元素和,若存在,则直接将map中的value加到count上即可记录符合条件的次数。这道题实际上是使用哈希表,将一个四次嵌套循环分成了两个两次的嵌套循环,实现了使用空间换时间的效果,降低了时间复杂度。
383. 赎金信
题目链接/文章讲解: 代码随想录
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char, int> map;
for (int i = 0; i < magazine.size(); i++) {
map[magazine[i]]++;
}
for (int i = 0; i < ransomNote.size(); i++) {
if (map.find(ransomNote[i]) != map.end()) {
map[ransomNote[i]]--;
if (map[ransomNote[i]] == 0)
map.erase(ransomNote[i]);
}
else
return false;
}
return true;
}
思路:先将第二个字符串的每个字母存在map的key中,用value来表示出现次数,再遍历第一个数组,如果遍历时字母在map中存在,则将其value值减一,若减少为0则说明第二个字符串中的这个字母用完了,将其从map中删掉。如果有不在map中的字母则不满足要求,返回false,否则在最后返回true。
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
//add
if (ransomNote.size() > magazine.size()) {
return false;
}
for (int i = 0; i < magazine.length(); i++) {
// 通过record数据记录 magazine里各个字符出现次数
record[magazine[i]-'a'] ++;
}
for (int j = 0; j < ransomNote.length(); j++) {
// 遍历ransomNote,在record里对应的字符个数做--操作
record[ransomNote[j]-'a']--;
// 如果小于零说明ransomNote里出现的字符,magazine没有
if(record[ransomNote[j]-'a'] < 0) {
return false;
}
}
return true;
}
在给出的示例代码中是使用的数组实现的哈希表,由于只会有小写字母组成,使用数组实现时间消耗实际会小一些,但思路是一致的,使用map空间消耗可能会小一些。
15. 三数之和
题目链接/文章讲解/视频讲解: 代码随想录
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[j], c = -(a + b)
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
if (nums[i] > 0) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
continue;
}
unordered_set<int> set;
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 2
&& nums[j] == nums[j-1]
&& nums[j-1] == nums[j-2]) { // 三元组元素b去重
continue;
}
int c = 0 - (nums[i] + nums[j]);
if (set.find(c) != set.end()) {
result.push_back({nums[i], nums[j], c});
set.erase(c);// 三元组元素c去重
} else {
set.insert(nums[j]);
}
}
}
return result;
}
首先是使用set的哈希表做法,这道题使用哈希表做法的话去重会比较复杂,不建议使用该方法。开始先进行排序,并将三元组元素a进行去重,若又连续两个相同的元素a,若第一个又满足条件的,第二个也一定满足,因此要将第二个跳过。i、j、c的实际顺序其实是i<c<j,因此,在set.erase(c);这个去重实际是去重第二个元素。j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]这个条件是用来去重b元素的,c是遍历j走过的数组值 如果有2个连续的就是C = j ,3个连续的j就重复了。实际上,在a确定之后符合条件的c+b的和就已将确定了,如果有三个相同的元素的话,在前两个元素遍历完之后,如果有满足条件的就已经被添加至结果数组里了,所以应该被去重。
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(), nums.end());
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.size(); i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
// 错误去重a方法,将会漏掉-1,-1,2 这种情况
/*
if (nums[i] == nums[i + 1]) {
continue;
}
*/
// 正确去重a方法
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while (right > left) {
// 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
/*
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) 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]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
// 找到答案时,双指针同时收缩
right--;
left++;
}
}
}
return result;
}
采用双指针做法会更加的高效,定义指针left,reigt,初始时,left=i+1,right=size-1。
如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
关于去重的内容,可以直接去看代码随想录中的内容,写的相当详细且易懂了。
18. 四数之和
题目链接/文章讲解/视频讲解: 代码随想录
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int length = nums.size();
if (length < 4) {
return result;
}
sort(nums.begin(), nums.end());
for (int i = 0; i < length - 3; i++) {
if (i > 0 && nums[i] == nums[i-1])
continue;
if ((long)nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target)
break;
for (int j = i+1; j < length - 2; j++) {
if (j>i+1 && nums[j] == nums[j-1])
continue;
int left = j+1, right = length-1;
while (left < right) {
if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target)
left++;
else if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target)
right--;
else {
result.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});
//left和right的去重操作
while (right > left && nums[left] == nums[left + 1]) left++;
while (right > left && nums[right] == nums[right - 1]) right--;
left++;
right--;
}
}
}
}
return result;
}
这道题和上一题思路上一致,都是使用双指针来解决,不过这道题要多加一套循环。在去重和剪枝上也都与上一题类似,不过这道题剪枝可以直接使用四个元素的大小来进行判断,更加精确。
但是我写的时候在left和right去重时right > left && 这个条件忘写了,导致一直造成数组越界,还找了半天bug,真是不注意细节啊!