目录
一、做题心得
二、题目及题解
454.四数相加II
题目链接
题解
383. 赎金信
题目链接
题解
15. 三数之和
题目链接
题解
18. 四数之和
题目链接
题解
三、小结
一、做题心得
今天是代码随想录训练营打卡的第七天,做的也是同昨天一样的哈希表部分。感觉得出题目难度有所增大,然后我自己的话,最后两个题哈希的方法过于繁琐,做的时间有些久了,放弃了。后边通过题解,慢慢搞懂了双指针的做法(后两个题思路基本一样)。话不多说,直接开始看题。
二、题目及题解
454.四数相加II
题目链接
454. 四数相加 II - 力扣(LeetCode)
题解
首先说说暴力吧,这个题其实一眼就能看出暴力解法,但也很显然,时间复杂度过高,肯定过不了所有用例。我试着敲了一下,大家可以看看结果,如下:
然后各位可以回想一下,昨天打卡的两数之和(1. 两数之和 - 力扣(LeetCode))那道题,是不是感觉有一些相似。只不过这里变成了四个数组对应的四个值,所以我们要采用分组的思路。把i+j看成一体,把k+l看成一体,用和昨天差不多思路,定义unordered_map<key,value>型哈希表,其中key为i+j的具体的值(和),value为该值出现的次数。对a+b所有可能及其值存储到哈希表中,再通过查找函数等等操作得出结果。(还是不理解的话可以去看看昨天打卡的1.两数之和那道题)
代码如下:
//分组+哈希
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> hash; //在这里key为i+j的具体的值(和),value为该值出现的次数
int cnt = 0; //记数
for(int i : nums1) //先存储i+j的值及其对应的个数到哈希表中
{
for(int j : nums2 )
{
hash[i+j]++;
}
}
for(int k : nums3)
{
for(int l : nums4)
{
if(hash.find(0 - k - l) != hash.end()) //若hash表中存在满足和为0-k-l的i+j时,cnt加上满足的i+j的个数
cnt += hash[0 - k - l];
}
}
return cnt;
}
};
383. 赎金信
题目链接
383. 赎金信 - 力扣(LeetCode)
题解
这个题感觉和昨天那个242. 有效的字母异位词 - 力扣(LeetCode)一模一样,比较简单,相信大家都能解决,不会的话可以看看我昨天的打卡或者去搜索巩固一下哈希的基本知识。
代码如下:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char,int> hash;
for(char ch : magazine)
{
hash[ch]++;
}
for(char ch : ransomNote)
{
hash[ch]--;
if(hash[ch] < 0)
return false;
}
return true;
}
};
//分析:由于ransomNote中的元素都能被magazine包含,故ransomNote出现的每一个元素的个数都少于在magzine出现的次数,即原本hash[ch]++后再减也应该大于等于0
15. 三数之和
题目链接
15. 三数之和 - 力扣(LeetCode)
题解
这个题我感觉作为第一次做还是挺有难度的。如果用哈希做的话,很容易出错,时间上讲也不如双指针来的快。所以这里我们统一学习一下“排序+双指针”的思路。(由于下一道题与这个差不多,我把题解都写在这里,希望不会的各位可以认真看一下下边步骤并结合一下代码思考)
先分析题意,我们将要得到的三个数记作a,b,c(最后分别对应着nums[i],nums[left],nu ms[right])
然后进行以下步骤:
1.初始化结果向量:首先,初始化一个vector<vector<int>>类型的变量ans,用来存储所有满足条件的三元组。
2.排序:对输入的数组nums进行排序,这是为了使用双指针法,并且能够方便地去除重复解。
3.遍历数组:使用一个外层循环遍历数组nums,每次迭代中,当前元素记为a(即nums[i])。
4.去重a:在遍历过程中,如果当前元素a与前一个元素相同,则跳过本次循环(因为前面的迭代中已经考虑过这种情况了,不需要重复计算)。这里需要注意的是,去重a的逻辑应该放在i > 0的条件下,以避免在数组开头漏掉可能的解。
5.双指针法:在内层,使用两个指针left和right,分别指向a之后的第一个元素和数组的最后一个元素。然后,根据三数之和与0的比较结果,移动这两个指针来寻找满足条件的三元组。
6.去重b和c:当找到一个满足条件的三元组时,需要去除b和c(即nums[left]和nums[right])的重复值。这是通过移动指针,跳过与当前b或c相同的元素来实现的。这一步必须放在将当前三元组添加到ans之后进行,以确保不会漏掉任何可能的解。
7.返回结果:当遍历完整个数组后,返回存储了所有满足条件的三元组的ans。
代码如下:(有注释)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(), nums.end()); //先排序
vector<vector<int>> ans; //由于数组元素为数组,故用嵌套vector存储结果
int n = nums.size();
if (n < 3) //特殊情况先考虑
return ans;
for (int i = 0; i < n - 2; i++) {
if (nums[i] > 0) break; //提前终止
if (i > 0 && nums[i] == nums[i - 1]) continue; //去重a,避免重复解
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break; //由于数组已经排过序了,这时不可能存在满足的了,提前终止
if(nums[i] + nums[n-1] + nums[n-2] < 0) continue; //此时说明a(nums[i])小了,直接返回循环,i+1。(这样可以节省时间,直接跳过后续步骤。)
int left = i + 1, right = n - 1; //使用两个指针left和right,分别指向nums[i]之后的第一个元素和数组的最后一个元素
while (left < right) {
int sum = nums[i] + nums[left] + nums[right]; //注意sum为a,b,c的和
if (sum == 0) { //满足要求时
ans.push_back({ nums[i],nums[left],nums[right] }); //先存入结果
while (left < right && nums[left] == nums[left + 1]) left++; //移动左指针去重b(注意是while语句)
while (left < right && nums[right] == nums[right - 1]) right--; //移动右指针去重c
left++; //每次存放结果后,双指针都移动一位,便于后续下一步判断
right--;
}
else if (sum < 0)
left++; //如果和小于0, 向右移动左指针
else
right--; //如果和大于0, 向左移动右指针
}
}
return ans;
}
};
18. 四数之和
题目链接
18. 四数之和 - 力扣(LeetCode)
题解
由于和上道题思路差不多,这里可以直接看代码:(代码可能感觉有些长,但是思路其实很清晰,注意看看上一道题的思路和注释,相信你能做出这道题)
class Solution {
public: //注意:题目数据要求较大,有些语句要加上long
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ans;
sort(nums.begin(),nums.end());
int n = nums.size();
if(n < 4) return ans;
for(int i = 0;i < n - 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;
if((long)nums[i] + nums[n-1] + nums[n-2] + nums[n-3] <target)
continue;
for(int j = i + 1;j < n - 2;j++)
{
if(j > i + 1 && nums[j] == nums[j-1])
continue;
if((long)nums[i] + nums[j] + nums[j+1] + nums[j+2] > target)
break;
if((long)nums[i] + nums[j] + nums[n-1] + nums[n-2] < target)
continue;
int left = j + 1;
int right = n - 1;
while(left < right)
{
long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
if(sum == target)
{
ans.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--;
}
else if(sum < target)
left++;
else
right--;
}
}
}
return ans;
}
};
三、小结
今天的打卡到此也就结束了,感觉自己今天的的确确学到了些新东西,还不错吧。后边也会继续更新代码随想录的打卡,不断学习,不断积累自己。最后,我是算法小白,但也希望终有所获。