代码随想录刷题训练营第七天|● 454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ● 18. 四数之和 ● 总结

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

四数相加

看完题后的思路

在这里插入图片描述

首先想到了暴力解法,四重for循环,时间复杂度为0(n^4).接着往下想,四数相加不正好是(两数相加)+(两数相加)吗,可以依次求出两数相加的结果存入hash表中 和-[下标1,下标2],然后再进行两数相加.

思路

上面的思路中,有两点疑问:

  1. (1+2)+(3+4)=(1+3)+(2+4)吗? 1,2,3,4代表数组
    是相等的,
    在这里插入图片描述
    如上图,挑选(0,0,0,1),如果挑选数组是(1+2)+(3+4),反映到值上就是 (1-2)+(-1+2),如果是(1+3)+(2+4),反映到值上就是(1-1)+(-1+2),四个家加数并没有改变,改变只是加数的顺序

代码

    // 454 四数相加
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, List<int[]>> sum1_2 = towSumOfArra(nums1, nums2);
        HashMap<Integer, List<int[]>> sum3_4 = towSumOfArra(nums3, nums4);
        //开始第二层求和
       return twoSum02(sum1_2, sum3_4);
    }
    public  HashMap<Integer, List<int[]>> towSumOfArra(int[] nums1,int nums2[]){
        HashMap<Integer, List<int[]>> map = new HashMap<>();
        for (int i = 0; i <nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                int sum=nums1[i]+nums2[j];
                List<int[]> list = map.getOrDefault(sum, new ArrayList<>());
               list.add(new int[]{i,j});
                map.put(sum,list);
            }
        }
        return map;
    }

    public int twoSum02( HashMap<Integer, List<int[]>> map1, HashMap<Integer, List<int[]>> map2) {
        int res=0;
        for (Map.Entry<Integer, List<int[]>> entry : map1.entrySet()) {
            Integer key = entry.getKey();
            if (map2.containsKey(-1*key)){
                res+=(map1.get(key).size()*map2.get(-1*key).size());
            }
        }
        return res;
    }
}

复杂度

暴力:
时间复杂度 0(n^4)
空间复杂度 0(1)
hash表法
时间复杂度 0(n^2) (两两求和时)
空间复杂度0(n^2)
在这里插入图片描述

难点 收获

错误的将数组两两组合,再相加,导致结果重复

    // 454 四数相加
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, List<int[]>> sum1_2 = towSumOfArra(nums1, nums2);
        HashMap<Integer, List<int[]>> sum1_3 = towSumOfArra(nums1, nums3);
        HashMap<Integer, List<int[]>> sum1_4 = towSumOfArra(nums1, nums4);
        HashMap<Integer, List<int[]>> sum2_3 = towSumOfArra(nums2, nums3);
        HashMap<Integer, List<int[]>> sum2_4 = towSumOfArra(nums2, nums4);
        HashMap<Integer, List<int[]>> sum3_4 = towSumOfArra(nums3, nums4);
        //开始第二层求和
       return twoSum02(sum1_2, sum3_4)+twoSum02(sum1_3,sum2_4)+twoSum02(sum1_4,sum2_3);


    }
    public  HashMap<Integer, List<int[]>> towSumOfArra(int[] nums1,int nums2[]){
        HashMap<Integer, List<int[]>> map = new HashMap<>();
        for (int i = 0; i <nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                int sum=nums1[i]+nums2[j];
                List<int[]> list = map.getOrDefault(sum, new ArrayList<>());
               list.add(new int[]{i,j});
                map.put(sum,list);
            }
        }
        return map;
    }

    public int twoSum02( HashMap<Integer, List<int[]>> map1, HashMap<Integer, List<int[]>> map2) {
        int res=0;
        for (Map.Entry<Integer, List<int[]>> entry : map1.entrySet()) {
            Integer key = entry.getKey();
            if (map2.containsKey(-1*key)){
                res+=(map1.get(key).size()*map2.get(-1*key).size());
            }
        }
        return res;
    }

三刷时再看一遍

三数之和

看完题后的思路

想到了排序,使用双指针指向正数最小与负数最小,希望正数最小+负数最小能够递增,这样第三个指针从最大处递减
但是两个指针移动相同的方向,都是递增,所以无法一遍筛选出所有的情况

思路

排完序,i从0开始,left从i-1开始,right从数组末尾开始,通过left与right的移动来判断包含i的,且i是最小的元素的加和中有没有符合条件的
为什么left要从i+1开始?
因为i前面的的已经判断完,其作为最小的被加数的情况已经判断完
问题: 如何去重
在这里插入图片描述
去重的逻辑
三元组不能重复,三元组内的元素可以重复
对于三个数 a,b,c 分别对应nums[minItermIndex] nums[left] nums[right],如果nums[minItermIndex]==nums[minItermIndex-1],则重复,因为以a为最小元素的和在minItermIndex-1已经找遍了.当找到a+b+c=0时,此时不能跳出循环,因为可能还有例如 -2 -1+3与-2+0-2,所以left与right也要去重

  while (left<right&&nums[left]==nums[left+1]){
                        left++;
                    }
                     while (left<right&&nums[right]==nums[right-1]){
                         right--;
                     }
                     left++;
                     right--;
                }

代码

   public List<List<Integer>> threeSum(int[] nums) {
        int minItermIndex=0;// 最小一项的下标
        Arrays.sort(nums);
        ArrayList<List<Integer>> lists = new ArrayList<>();
        for (; minItermIndex < nums.length-2; minItermIndex++) {
            // 去重 如果
            if (minItermIndex>0&&nums[minItermIndex-1]==nums[minItermIndex]){
                continue;
            }
            int left=minItermIndex+1,right=nums.length-1;
            int minIterm = nums[minItermIndex];
            while (left<right){
                int sum = minIterm + nums[left] + nums[right];
                if (sum<0){
                   left++; 
                }else if (sum==0){
                    ArrayList<Integer> list = new ArrayList<>();
                  list.add(nums[minItermIndex]);
                    list.add(nums[left]);
                    list.add(nums[right]);
                    lists.add(list);
                    //不能 break; 因为可能还有 等于0的组合
                    //去重02 当最小元素定下来,left不能与上一种相同 0-1+1 与 0+0-0
                    while (left<right&&nums[left]==nums[left+1]){
                        left++;
                    }
                     while (left<right&&nums[right]==nums[right-1]){
                         right--;
                     }
                     left++;
                     right--;
                }else {
                    right--;
                }
            }
           
        }
        return lists;
    }

复杂度

时间复杂度 0(n^2) 每一轮双指针开销是0(1),一共n轮
空间复杂度 0(1)

收获

  1. 学到了一种双指针双向单调的情形,排序数组,双指针指向两端
  2. 欢动窗口中,也是一种双指针双向单调的情形,left+1,窗口减小,right+1,窗口增加
    三刷还要再来一遍

四数之和

四数之和与三数之和本质上一样,只粘出代码

    // 四数之和
        public List<List<Integer>> fourSum(int[] nums, int target) {
            Arrays.sort(nums);
            ArrayList<List<Integer>> lists = new ArrayList<>();
            for (int i = 0; i < nums.length; i++) {
                // 去重
                if (i>0&&nums[i]==nums[i-1]){
                    continue;
                }
                // 剪枝
                    // 剪枝
                    if ((target<=0&&nums[i]>0)){
                    
                        break;
                    }
                for (int j = i+1; j <nums.length ; j++) {
                    if (j>i+1&&nums[j]==nums[j-1]){
                        continue;
                    }
                    long sum_ij = nums[i] + nums[j];
                    long diff = target - sum_ij;
                
                    int left=j+1,right=nums.length-1;
                    while (left<right){
                        int sum =nums[left] + nums[right];
                        if (sum<diff){
                            left++;
                        }else if (sum==diff){
                            ArrayList<Integer> list = new ArrayList<>();
                            list.add(nums[i]);
                            list.add(nums[j]);
                            list.add(nums[left]);
                            list.add(nums[right]);
                            lists.add(list);
                            //不能 break; 因为可能还有 等于0的组合
                            //去重02 当最小元素定下来,left不能与上一种相同 0-1+1 与 0+0-0
                            while (left<right&&nums[left]==nums[left+1]){
                                left++;
                            }
                            while (left<right&&nums[right]==nums[right-1]){
                                right--;
                            }
                            left++;
                            right--;
                        }else {
                            right--;
                        }
                    }
                    
                }
            }
            return lists;
        } 

三刷再来一遍

赎金信

太简单了,粘贴代码如下

   // 赎金信
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length()>magazine.length()){
            return false;
        }
        int ransomNoteMap[]=new int[26];
        int magazineMap[]=new int[26];
        for (int i = 0; i < ransomNote.length(); i++) {
            char c = ransomNote.charAt(i);
            ransomNoteMap[c-'a']++;
        }

        for (int i = 0; i < magazine.length(); i++) {
            char c = magazine.charAt(i);
            magazineMap[c-'a']++;
        }

        for (int i = 0; i < 26; i++) {
            if (ransomNoteMap[i]>magazineMap[i]){
                return false;
            }
        }
        return true;
    }
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“代码随想录刷题训练营第七天|● 454.四数相加II ● 383. 赎金信 ● 15. 三数之和 ● 18. 四数之和 ● 总结” 的相关文章