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

91 阅读4分钟

454.四数相加II

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

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

454. 四数相加 II - 力扣(Leetcode)

思路

先同时遍历nums1nums2数组,将两数组的元素依次相加,统计和的频数,存储在哈希表map中。
要令nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0,则需要nums1[i] + nums2[j] == - (ums3[k] + nums4[l])
同时遍历nums3nums4数组,判断- (ums3[k] + nums4[l])是否在map中,如果在,则当前满足条件的情况有- (ums3[k] + nums4[l])的频数种。

代码

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {

        Map<Integer,Integer> map=new HashMap<>();

        for(int i=0;i<nums1.length;i++){
            for(int j=0;j<nums2.length;j++){
                map.put(nums1[i]+nums2[j],map.getOrDefault(nums1[i]+nums2[j],0)+1);
            }
        }

        int count=0;

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

        return count;
    }
}

383. 赎金信

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

383. 赎金信 - 力扣(Leetcode)

思路

统计ransomNote中各字母出现的频次,再遍历magazine,当出现ransomNote中的字母时,该字母对应的频数减1。在遍历完成后,如果有字母的频数仍大于1,则ransomNote 不能由 magazine 里面的字符构成。

代码

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] map=new int[26];

        for(int i=0;i<ransomNote.length();i++){
            map[ransomNote.charAt(i)-'a']++;
        }

        for(int i=0;i<magazine.length();i++){
            map[magazine.charAt(i)-'a']=map[magazine.charAt(i)-'a']>0?map[magazine.charAt(i)-'a']-1:0;
        }

        for(int i=0;i<map.length;i++){
            if(map[i]>0){
                return false;
            }
        }

        return true;
    }
}

也可以在遍历magazine时进行判断,代码如下:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] map=new int[26];

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

        for(int i=0;i<ransomNote.length();i++){
            map[ransomNote.charAt(i)-'a']-=1;
            if(map[ransomNote.charAt(i)-'a']<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 且不重复的三元组。 注意: 答案中不可以包含重复的三元组。

15. 三数之和 - 力扣(Leetcode)

思路

使用双指针法

  1. 对给定数组进行自然排序(从小到大)。
  2. 对数组进行遍历,对每一个数组元素,固定其作为三元组的一个元素,使用startend将该元素后的数组序列从两端进行遍历,判断nums[i] + nums[start] + nums[end]0的关系,当等于0时,找到一个三元组;否则,调整startend的位置继续遍历。

Tips

  1. 遍历数组,每个元素作为三元组中的固定元素。
  2. for(int i=0;i<nums.length-2;i++)i的取值:在nums[i]后的数组序列中还要取两个值组成三元组,也就是nums[i]后至少还有 2 个元素,i最大取值为i-3
  3. while(start<end) 中 为什么不是start<=end:因为{ nums[i], nums[start], nums[end] }三元组中,nums[start]nums[end]不能是同一个数组元素。

代码

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

        // 从小到大排序
        Arrays.sort(nums);

        int start,end,curNum;

        // nums[i]后至少还应有2个元素
        // i的最大值为 nums.length-3
        for(int i=0;i<nums.length-2;i++){
            if(nums[i]>0){
                break;
            }

            if(i>0&&nums[i-1]==nums[i]){
                continue;
            }

            start=i+1;
            end=nums.length-1;
            

            // start不能等于end
            // nums[i] nums[start] nums[end]构成一个三元组
            while(start<end){
                curNum=nums[i]+nums[start]+nums[end];
                if(curNum==0){
                    // 找到
                    res.add(Arrays.stream(new Integer[]{nums[i],nums[start],nums[end]}).toList());
                    // 去重
                    // 跳过与nums[start]和nums[end]相同的元素
                    do{
                        start++;
                    }while(start<end&&nums[start-1]==nums[start]);

                    do{
                        end--;
                    }while(start<end&&nums[end+1]==nums[end]);

                }else if(curNum>0){
                    end--;
                }else{
                    start++;
                }
            }
        }

        return res;
    }
}

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
    你可以按 任意顺序 返回答案 。

18. 四数之和 - 力扣(Leetcode)

思路

与15.三数之和使用的方法一样,先固定四元组的前两个元素,再通过双指针遍历后面数组序列来寻找使条件成立的数组元素。

Tips

  1. 使用两个for循环(foriforj)来确定四元组中的前两个元素,使用startend来遍历四元组第二个元素后的数组序列。因此,四元组的表示为:{ nums[i], nums[j], nums[start], nums[end] }
  2. 15.三数之和分析,本题中i的最大值为nums.length-4j的最大值为nums.length-3
  3. nums[i] > target && (nums[i] > 0 || target >= 0)剪枝条件:
    • 已知nums[i+1]nums[nums.length-1]都大于(或等于)nums[i]
    • 在满足nums[i] > target条件时,
    • 如果target >= 0
      • nums[i]与后面的元素的和必然大于等于nums[i],则必然大于target,条件必然不成立,可以剪枝。
    • target < 0
      • 如果 nums[i] < 0,数组中下标i之后的元素有可能是负数,nums[i]加上一个负数,结果小于nums[i],即结果有可能是target,条件有可能成立,不可以剪枝。
      • 如果 nums[i] > 0nums[i]加上任何正数一定大于 0,则必然大于target,条件不成立,可以剪枝。

代码

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {

        List<List<Integer>> res=new ArrayList<>();

        Arrays.sort(nums);

        int start,end,curNum;

        // nums[i]后至少有 3 个元素
        for(int i=0;i<nums.length-3;i++){
            if(i>0&&nums[i-1]==nums[i]){
                continue;
            }

            // nums[j]后至少有 2 个元素
            for(int j=i+1;j<nums.length-2;j++){
                if(nums[i]>target&&(nums[i]>0||target>=0)){
                    break;
                }

                if(j>i+1&&nums[j-1]==nums[j]){
                    continue;
                }

                start=j+1;
                end=nums.length-1;

                while(start<end){
                    curNum=nums[i]+nums[j]+nums[start]+nums[end];

                    if(curNum==target){
                        // 找到四元组
                        res.add(Arrays.stream(new Integer[]{nums[i],nums[j],nums[start],nums[end]}).toList());

                        // 去重
                        do{
                            start++;
                        }while(start<end&&nums[start-1]==nums[start]);

                        do{
                            end--;
                        }while(start<end&&nums[end+1]==nums[end]);

                    }else if(curNum>target){
                        end--;
                    }else{
                        start++;
                    }
                }
            }
        }

        return res;

    }
}