454.四数相加II
题目链接/文章讲解/视频讲解:代码随想录
给你四个整数数组 nums1
、nums2
、nums3
和 nums4
,数组长度都是 n
,请你计算有多少个元组 (i, j, k, l)
能满足:
0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
解题思路:与两数之和类似,但两数之和为在一个数组上先用map储存前面已经遍历的数的数值和下标,need=target-nums[i],在map中寻找need是否存在和对应的下标;该题有四个数组,为简化时间复杂度,分为两组,用map容器记录前两组的数值累加和和出现次数,再去后两组中找寻目标值,累加出现次数。
补充知识:引用map两个值的方式1. 使用迭代器
如果你要从
map
中访问两个值,可以通过迭代器来实现。下面是一个示例:int main() { unordered_map<int, int> m; m[1] = 10; m[2] = 20; m[3] = 30; // 获取两个值 auto it = m.begin(); // 获取第一个元素的迭代器 int first_value = it->second; // 获取第一个值 it++; // 移动到下一个元素 int second_value = it->second; // 获取第二个值 cout << "First value: " << first_value << endl; cout << "Second value: " << second_value << endl; return 0; }
2. 直接通过键访问
如果你已经知道键值,你也可以直接通过键来访问
map
中的值:unordered_map<int, int> m; m[1] = 10; m[2] = 20; m[3] = 30; // 直接通过键访问值 int first_value = m[1]; // 获取键为1的值 int second_value = m[2]; // 获取键为2的值
3. 使用
map
的find
方法你可以使用
find
方法来查找特定键对应的值,然后访问它:unordered_map<int, int> m; m[1] = 10; m[2] = 20; m[3] = 30; // 使用find方法访问 auto it1 = m.find(1); // 查找键为1的元素 auto it2 = m.find(2); // 查找键为2的元素 if (it1 != m.end() && it2 != m.end()) { cout << "First value: " << it1->second << endl; cout << "Second value: " << it2->second << endl; }
解题代码
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int>m;//存储a+b的数值和次数,默认键值为0
for(int i=0;i<nums1.size();i++)
{
for(int j=0;j<nums2.size();j++)
{
int sum=nums1[i]+nums2[j];
//if(m.find(sum)!=m.end()){value++;}
//else {m.insert(pair<int,int>(sum,1));}
m[sum]++;//map容器可直接通过m[sum]来访问键值,第一次出现的数建值也可初始化为1
}
}
int result=0;
for(int i=0;i<nums3.size();i++)
{
for(int j=0;j<nums4.size();j++)
{
int sum1=nums3[i]+nums4[j];
int need=-(sum1);
if(m.find(need)!=m.end())
{
result+=m[need];
}
}}
return result;
}
};
- 时间复杂度: O(n^2)
- 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
代码改进
class Solution { public: int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) { unordered_map<int, int> umap; //key:a+b的数值,value:a+b数值出现的次数 // 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中 for (int a : A) { for (int b : B) { umap[a + b]++; } } int count = 0; // 统计a+b+c+d = 0 出现的次数 // 再遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就把map中key对应的value也就是出现次数统计出来。 for (int c : C) { for (int d : D) { if (umap.find(0 - (c + d)) != umap.end()) { count += umap[0 - (c + d)]; } } } return count; } };
383. 赎金信
建议:本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题
题目链接/文章讲解:代码随想录
注意点:vector<int>hash(26,0);目的是防止数组越界
class Solution { public: bool canConstruct(string ransomNote, string magazine) { vector<int>hash(26,0); for(int i=0;i<ransomNote.size();i++) { hash[ransomNote[i]-'a']++; } for(int j=0;j<magazine.size();j++) { hash[magazine[j]-'a']--; } for(int i=0;i<26;i++) { if(hash[i]>0) return false;//反向直接跳出循环 } return true; } };
或简单改进
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
vector<int> hash(26, 0); // 初始化一个长度为26的数组,表示26个字母的计数
// 计算ransomNote中每个字母的出现次数
for (char ch : ransomNote) {
hash[ch - 'a']++;
}
// 在magazine中减少对应字母的计数
for (char ch : magazine) {
hash[ch - 'a']--;
}
// 如果有任何字母的计数大于0,说明ransomNote无法构建
for (int count : hash) {
if (count > 0) {
return false;
}
}
return true;
}
};
巧妙点:反向思维,不符合条件的直接跳出循环
for(int i=0;i<26;i++)
{
if(hash[i]>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
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
这道题的关键在于对结果值去重
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//使用双指针法优先排序
vector<vector<int>>result;//二维数组,返回什么
sort(nums.begin(),nums.end());//默认升序排列
for(int i=0;i<nums.size();i++)
{
if(nums[i]>0)return result;
if(i>0&&nums[i]==nums[i-1])continue;//跳出本轮循环直接i++,原因
int left=i+1;
int right=nums.size()-1;
while(left<right)//求三元组,所以不能相等
{
if(nums[i]+nums[left]+nums[right]==0)
{
result.push_back(vector<int>{nums[i],nums[left],nums[right]});
//在找到一个符合条件的三元组后,对b和c进行去重
//例如0 -1 -1 -1 -1 -1 -1 1 1 1 1 1
while(left<right&&nums[right]==nums[right-1])right--;//跳过相同的值
while(left>right&&nums[left]==nums[left+1])left++;//跳过相同的值
//使用while保证在连续遇到相同的值时可以连续收缩,避免只收缩一次
//在找到正确答案后,双指针同时收缩
right--;
left++;
}
else if(nums[i]+nums[left]+nums[right]<0)
{
left++;
}
else
{
right--;
}
}
}
return result;
}
};
去重操作:
对a去重,若代码这样写
if (nums[i] == nums[i + 1]) { // 去重操作
continue;
}
那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。
这样去除的结果集中有重复元素的结果
那么应该这么写:
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
示例 : [-1, 0, 1, 2, -1, -4]
- 排序后,数组为:
[-4, -1, -1, 0, 1, 2]
。 - 遍历时,当我们选到第一个
-1
(i=1
)时:left = 2
,right = 5
,我们得到三元组[-1, -1, 2]
。
- 然后继续遍历到
i=2
,由于nums[2] == nums[1]
,所以跳过i=2
,避免重复三元组。 - 接着,我们继续遍历到
i=3
,即nums[i] = 0
,然后找到和为0的三元组[[-1, 0, 1]]
。
通过这种方法,我们避免了重复三元组的出现(可以自己手动模拟一下代码的过程)
b和c的去重
while(left<right&&nums[right]==nums[right-1])right--;//跳过相同的值
while(left>right&&nums[left]==nums[left+1])left++;//跳过相同的值
问题:
while(left<right)//外层循环已经判断
while(left<right&&nums[right]==nums[right-1])right--;//跳过相同的值 while(left>right&&nums[left]==nums[left+1])left++;//跳过相同的值
为什么明明外层循环已经有left<right的判断,去重时之前还要判断left<right
解答:
- 外层的
while(left < right)
确保了指针不会交错,但是它不会阻止我们跳过重复元素时指针停留在无效位置。 - 在去重操作中,我们需要再次判断
left < right
,以确保指针移动时不越过有效范围,避免重复三元组的出现。去重时再判断
left < right
: -
第一次
while(left < right && nums[right] == nums[right - 1]) right--;
:当我们发现nums[right] == nums[right - 1]
时(即遇到重复的1
),我们跳过这些重复的元素,并且我们继续检查新的right
值。此时,我们需要再次判断
left < right
,这是因为跳过重复元素后,right
可能已经越过了left
,导致指针交错。 -
第二次
while(nums[left] == nums[left + 1]) left++;
:同样地,当我们发现nums[left] == nums[left + 1]
时(即遇到重复的0
),我们跳过这些重复的元素,并且继续检查新的left
值。这里同样需要再次判断
left < right
,因为跳过重复元素后,left
可能越过了right
,导致left
不再指向有效的位置。
18. 四数之和
要比较一下,本题和 454.四数相加II 的区别
题目链接/文章讲解/视频讲解:代码随想录
思路:1.与三数之和很类似,但需要进行两次剪枝和两次去重操作
2.当处理特别大的数字的时候,需要提防整数溢出(四数之和>2^32-1),改用为long类型
3.当进行二次去重时,注意此时i>k+1,不在是i>0
if (i > k + 1 && nums[i] == nums[i - 1]) {
continue;
}
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>result;//用来存储结果
sort(nums.begin(),nums.end());
for(int k=0;k<nums.size();k++)
{
if(nums[k]>target&&nums[k]>=0)break;//进行一级剪枝操作
//返例,target=-5;-1,-4 是不能直接剪枝的
if(k>0&&nums[k]==nums[k-1])continue;//进行一级去重操作,直接跳过当层迭代
for(int i=k+1;i<nums.size();i++)
{
if(nums[i]+nums[k]>target&&nums[i]+nums[k]>=0)break;//进行二级剪枝操作,把前两个数看成一个整体
if(i>k+1&&nums[i]==nums[i-1])continue;//进行二级去重操作
int left=i+1;
int right=nums.size()-1;
while(left<right)
{
if((long)nums[k]+nums[i]+nums[left]+nums[right]<target)
{ left++;}
else if((long)nums[k]+nums[i]+nums[left]+nums[right]>target)
{right--;}
else
{
result.push_back(vector<int>{nums[k],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 result;
}
};