代码随想录07(四数相加,四数之和)

454.四数相加II

题目链接/文章讲解/视频讲解:代码随想录

给你四个整数数组 nums1nums2nums3 和 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. 使用 mapfind 方法

    你可以使用 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 != ji != 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]
  1. 排序后,数组为:[-4, -1, -1, 0, 1, 2]
  2. 遍历时,当我们选到第一个 -1i=1)时:
    • left = 2right = 5,我们得到三元组 [-1, -1, 2]
  3. 然后继续遍历到 i=2,由于 nums[2] == nums[1],所以跳过 i=2,避免重复三元组。
  4. 接着,我们继续遍历到 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;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值