算法训练营第七天|454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

54 阅读2分钟

LeetCode 454.四数相加II

四个数组,两两一组,和为0则结果加一

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer, Integer> map = new HashMap<>(); // <两数之和, 该和的出现次数>
        int res = 0;

        for(int a : nums1){
            for(int b : nums2){
                map.put(a + b, map.getOrDefault(a + b, 0) + 1);
            }
        }

        for(int c : nums3){
            for(int d : nums4){
                res += map.getOrDefault(0 - (c + d), 0);
            }
        }

        return res;
    }
}

LeetCode 383. 赎金信

数组在哈希法中的应用

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        // 题目要求只有小写字母
        int[] alphabet = new int[26];

        for(char m : magazine.toCharArray()){
            alphabet[m - 'a']++;
        }

        for(char r : ransomNote.toCharArray()){
            alphabet[r - 'a']--;
            if(alphabet[r - 'a'] < 0){
                return false;
            }
        }
        
        return true;
    }
}

LeetCode 15. 三数之和

nSum 系列问题的核心思路就是排序 + 双指针。三数之和这里,就要固定一个指针不动,然后移动另外两个指针寻找和。

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

        return res;
    }
}

还可以以递归的形式,写出nSum的通用代码。

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

        // n 为 3, 从 nums[0] 开始计算和为 0 的三元组
        return nSumTarget(nums, 3, 0, 0);
    }

    /**
    调用这个函数前一定要先给nums排序
    parameters: 
    @nums: 寻找范围的数组
    @n: 想求的是几数之和
    @start: 从哪个索引开始计算
    @target: 想凑出的目标和
    
    output:
    @res: List<List<Integer>> 
    */
    private List<List<Integer>> nSumTarget(int[] nums, int n, int start, int target){
        int size = nums.length;
        List<List<Integer>> res = new ArrayList<>();

        // 至少是两数之和,且数组大小不应该小于n
        if(n < 2 || size < n)return res;

        // 2Sum是base case
        if(n == 2){
            // 双指针那一套操作
            int low = start, high = size - 1;
            while(low < high){
                int sum = nums[low] + nums[high];
                if(sum < target){
                    while(low < high && nums[low] == nums[low + 1])low++;
                }
                else if(sum > target){
                    while(low < high && nums[high] == nums[high - 1])high--;
                }
                else{
                    res.add(new ArrayList<>(Arrays.asList(nums[low], nums[high])));
                    while(low < high && nums[low] == nums[low + 1])low++;
                    while(low < high && nums[high] == nums[high - 1])high--;
                }
            }
        }
        else{
            // n > 2 时,递归计算(n-1)Sum的结果
            for(int i = start; i < size; i++){
                // 剪枝
                // If the smallest possible sum is greater than target, break
                if(n * nums[i] > target) break;
                // If the largest possible sum is smaller than target, continue
                if(nums[i] + (n - 1) * nums[size - 1] < target) continue;
                
                List<List<Integer>> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
                for(List<Integer> arr : sub){
                    // (n-1)Sum 加上 nums[i] 就是 nSum
                    arr.add(nums[i]);
                    res.add(arr);
                }
                while(i < size - 1 && nums[i] == nums[i + 1])i++;
            }
        }
        return res;
    }
}

LeetCode 18. 四数之和

只对此题:

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

        for(int i = 0; i < nums.length; i++){
            if(nums[i] > target && (nums[i] >= 0 || target >= 0)){
                // nums[i] > target 并且排序后最小的数nums[i]非负,直接返回,剪枝操作
                return res;
            }
            if(i > 0 && nums[i] == nums[i-1]){
                continue;
            }

            for(int j = i + 1; j < nums.length;j++){
                if(j > i + 1 && nums[j] == nums[j-1]){
                    continue;
                }
                int left = j + 1;
                int right = nums.length - 1;
                while(left < right){
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if(sum > target){
                        right--;
                    }
                    else if(sum < target){
                        left++;
                    }
                    else{
                        res.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        while(left < right && nums[right] == nums[right - 1]) right--;
                        while(left < right && nums[left] == nums[left + 1]) left++;
                        right--;
                        left++;
                    }
                }
            }
        }
        return res;
    }
}

nSum模板:

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

        // n 为 4, 从 nums[0] 开始计算和为 target 的三元组
        return nSumTarget(nums, 4, 0, target);
    }

    /**
    调用这个函数前一定要先给nums排序
    parameters: 
    @nums: 寻找范围的数组
    @n: 想求的是几数之和
    @start: 从哪个索引开始计算
    @target: 想凑出的目标和
    
    output:
    @res: List<List<Integer>> 
    */
    private List<List<Integer>> nSumTarget(int[] nums, int n, int start, int target){
        int size = nums.length;
        List<List<Integer>> res = new ArrayList<>();

        // 至少是两数之和,且数组大小不应该小于n
        if(n < 2 || size < n)return res;

        // 2Sum是base case
        if(n == 2){
            // 双指针那一套操作
            int low = start, high = size - 1;
            while(low < high){
                int sum = nums[low] + nums[high];
                int leftVal = nums[low], rightVal = nums[high];
                if(sum < target){
                    while(low < high && nums[low] == leftVal)low++;
                }
                else if(sum > target){
                    while(low < high && nums[high] == rightVal)high--;
                }
                else{
                    res.add(new ArrayList<>(Arrays.asList(nums[low], nums[high])));
                    while(low < high && nums[low] == leftVal)low++;
                    while(low < high && nums[high] == rightVal)high--;
                }
            }
        }
        else{
            // n > 2 时,递归计算(n-1)Sum的结果
            for(int i = start; i < size; i++){
                // 剪枝
                // If the smallest possible sum is greater than target, break
                if(n * nums[i] > target) break;
                // If the largest possible sum is smaller than target, continue
                if(nums[i] + (n - 1) * nums[size - 1] < target) continue;

                List<List<Integer>> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);

                for(List<Integer> arr : sub){
                    // (n-1)Sum 加上 nums[i] 就是 nSum
                    arr.add(nums[i]);
                    res.add(arr);
                }
                while(i < size - 1 && nums[i] == nums[i + 1])i++;
            }
        }
        return res;
    }
}

// TODO: nSum模板在个别用例下会超时或是解答错误。