454四数相加: 要点,通过分而治之得思想,将四个数组分成两对,这样就在暴力解法的基础上从n的4次方改进成2个n平方遍历。 使用hash表unordered_map构造map用于存储和快速查找,还要注意cpp中迭代器的使用:
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
//使用map存储nums1,nums2之和,然后计算nums3,nums4之和,在map中查找相应值
int count=0;
unordered_map<int,int> calmap;
for(int i=0;i<nums1.size();i++)
for(int j=0;j<nums2.size();j++){
int sum1=nums1[i]+nums2[j];
calmap[sum1]++;
}
//计算统计次数
for(int i=0;i<nums3.size();i++)
for(int j=0;j<nums4.size();j++){
auto it = calmap.find(0-nums3[i]-nums4[j]);
if (it != calmap.end()) {
// 找到补数:累加该补数出现的次数
count += it->second;
}
}
return count;
}
};
15.三数之和。 此处如果采用hash表做法会异常复杂,因此考虑到为从同一数组中取三数求和,可以在排序的基础上使用双指针法进行数组的快速遍历。
重点难点: 1.如何设计双指针,i的每次移动,都需要重置j,k,并根据和的大小移动指针 2.如何去重,三数中每次i移动时当出现相同元素时就会出现重复,也就是说外层循环和内层循环都要去重一次 3.如何存储目标数组,设置vector数组,并且要熟悉迭代器使用
public:
vector<vector<int>> threeSum(vector<int>& nums) {
//先对nums进行排序
int n = nums.size();
// 核心步骤1:排序(去重+双指针的基础)
sort(nums.begin(), nums.end());
//设置存储向量
vector<vector<int>> group;
// 边界处理:数组长度小于3,直接返回空
if (n < 3) return group;
//开始循环遍历,注意最左侧数字只多只能是倒数第三个元素
for(int i=0;i<nums.size()-2;i++){
//剪支判断
if(nums[i]>0) break;
if (i > 0 && nums[i] == nums[i-1]) continue;
//循环判断数组元素是否满足条件
int j=i+1;
int k=nums.size()-1;
while(j<k){
if(nums[i]+nums[j]+nums[k]<0){
j++;
}
else if(nums[i]+nums[j]+nums[k]>0){
k--;
}
else if(nums[i]+nums[j]+nums[k]==0){
group.push_back({nums[i], nums[j], nums[k]});
//去重判断,注意,这里去重判断需要同自身前一元素相互比较
// 去重2:跳过j的重复值(避免重复三元组)
while (j < k && nums[j] == nums[j+1]) ++j;
// 去重3:跳过k的重复值(避免重复三元组)
while (j < k && nums[k] == nums[k-1]) --k;
// 找到有效解后,同时移动j/k继续查找
++j;
--k;
}
}
}
return group;
}
};
18.四数之和
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
//先对nums进行排序操作;
sort(nums.begin(), nums.end());
vector<vector<int>> result;
//排除小于4的数组
if (nums.size()< 4) return result;
//在三数之和的基础上增加一层外层循环
for(int i=0;i<nums.size()-3;i++){
//执行剪枝操作,因为使用target,无法判断target的正负以及nums数组的正负分布情况
if(nums[i]>target && target>0 &&nums[i]>0){
break;
}
//一层循环去重
if(i>0 && nums[i]==nums[i-1]) continue;
//二层循环为三数之和
for(int j=i+1;j<nums.size()-2;j++){
long long two_sum=nums[i]+nums[j];
//三数之和的剪支操作
if(two_sum>target&&target>0&&nums[j]>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 long four_sum=two_sum+nums[left]+nums[right];
if(four_sum<target){
left++;
}
//四数指针大于目标,右指针左移
else if(four_sum>target){
right--;
}
//记录符合要求四数
else if(four_sum==target){
result.push_back({nums[i],nums[j],nums[left],nums[right]});
// 去重:跳过left的重复值
while (left < right && nums[left] == nums[left+1]) left++;
// 去重4:跳过right的重复值
while (left < right && nums[right] == nums[right-1]) right--;
// 移动指针继续查找
left++;
right--;
}
}
}
}
return result;
}
};
要点分析: 1.思路,在三数之和的基础上增加外层循环 2.剪支操作的条件区分,要注意到剪支操作需要考虑负数形式 3.每一层都要执行去重操作,注意内层循环的去重操作循环在循环条件内部 4.执行int加法计算时要注意超出取值范围问题,必须要设置long long 变量