代码随想录Day7

题目列表

  • 454.四数相加II(LeetCode)
  • 383.赎金信(LeetCode)
  • 15.三数之和(LeetCode)
  • 18.四数之和(LeetCode)

解题过程

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
示例 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

解题思路

根据示例可以看出这些元组的值允许重复。同时也要维护四个元素的和与个数的关系,采用哈希表 HashMap。
要在四个数组中各选一个数字加起来,那么就可以先处理前两个数组,算出所有的两数之和以及它们各自出现的次数,放到哈希表中,然后处理后两个数组,算这两个数组两数之和的同时判断 0 - nums3[i] - nums4[j] 在HashMap 的 key 中是否出现过,如果是,那么累计个数加上出现的次数,直到结束。

注意事项

1.元组允许重复

也就是说不用关心是具体的数值是多少,只要它们符合条件即可,这同时也意味着我们需要记录符合条件的 sum 出现的具体次数。

代码展示

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int res = 0;
        Map<Integer,Integer> map = new HashMap<>();
        
        for(int i : nums1){
            for(int j : nums2){
                int sum = i+j;
                map.put(sum,map.getOrDefault(sum,0)+1);
            }
        }
         for(int i : nums3){
            for(int j : nums4){
                int find = 0 - (i+j);
                res += map.getOrDefault(find,0);
            }
        }
        return res;
    }
}

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

解题思路

每个字母都只用一次,ransomNote里有的字母,有几个字母,magazine必须包含。
我想的是先使用哈希数组记录ransomNote中出现的字母以及次数,再遍历magazine进行减1,最后如果这个数组所有元素都不大于零,就说明满足要求。

代码展示

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        if(ransomNote.length() > magazine.length()){
            return false;
        }
        int [] record = new int [26];
        for(char c : ransomNote.toCharArray()){
            record [c-'a'] += 1;
        }
          for(char c : magazine.toCharArray()){
            record [c-'a'] -= 1;
        }
        for(int i : record){
            if(i > 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 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 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 。
提示:
3 <= nums.length <= 3000
-105 <= nums[i] <= 105

解题思路

预先看了网站的题解,这道题的难点在于“去重”,在结果集中不允许有重复的三元组。
如果先用哈希法得出所有相加为 0 的三元组再遍历去重,就会超时,哈希法不太适用。题解提供了一个使用双指针解法的思路:

  1. 首先要对数组进行排序(这道题不涉及记录下标,可以排序),数组有序是“双指针无遗漏地移动”以及“更便利地去重”的前提条件。
  2. 然后定义一层循环,i 从 0 开始,并且定义两个指针 left 和 right, left 初始指向 i + 1 的位置,right 初始指向 n - 1 的位置。
  3. 接下来进行寻找三元组以及去重:
    1. 先看如何获取到有效的三元组,在每一次 for 循环中,先计算 nums[i] + nums[left] + nums[right] 的值,如果 == 0 则记录在结果集中,如果 > 0,则需要 right 指针向前移动,如果 < 0,则需要 left 指针向后移动,一直到 right > left 这个条件不再满足,这一次循环就算结束了。
    2. 在寻找有效的解的同时要进行去重操作。
      首先是第一位的去重,也就是 i 指示的那个数,注意去重是不允许有重复的三元组,三元组中是可以有重复的元素的。因此去重条件应该是(nums[i]==nums[i-1]),这样就可以保证在每次大循环中无重复三元组,这得利于数组有序,因为后面的数字只大不小,所以不用担心跳过 nums[i] 后第二位第三位又出现与 nums[i] 相等的数。
      再注意第二位与第三位的去重,这两个位置的去重只发生在一次循环的内部,假如说这个数组的第一个数字是-1,剩下五个0和五个1,假如不干涉,到时候就会记录五个重复的三元组【-1,0,1】,这显然是不允许的,正确做法是要对left和right指示的数字进行判断去重,相等就要跳过。

注意事项

去重应该在判断三数之和是否等于0之后

设想一个数组全是 0 ,假如先去重,那么会一直去重,直到循环结束,这样就无法得到一个有效解【0,0,0】。首位去重的条件里有 i > 0 一方面是防止越界,另一方面也保证了先判断一次,再进行去重。而第二位和第三位的去重语句要在记录语句之后,防止上述情况发生。

代码展示

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);

        for(int i = 0;i < nums.length; i++){
            int left = i + 1;
            int right = nums.length - 1;

            //第一个位置去重
            if (i > 0 && nums[i] == nums[i - 1]) {  
                continue;
            }

            while(left < right){
                int sum = nums[i] + nums[left] + nums[right];
                if(sum == 0){
                    res.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--;
            
                }else if(sum > 0){
                    right--;
                }else{
                    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:
输入: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]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

解题思路

与三数之和类似,只是现在变成四个数,需要一个双层的for循环,边界条件要稍作修改。此外,三数之和与四数之和在循环中都可以剪枝提升效率,上一个题因为 target = 0 是确定的,所以直接判断当前数字是否大于0就可以了。但这道题不可以,因为target是随机的,如果简单地写为当前数字大于target,可能会遗漏情况(例如 target = -5, nums[0] = -4),nums[1] = -1)

注意事项

剪枝条件

如果当前数字是正数且已超过目标值,此时后续所有组合的四数之和必然大于目标值,因此可以提前终止循环.

代码展示

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);

        for(int k = 0; k < nums.length; k++){
            //剪枝
            if(nums[k] > target && nums[k] >= 0){
               break;
            }
             //第一个位置去重
            if (k > 0 && nums[k] == nums[k - 1]) {  
                continue;
            }

            for(int i = k + 1;i < nums.length; i++){
                //剪枝
                if((nums[k] + nums[i]) > target && nums[k] + nums[i] >= 0){
                    break;
                }

                //第二个位置去重
                if (i > k + 1 && nums[i] == nums[i - 1]) {  
                    continue;
                }

                int left = i + 1;
                int right = nums.length - 1;
                while(left < right){
                    int sum = nums[k] + nums[i] + nums[left] + nums[right];
                    if(sum < target){
                        left++;
                    }
                    else if(sum > target){
                        right--;
                    }
                    else{
                        res.add(Arrays.asList(nums[k], 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 res;
    }
}

参考资料

代码随想录

posted @ 2025-04-29 12:12  cbdsszycfs  阅读(299)  评论(0)    收藏  举报