哈希表部分:454. 四数相加 II, 383. 赎金信, 15. 三数之和, 18. 四数之和

代码随想录算法训练营第7天 | 哈希表部分:454. 四数相加 II, 383. 赎金信, 15. 三数之和, 18. 四数之和

哈希表部分

454. 四数相加 II

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:0 <= i, j, k, l < n,nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

基本思路:如果是暴力运算的话,时间复杂度O(n4),使用hash寻找(0-其他三个的话)算法的复杂度O(n3),存在O(n2)复杂度吗
有问题的点:存在结果重复的问题,比如[1,-2,-1,2]和[-2,2,-1,1]
其中表示的是一个元组,这种情况该怎么解决
和18题的同一个数组中求四数之和target相比,本题使用四个独立的数组,
答案:将数组1和数组2遍历的元素的和保存在map中,遍历数组3和数组4,在map中寻找target-nums3[i]-nums4[j]的key,看是否存在,存在的话,就将key对应的value作为元组的次数添加
使用三层循环的话就会出现超时的错误,这边需要注意的是,需要统计第四个数组中某个元素出现的次数,因为这边数组中存在重复的部分,所以这边需要考虑,另外,到第三层循环发现结果不符合的时候,就需要回溯,即把当时遍历的那个元素从sum和中剪掉,这边刚开始写的时候没有考虑到

public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
      Map<Integer,Integer> map=new HashMap<>();
      int count=0;
      for(int i=0;i<nums4.length;i++){
          if(map.containsKey(nums4[i])){
              map.put(nums4[i],map.get(nums4[i])+1);//这边也是
          }else{
              map.put(nums4[i],1);
          }
      }
      for(int i=0;i<nums1.length;i++){
            int sum=0;
            sum +=nums1[i];
          for(int j=0;j<nums2.length;j++){
                sum +=nums2[j];
              for(int k=0;k<nums3.length;k++){
                sum +=nums3[k];
                if(map.containsKey(-sum)){
                    count +=map.get(-sum);//
                     sum -=nums3[k];//这边的问题
                }else{
                    sum -=nums3[k];
                }
            }
            sum -=nums2[j];
          }
          sum -=nums1[i];
      }
      return count;
    }

双层循环的解决:

public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
      Map<Integer,Integer> map=new HashMap<>();
      int count=0;
      for(int i=0;i<nums1.length;i++){
          for(int j=0;j<nums2.length;j++){
              int num=nums1[i]+nums2[j];
              if(map.containsKey(num)){
                  map.put(num,map.get(num)+1);
              }else{
                  map.put(num,1);
              }
          }
      }

      for(int i=0;i<nums3.length;i++){
          for(int j=0;j<nums4.length;j++){
              int num=nums3[i]+nums4[j];
              if(map.containsKey(-num)){
                 count +=map.get(-num);
              }
          }
      }
      return count;
    }
    

383. 赎金信

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。如果可以,返回 true ;否则返回 false 。magazine 中的每个字符只能在 ransomNote 中使用一次。

思路:magazine必须包含ransomNote中的所有字符,并且如果是相同字符的话,magazine中的字符数至少要大于等于ransomNote中对应字符的个数
遍历两个字符串,将字符存到map中

//错误的地方:String是一个字符串,是一个对象,不能采用foreach来进行字符的遍历
//需要将字符串转换成数组
 Map<Character,Integer> map2=new HashMap<>();
       for(Character c:magazine){
           if(map1.containsKey(c)){
               map1.put(c,map1.get(c)+1);
           }else{
               map1.put(c,1);
           }
       }

char[] a=b.toCharArray();//将字符串转化为字符数组

public boolean canConstruct(String ransomNote, String magazine) {
       Map<Character,Integer> map=new HashMap<>();
       char[] r1=magazine.toCharArray();//将字符串转换为字符数组
       char[] r2=ransomNote.toCharArray();
       for(int i=0;i<r1.length;i++){
           if(map.containsKey(r1[i])){
               map.put(r1[i],map.get(r1[i])+1);
           }else{
               map.put(r1[i],1);
           }
       }
       for(int j=0;j<r2.length;j++){
           if(!map.containsKey(r2[j])){
              return false;
           }else{
               map.put(r2[j],map.get(r2[j])-1);
               if(map.get(r2[j])<0){
                   return false;
               }
           }
       }
       return true;
    }

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。注意:答案中不可以包含重复的三元组。

他来了他来了,他带着不能包含重复的三元组走来了
思路:
需要注意的点:三个位置的下标必须全部不相同,而且数组中是存在重复的,所以使用map的话不能解决这个问题,而且需要返回下标,采用遍历+双指针的模式对这个题目进行解决,刚不小心看到了答案,看到了需要对原始的数组进行排序,这边是因为第一个被遍历的元素也可能是出现重复的情况,先进行排序的话,就会防止重复的出现,避免后面还需要降重。

public List<List<Integer>> threeSum(int[] nums) {
     List<List<Integer>> res=new ArrayList<>();
     Arrays.sort(nums);
     int left=0,right=0;
      for(int i=0;i<nums.length-2;i++){
          //i的前进
          //如果出现i的重复,就向前走
          while(i>0 && i<nums.length-2&& nums[i]==nums[i-1]){
             i++;
          }
          //这里要在将更新之后的i赋给left;
          left=i+1;
          right=nums.length-1;
          int sum=0;
            while(left<right){
                sum=nums[i]+nums[left]+nums[right];
                if(sum>0){
                    right--;
                }else if(sum<0){
                    left++;
                }else{
                    List<Integer> list=new ArrayList<>();//这边记得每次创建新的集合保存结果
                    list.add(nums[i]);
                    list.add(nums[left]);
                    list.add(nums[right]);
                    res.add(list);
                    right--;
                    left++;
                    //右指针出现的重复
                    while(left<right && nums[right]==nums[right+1]){
                        right--;//右指针的前进
                    }
                    //左指针出现的重复
                    while(left<right && nums[left]==nums[left-1]){
                        left++;//左指针的后退
                    }
                }
            }
      }
      return res;
    }

18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):0 <= a, b, c, d < n,a、b、c 和 d 互不相同,nums[a] + nums[b] + nums[c] + nums[d] == target,你可以按 任意顺序 返回答案 。

思路:四数之和和三数之和是同样的思路,只不过是对两层遍历+双指针
再次明确,这边说的不重复是指结果集中,不能出现两个四元组中的元素相同(这边不考虑顺序的问题,只要元素重复,就认为是相同的,比如说[1,2,2,3]和[3,2,2,1]就是相同的),和上一个题目相比,前两遍历的元素中需要适当的考虑,是的,对每一层for都需要进行判断;
注意点:如果排序过后,我们的最小值是正数,并且大于target,那么直接返回空集合就可以,不需要进行后面的计算

public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res=new ArrayList<>();
        Arrays.sort(nums);
        if(nums[0]>0 && nums[0]>target){
            return res;//感觉这里就像是特意为了通过测试用例写的
        }
        int left=0,right=0;
        for(int i=0;i<nums.length-3;i++){
        //同样也需要对i进行对应的操作
            while(i>0 && i<nums.length-3&&nums[i]==nums[i-1]){
                i++;
            }
            for(int j=i+1;j<nums.length-2;j++){
                /*
                //这样写的话会出现[2,2,2,2,2],target为8,但是结果集为空的问题,
                while(j<nums.length-2 && nums[j]==nums[j-1]){
                    j=j+1;
                }
                */
                //保证至少执行一个四个元素和的判断
                 while(j>i+1 && j<nums.length-2 && nums[j]==nums[j-1]){
                    j=j+1;
                }
                left=j+1;
                right=nums.length-1;
                int sum=0;
                while(left<right){
                    sum=nums[i]+nums[j]+nums[left]+nums[right];
                    if(sum>target){
                        right--;
                    }else if(sum<target){
                        left++;
                    }else{
                        List<Integer> list=new ArrayList<>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[left]);
                        list.add(nums[right]);
                        res.add(list);
                        right--;
                        left++;
                        while(left<right && nums[left]==nums[left-1]){
                            left++;
                        }
                        while(left<right && nums[right]==nums[right+1]){
                            right--;
                        }
                    }
                }
            }
        }
        return res;
    }

总结:
四个元组相加的时候,可以采用map来进行计算,并且时间复杂度达到了O(n2),普通的一个数组中实现四个不同位置处的元素值和为target的话,就不可以,因为有些元素可能会出现多次,使用map的话无法统计位置的信息

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值