算法Day6|454-四数相加II;383-赎金信;15-三数之和;18-四数之和

121 阅读6分钟

1、题目1: 454-四数相加II

题目:leetcode.cn/problems/4s…

类型:中等、数组、哈希

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

  • 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

思路:

  • 假设有A、B、C、D 是不是可以只遍历两个数组AB,获取到各个位置的和集合,然后再遍历数组C D,通过 0-AB集合的值在 数组CD中存在,那就找到了符合规则的四元组。

  • 需要统计某个值出现的次数,所以用map合适,value存储出现的次数

public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
    int count = 0;
    HashMap<Integer, Integer> map = new HashMap<>();
    // 统计两个数组中的元素之和,同时统计出现的次数,放入 map
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) {
            if(map.containsKey(0-i-j)){
                count +=map.get(0-i-j);
            }
        }
    }
    return count;
}

2、题目2: 383-赎金信

题目:leetcode.cn/problems/ra…

类型:简单

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

如果可以,返回 true ;否则返回 falsemagazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1 :
输入: ransomNote = "a", magazine = "b"
输出: false
示例 2 :
输入: ransomNote = "aa", magazine = "ab"
输出: false
示例 3 :
输入: ransomNote = "aa", magazine = "aab"
输出: true

代码:

public boolean canConstruct(String ransomNote, String magazine) {
    HashMap<Character,Integer> map = new HashMap<>();

    for (char c : magazine.toCharArray()) {
        map.put(c,map.getOrDefault(c,0)+1);
    }
    for (char c : ransomNote.toCharArray()) {
        if(map.containsKey(c)){
            Integer i = map.get(c);
            if(i>1){
                map.put(c,--i);
            }else{
                map.remove(c);
            }
        }else{
            return false;
        }
    }
    return true;
}

3、题目3: 15-三数之和

题目:leetcode.cn/problems/3s…

类型:中等

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != 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 。

思路:

  • 两层 for 循环就可以确定 a 和 b 的值,那么就可以使用 0-a-b 来判断是否在哈希中出现过,但题目中说不可以包含重复的三元组。这个就是本题的难点

本题使用哈希法不合适,程序的执行时间依然比较长。

而双指针法要比哈希法更高效些。双指针法一定要排序,不在意下标的时候可以用。

  • 1、拿 nums 数组来举例,首先将数组排序(一定),因此left++,right-- 才能不断的逼近目标值

  • 2、一层 for 循环,i 从下标 0 的地方开始,left 定义在 i+1 的位置,right 在数组结尾的位置

  • 3、如何移动 right、left,如果 nums[i] + nums[left] + nums[right] >0 就说明此时三数之和大了,right 左移,如果小了,left 右移动,直到 left 和 right 相遇。

  • 4、nums[i] >0,直接 break 跳出,因为此时3个元素都大于0,不可能找到结果

  • 5、当 i >0 且 nums[i] == nums[i-1] 时,跳过此元素 nums[i] ;因为已经将 nums[i-1] 的所有组合加入到结果中了,本次双指针搜索只会得到重复组合。 (注意不能是判断 nums[i] == nums[i+1], 因为可能会把结果集丢掉,例如[-1,-1,2] 的时候,就丢了这组结果集)

  • 6、left、right 分别在两端,当 left < right 循环计算 sum 值= nums[left] + nums[right] + nums[i]

    • 当 s<0 时,left+=1 并跳过所有重复的 nums[left];

    • 当 s>0 时,right--;并跳过所有重复的 nums[right];

    • 当 s=0时,记录组合[i,left,right] 放入 结果集

代码:

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

    Arrays.sort(nums);

    for (int i = 0; i < nums.length; i++) {
        if(nums[i]>0){
            return result;
        }
        // 去重
if(i>0 && nums[i] == nums[i-1]){
            continue;
        }
        int left = i+1;
        int right = nums.length -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]));
                // 去重逻辑,对 bc 去重
while (right>left && nums[right] == nums[right-1])
                    right--;
                while (left<right && nums[left] == nums[left+1])
                    left++;
                // 找到答案时,双指针同时收缩,查找其他的可能值
right--;
                left++;
            }
        }
    }
    return result;
}

4、题目4: 18-四数之和

题目:leetcode.cn/problems/4s…

类型:中等

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

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

类似4数之和,双指针解决.

  • 三数之和:双指针解法是一层 for 循环 nums[i] 作为确定值,然后根据 left 和 right 下标作为指针查找
  • 四数之和:两层 for 循环 nums[i] +nums[j] 作为确定值,依然是双指针。时间复杂度是On 的3次方
public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> result = new ArrayList<>();

    Arrays.sort(nums);

    for (int i = 0; i < nums.length; i++) {
        // 剪枝处理
if(nums[i]> target && nums[i] >= 0){
            break;
        }
        // 去重
if(i>0 && nums[i] == nums[i-1] ){
            continue;
        }

        for (int j = i+1; j < nums.length; j++) {
            if(nums[i] + nums[j] > target && nums[i]+nums[j] >=0){
                break;
            }
            // 去重
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{
                    result.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                    while (left<right && nums[left] == nums[left+1])
                        left++;
                    while (left<right && nums[right] == nums[right-1])
                        right--;
                    left++;
                    right--;
                }
            }
        }
    }
    return result;
}

5、总结

一般来说哈希表都是用来快速判断一个元素是否出现集合里。

对于哈希表,要知道哈希函数和哈希碰撞在哈希表中的作用。

哈希函数是把传入的key映射到符号表的索引上。

哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。

接下来是常见的三种哈希结构:

  • 数组
  • set(集合)
  • map(映射)

同时,不管是在哈希、链表、数组,都不可避免的出现了双指针法,这套方式可以使时间复杂度下降一个等级。

三数之和、四数之和等不可重复的的元素都可以使用方法进行计算。