随想录DAY7|力扣解题454. 四数相加 II、383. 赎金信、15. 三数之和、18. 四数之和

454. 四数相加 II

给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

思路:

这个题目看起来与下面会讲的18.四数之和很像,但是该题目给出的是四个独立的数组,而四数之和的话给的是一个数组且答案中不能含有重复的部分,这就需要我们进行大量的去重操作了,而本题并不需要,而且只要返回最后结果的个数即可。

我们可以讲nums1与nums2相加的数存放入一个map中,key为相加后元素的结果,val存放的是该结果出现的次数,再拿这个map容器与nums3与nums4相加的结果进行比较,如满足nums1+nums2 = -nums3-nums4的话则结果count += map.get(-nums3-nums4),遍历后返回这一结果即可

下面是代码演示

//思路:设四个数组里面取的数分别为a,b,c,d那么可以分为(a+b)+(c+d)
    //分别算出a+b和c+d的值,然后利用map进行匹配;
    //map中key存放(a+b)的值,val存放出现的次数
    //然后用c+d的值对map进行查找-(c+d)即可
    //因为a+b = 0-(c+d);
    //整体的时间复杂度为O(n^2)
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer,Integer> map = new HashMap<>();
        int result = 0;
        int sum;
        //将a+b的值及出现次数保存入map中
        for(int i :nums1){
            for(int j :nums2){
                sum = i+j;
                map.put(sum, map.getOrDefault(sum, 0) + 1);
            }
        }
        //在map中寻找-(c+d)
        for(int i :nums3){
            for(int j :nums4){
                result += map.getOrDefault(-i-j,0);

            }
        }
        return result;
    }

383. 赎金信

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

此题与我之前写过的一篇242.有效的字母异位词的思路是一致的,在此不再赘述,直接上代码

//与对比字母异位词很像,思路是一致的
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        //定义哈希映射数组
        int[] letter = new int[26];
        for (int i = 0; i < ransomNote.length(); i++) {
            letter[ransomNote.charAt(i)-'a']++;
        }
        for (int i = 0; i < magazine.length(); i++) {
            letter[magazine.charAt(i)-'a']--;
        }
        //如果ransomNote的字符全由magazine构成的话那么letter只有0与负数的结果
        for(int i :letter){
            if(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 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

梦碎裂的地方,太难了这题,疯狂的去重,思路不清晰的话很容易就写出bug了

思路:

本题并不好像四数相加一样使用哈希法,因为结果要求的是不重复的三元组,而哈希法很难做到这一点,正确的解题思路应该是采用双指针法

下面是思路图

偷懒了,借用卡哥的思路

拿这个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相遇为止。

还有个一个问题就是怎么去重,就是数组里面有很多连续的数放在一起的话很有可能给结果输入很多重复的三元集,那要怎么去重呢?

对a元素:

首先我们要确定的是,碰到相同的元素是,判断条件究竟是nums1[i] == nums1[i+1]跳过还是nums1[i] == nums1[i-1]跳过呢?假设有一个输入{-1,-1,-2},我们发现使用前者的话会把数组中重复的元素跳过!而我们想要的消除重复的结果集,所以我们要找到一个条件,让结果先输入到结果集里,再进行跳过,那么是哪个条件呢?

就是nums1[i] == nums1[i-1],因为这个判断条件只会跳过输入过结果的元素,符合题目的要求.

对b、c元素:

按理来说我们在对bc元素进行去重操作的时候是不是也要考虑像a那样的情况啊?其实并不需要。a之所以得那么操作是因为在每个元素上时都不确定结果,所以在去重时采用nums1[i] == nums1[i+1]的判断方法很容易就把结果集中的元素给去掉了,而b、c可以在保存了数据之后再进行去重的操作,这样就规避了a的问题了,所以他们两者的判断条件分别为nums1[left] == nums1[left+1]与nums1[right] == nums1[right-1]。

只要将结果收割入结果集中后,b、c相关的元素就统统不需要了,也就是都可以跳过了,所以可以采用这种判断方法

下面是代码演示:

public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);

        int left = 1;
        int right = nums.length - 1;
        List<List<Integer>> result = new ArrayList<>();
        if(nums[0]>0){
            return result;
        }
        //进行去重
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            right = nums.length - 1;
            left = i + 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                    right--;
                }
                else if (sum < 0) {
                    left++;
                }
                else {
                    result.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    //对于两个指针进行去重操作
                    while (right > left &&nums[left] == nums[left +1]) {
                        left++;
                    }
                    while (right > left &&nums[right] == nums[right - 1]) {
                        right--;
                    }
                    left++;
                    right--;

                }


            }
        }

        return result;
    }

18. 四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abc 和 d 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

思路 :

这道题的思路其实与上一道题的思路一致,但是去重的过程多了一层,剪枝去叶的操作也有些不同,大体上就是在三数之和的最外层多了一个for循环去遍历第四个数,所以时间复杂度也从三个数的O(n^2)变为了O(n^3),不再赘述,请看代码演示

//与三数之和是一个思路,不过结果的条件不一样,三数之和的结果是0,
    // 而四数之和的结果是target-nums[d]
    public List<List<Integer>> fourSum(int[] nums, int target) {
        //对数组进行从小到大的排序
        Arrays.sort(nums);
        int left;
        int right;
        List<List<Integer>> retsult = new ArrayList<>();
        //最外层遍历
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0 && nums[i] > target) {
                return retsult;//进行剪枝
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
                //进行去重
                continue;
            }
            //三数之和的逻辑
            for (int k = i + 1; k < nums.length; k++) {
                if (k > i + 1 && nums[k] == nums[k - 1]) {
                    //进行去重
                    continue;
                }
                long sum;//防止四个int数据相加导致溢出
                //target - nums[i] = nums[k] + nums[left] + nums[right];
                int newTarget = target - nums[i];
                left = k + 1;
                right = nums.length - 1;
                while (left < right) {
                    sum = nums[k] + nums[left] + nums[right];
                    if (sum < newTarget) {
                        left++;
                    } else if (sum > newTarget) {
                        right--;
                    } else {
                        retsult.add(Arrays.asList(nums[i], nums[k], nums[left], nums[right]));
                        //对于left与right对应元素进行去重
                        while (left < right && nums[left] == nums[left + 1]) {
                            left++;
                        }
                        while (left < right && nums[right] == nums[right - 1]) {
                            right--;
                        }
                        //进行下一次运算
                        left++;
                        right--;
                    }
                }

            }
        }
        return retsult;
    }

可以看到,第一层for循环后进行的就是三数之和的操作,所以在完全理解的第三题后会发现第四题无非是第三题的一个小拓展.

那么今天的解题就是这些,感谢大家的收看!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值