代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和

代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和

  • 454.四数相加II
    • 解法一:HashMap
  • 383. 赎金信
    • 解法一:数组记录
    • 解法二:双重循环暴力破解
  • 15. 三数之和
    • 解法一:排序+双指针(优)
    • 解法二:哈希法(易错不使用)
  • 18. 四数之和
    • 解法一
  • 总结


454.四数相加II

教程视频:https://www.bilibili.com/video/BV1Md4y1Q7Yh/?vd_source=ddffd51aa532d23e6feac69924e20891
代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和_第1张图片

解法一:HashMap

类比有效字母的异位词,可以判断本题应该使用哈希结构记录遍历过的数值和次数.(本题数值范围大且不去重所以使用HashMap).
四重for循环的时间复杂度为O(n4),为了降低复杂度,可以将四个数组平均拆分成两两数组之和,此时时间复杂度为O(n2)

因此确定思路:

  1. 首先定义 一个HashMap,key放a和b两数之和,value 放a+b出现的次数。
  2. 遍历A和B数组,统计两个数组元素a,b之和,和出现的次数,放到map中。
  3. 定义int变量result,用来统计 a+b+c+d = 0 出现的次数。
  4. 再遍历C和D数组,找到如果 0-(c+d) 在map中出现过的话,就用result把map中key对应的value也就是出现次数统计出来。
  5. 最后返回统计值 result
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, Integer> record = new HashMap<>();
        int result = 0;
        int temp;
        for(int num1:nums1){
            for(int num2:nums2){
                temp = num1+num2;
                if(record.containsKey(temp)){
                    record.put(temp,record.get(temp)+1);
                }else{
                    record.put(temp,1);
                }
            }
        }
        for(int num3:nums3){
            for(int num4:nums4){
                temp = 0-num3-num4;
                if(record.containsKey(temp)){
                    result+=record.get(temp);
                }
            }
        }
        return result;
    }

383. 赎金信

教程:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html#%E6%80%9D%E8%B7%AF
代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和_第2张图片

解法一:数组记录

因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。

    public boolean canConstruct(String ransomNote, String magazine) {
        //如果ransomNote长度更大,肯定不行
        if(ransomNote.length()>magazine.length()){
            return false;
        }
        //建立长度为26的int[]记录每个字符出现的次数(因为全都由小写英文字母组成)
        int[] record = new int[26];
        char[] mArr = magazine.toCharArray();
        for(char m : mArr){
            record[m-'a']++;
        }
        //遍历ransomNote判断是否可以由int[]中字符组成
        char[] rArr = ransomNote.toCharArray();
        for(char r : rArr){
            if(--record[r-'a']<0){
                return false;
            }
        }
        return true;
    }

解法二:双重循环暴力破解

复杂度高,暂不实现


15. 三数之和

教程视频:https://www.bilibili.com/video/BV1GW4y127qo/?spm_id_from=333.788&vd_source=ddffd51aa532d23e6feac69924e20891
代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和_第3张图片
代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和_第4张图片

解法一:排序+双指针(优)

时间复杂度:排序O(nLog(n))+搜索解O(n2)
要使得 a+b+c=0,即要在数组中找三个位置,这三个位置上的数值和为0。因此想到使用指针指定位置。
解题思路是:

  1. 先将数组排序(Arrays.sort(nums););
  2. 用指针表示选位置(i从下标0的地方开始for循环,下标 left 定义在 i+1 的位置,下标 right 在数组结尾的位置);
  3. 判断三数之和是否为0,若不为0则移动 left 或者 right ,直到left与right相遇为止;为0则同时移动 left 和 right,继续判断直到left与right重合;
  4. 由于题中给出abc三个数自身不重复,要考虑去重问题:
    本题重点在于去重细节的设计
  • a 在for循环开始时去重,若与【前一位置】相同则跳过本次循环。(必须与前一位置相比,否则记录不到当前a下的结果)
  • b c 在【记录结果后】去重,否则会漏掉本次找到的三元组。
    public List<List<Integer>> threeSum(int[] nums) {
        //先进行排序
        Arrays.sort(nums);
        List<List<Integer>> result = new ArrayList<>();//返回值

        for(int i = 0; i<nums.length;i++){
            //如果排序后第一个数大于0,则不可能找到三元组
            if(nums[i]>0){
                return result;
            }
            //对A去重复
            if(i>0 && nums[i]==nums[i-1]){
                continue;//i++
            }
            int left = i+1;
            int right = nums.length-1;
            while(left < right){
                if(nums[i]+nums[left]+nums[right]>0){
                    right--;
                }else if(nums[i]+nums[left]+nums[right]<0){
                    left++;
                }else{ // nums[i]+nums[left]+nums[right]==0
                    Integer[] list  = new Integer[3];
                    list[0] = nums[i];
                    list[1] = nums[left];
                    list[2] = nums[right];
                    result.add(Arrays.asList(list);//这里注意,先保存结果再进行去重
                    //对 b c 去重复
                    while(left < right && nums[left]==nums[left+1]){
                       left++; 
                    }
                    while(left < right && nums[right]==nums[right-1]){
                        right--;
                    }
                    left++;
                    right--;
                }
            }
        }
        return result;
    }

解法二:哈希法(易错不使用)


18. 四数之和

教程视频:https://www.bilibili.com/video/BV1DS4y147US/?spm_id_from=333.788&vd_source=ddffd51aa532d23e6feac69924e20891
代码随想录算法训练营day07 | 454.四数相加II , 383. 赎金信, 15. 三数之和 , 18. 四数之和_第5张图片
示例2说明,题中的“a、b、c 和 d 互不相同”指的是元素不同,值可以相等。

解法一

    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        //结果不重复,排序解决
        Arrays.sort(nums);

        for(int k=0;k<nums.length;k++){
            //剪枝1(注意这里增加了nums[k]>0,因为需要排除负数相加值减小的情况)
            if(nums[k]>0 && nums[k]>target){
                break;
            }

            //k去重复
            if(k>0 && nums[k]==nums[k-1]){
                continue;
            }

            for(int i = k+1;i<nums.length;i++){
                //剪枝2(将nums[k]+nums[i]看作整体,思路同剪枝1)
                int preSum = nums[k]+nums[i];
                if(preSum>0 && preSum>target){
                    break;
                }

                //i去重
                if(i>k+1 && nums[i]==nums[i-1]){
                    continue;
                }
                int left = i+1;
                int right = nums.length - 1;
                while(left<right){
                    // 题中说明了-10^9 <= nums[i] <= 10^9
                    // nums[k] + nums[i] + nums[left] + nums[right] > target int可能会溢出
                    // long sum = (long) nums[k]+nums[i]+nums[left]+nums[right];
                    long sum = (long) preSum+nums[left]+nums[right];
                    
                    if(sum>target){
                        // System.out.print("right--");
                        right--;
                    }else if(sum<target){
                        // System.out.print("left++");
                        left++;
                    }else{
                        Integer[] list = new Integer[4];
                        list[0] = nums[k];
                        list[1] = nums[i];
                        list[2] = nums[left];
                        list[3] = nums[right];
                        result.add(Arrays.asList(list));
                        //left和right去重
                        while(left<right && nums[left]==nums[left+1]){left++;}
                        while(left<right && nums[right]==nums[right-1]){right--;}
                        left++;
                        right--;
                    }
                }
            }
        }
        return result;
    }

总结

  1. 拆分数据时,平均分配有助于减少时间复杂度。
  2. 要求结果不重复往往要先进行排序,然后使用指针遍历。去重细节较多,需注意。
  3. 【三数之和】和【四数之和】细节较多,需要多练几遍。

你可能感兴趣的:(代码随想录训练营,算法,java,数据结构)