一. LeetCode454题.四数相加II
题目链接/文章讲解/视频讲解:https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html
状态:已解决
1.思路
这道题还是思路还是很简单,将公式A[i] + B[j] + C[k] + D[l] = 0变形为:A[i] + B[j] = -(C[k] + D[l])即可。由于题目是求满足的条件元组 (i, j, k, l),因此不仅需要关注元素的值,也需要关注元素值出现的位置,故该题不应该去重,值相等的两个元素要算成两种不同的情况。最开始我用的set做,只保存了前两个数组的和的值,没有计算同个和值的个数,导致错误,因此为了得到同样的和值的个数,我们必须要用map的数据结构来实现这道题。
解题步骤:
(1)首先定义unordered_map数组(效率高),key存放a+b的值,value存放该和值出现的次数。
(2)遍历A数组和B数组,统计两个数组元素之和,和出现的次数,放到map中。
(3)用一个int变量sum存放满足等式的元组个数。
(4)遍历C数组和D数组,看map中是否有等于-(c+d)的值,若有,sum就加上该和值出现的次数value。
(5)返回sum。
2.代码实现
我特别傻地还存了一遍c+d的和,然后再进行了一次遍历查找map2中有没有map1需要的值,不仅耗时还耗内存,我真傻,真的。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> s1,s2;
long long int sum = 0;
for(int i=0;i<nums1.size();i++){
for(int j=0;j<nums2.size();j++){
auto iter = s1.find(nums1[i]+nums2[j]);
if(iter != s1.end()){
iter->second += 1;
}else{
s1.emplace(nums1[i]+nums2[j],1);
}
}
}
for(int i=0;i<nums3.size();i++){
for(int j=0;j<nums4.size();j++){
auto iter = s2.find(nums3[i]+nums4[j]);
if(iter != s2.end()){
iter->second += 1;
}else{
s2.emplace(nums3[i]+nums4[j],1);
}
}
}
for(auto iter1:s1){
int num = iter1.first;
auto iter2 = s2.find(-num);
if( iter2!= s2.end()) sum+=(iter1.second)*(iter2->second);
}
return sum;
}
};
根据视频讲解改进后的:
不仅学会了更快遍历vector容器的方法,还学到了直接用和作为索引访问map的技巧。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> s;
long long int sum = 0;
for(int a:nums1){
for(int b:nums2){
s[a+b]++;
}
}
for(int c:nums3){
for(int d:nums4){
if(s.find(-(c+d))!=s.end()){
sum += s[0-(c+d)];
}
}
}
return sum;
}
};
时间复杂度:O(n^2)
空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2
二. LeetCode 383. 赎金信
题目链接/文章讲解:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html
状态:已解决
1.思路
这道题跟昨天的242一样的思路,只是判断条件不一样,因为242有效字母异位词要求两个字符串长度相等且字母出现个数相同,而这道题只需要magazines里面的字符比ransom的相同字符多,前者可以构成后者即可。
(具体分析思路可以看昨天的做题链接:LeetCode242.有效的字母异位词)
2.代码实现
(看了文章介绍才发现可以直接在第二个循环里面就判断,反正发现了某个元素value小于0就返回)。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<int,int> s;
for(auto r:ransomNote){
s[r-'a']++;
}
for(auto m:magazine){
if(s.find(m-'a')!=s.end())
s[m-'a']--;
}
for(auto iter:s){
if(iter.second > 0) return false;
}
return true;
}
};
时间复杂度:O(n)
空间复杂度:O(1)
三. LeetCode15. 三数之和
题目链接/文章讲解/视频讲解:https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html
状态:已解决
1.思路
这道题用哈希法较难,不太适合,准备二刷再用哈希做。
讲解用的双指针,很妙。
拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
一个很重要的点是如何去重:对于nums[i],由于它是真正去遍历一次数组的部分,即每轮的最终起点,left和right都是根据i开启循环遍历的,因此它最先去重。去的方法也很简单,假如nums[i]==nums[i-1],就跳过,因为该值在第i个元素那里就已经做过后续循环遍历了。不能是nums[i]==nums[i+1],因为有可能数组包含三个连续的0,那么这种去重就直接到最后一个0去了,无法得到(0,0,0)的解。
left和right的去重很好理解,但不要忘了最后还要再更新一下left、right的值,因此while结束代表此时还在重复值中,left和right的下一个值才不在重复值之中。
2.代码实现
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) break;
if(i > 0 && nums[i]==nums[i-1]) continue;
int left = i+1;
int right = nums.size()-1;
while(left < right){
if(nums[i]+nums[left]+nums[right]<0){
left++;
}
else if(nums[i]+nums[left]+nums[right]>0){
right--;
}
else{
result.push_back(vector<int>{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的下一个值才不在重复之中。
left++;
right--;
}
}
}
return result;
}
};
时间复杂度:O(n^2)
空间复杂度:O(1)
四. LeetCode 18. 四数之和
题目链接/文章讲解/视频讲解:https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html
状态:已解决
1.思路
与三数之和思路差不多,依旧是双指针的做法,区别在于四数之和还要在最外面再套一层循环,此时以最外层的k作为每次循环的起点,代表第四个数,然后第i个数从它右侧开始遍历,left和right在i和k指向元素之和的基础上做移动。
2.代码实现
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;//一级剪枝
if(k > 0 && nums[k]==nums[k-1]) continue;//一级去重
for(int i=k+1;i<nums.size();i++){
if((nums[k]+nums[i])>target && nums[k]+nums[i]>=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的下一个值才不在重复之中。
left++;
right--;
}
}
}
}
return result;
}
};
时间复杂度:O(n^3) (双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法)
空间复杂度:O(1)
五、总结
不在同一个数组,而是在几个独立的数组中找元素之和为固定值的元组, 采用哈希法更简单。同一数组中就用双指针将时间复杂度降低一个数量级。