今日用时3小时😊
加油,三数之和的去重操作值得细细品味,懂得了用双指针三数之和的核心,之后n数之和的操作只不过是for的叠加
LeetCode 454.四数相加II
链接:454. 四数相加 II
思路:
直观思路:把逻辑上四个数组的问题转换为逻辑上两个数组的问题,问题转换为两数之和为0,较易
完整C++代码如下:
//时间复杂度: O(n^2)
//空间复杂度: O(n^2)
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int, int> sumCountAB; // 用于存储nums1和nums2中元素和的出现次数
// 统计nums1和nums2中元素的和的出现次数
for (int a : nums1) {
for (int b : nums2) {
sumCountAB[a + b]++;
}
}
int count = 0; // 记录满足条件的元组个数
// 遍历nums3和nums4中元素的和,查找其相反数出现的次数,如果存在则累加到count中
for (int c : nums3) {
for (int d : nums4) {
int target = -(c + d);
if (sumCountAB.find(target) != sumCountAB.end()) {
count += sumCountAB[target];
}
}
}
return count;
}
};
LeetCode 383.赎金信
链接:383. 赎金信
思路:
利用数组当哈希表,存储所要求的每个字母(所需要的)的个数,再用总的magazine(我们有的)去减,被用magazine减去后char_Num中只要有字母数量大于0,说明没满足所要求的数量,返回false,如果全小于等于0,返回true
完整C++代码如下:
//时间复杂度: O(n)
//空间复杂度: O(1)
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int char_Num[26]={};
for(auto c:ransomNote){
char_Num[c-'a']++;
}
for(auto c:magazine){
char_Num[c-'a']--;
}
for(int n:char_Num){
if(n>0)return false;
}
return true;
}
};
LeetCode 15.三数之和
链接:15. 三数之和
思路:
双指针
-
思路很简单,先对数组进行非递减排序
-
接着设置三个指针i,l,r进行移动,每轮固定i,将l,r定在i右边的两端,向中间移动,寻求三指针指向值的和为0(如下图,图片引用代码随想录),本题的难点在于如何去重和剪枝(优化算法)。
完整C++代码如下:
//时间复杂度: O(n^2)
//空间复杂度: O(1)
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();i++){
if (nums[i] > 0) {
//剪枝,i大于0,则l,r也一定大于0,直接返回
return res;
}
// 去重,新一轮的nums[i]如果和前一个相等,没必要再进行此轮循环
//因为以该大小的数作为i时,l,r所有情况在前一轮已经进行过了,直接跳过即可
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int l=i+1;
int r = nums.size() - 1;
while (l < r) {
if (nums[i] + nums[l] + nums[r] > 0) r--;
else if (nums[i] + nums[l] + nums[r] < 0) l++;
else {
res.push_back({nums[i], nums[l], nums[r]});
// 去重逻辑应该放在找到一个三元组之后,对r和l去重
//此处为何先进行r的去重,是因为即使数组中的数全相同,r也能最后停在l上
//即使后面再进行一次r--,r也能停到i上而不会溢出,反之若l在先去重,则后面收缩时会溢出
while (l < r && nums[r] == nums[r - 1]) r--;
while (l < r && nums[l] == nums[l + 1]) l++;
//再收缩一次才能到不同数的位置上
//这里注意是r--在前
r--;
l++;
}
}
}
return res;
}
};
LeetCode 18.四数之和
链接:18. 四数之和
思路:
双指针:
- 本题与上一题三数之和解法相似,区别在于上题使用一个for循环,将三个数中的一个设为固定值,使i为固定值;而本题使用双for,将四个数中的两个设为固定值,之后多数之和都是此思路,而双指针的作用在此处就很明显了,相比于暴力,可以简单一个数量级。
- 其实如果完全吃透了上一题,会发现本题整体部分与上题区别不大,只是注意第二个for仍然要进行剪枝,去重操作
- 以及注意个别例子超出int型最大值,使用long即可
注:双指针法将时间复杂度:O(n^t)的解法优化为 O(n^(t-1)的解法。也就是降一个数量级
完整C++代码如下:
//时间复杂度: O(n^3)
//空间复杂度: O(1)
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
//剪枝,注意要使得nums[i]>=0,可能存在[-2,-1,0,0],target为-3这类情况
//究其原因是target的值是否为正或负是不确定的,上题和为0是固定的
//此处采用break是为了避免函数有多个出口,统一结束时返回
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();j++){
//再次剪枝
if(nums[i]+nums[j]>target && nums[i]+nums[j]>=0){
break;
}
//再次去重
if(j > i+1 && nums[j] == nums[j-1]){
continue;
}
int l=j+1;
int r = nums.size() - 1;
while (l < r) {
//存在例子使得四数相加会溢出int的最大值
if ((long)nums[i] + nums[j] + nums[l] + nums[r] > target) r--;
else if ((long)nums[i] + nums[j] + nums[l] + nums[r] < target) l++;
else {
res.push_back({nums[i] , nums[j] , nums[l], nums[r]});
while (l < r && nums[r] == nums[r - 1]) r--;
while (l < r && nums[l] == nums[l + 1]) l++;
r--;
l++;
}
}
}
}
return res;
}
};