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

86 阅读1分钟

454. 四数相加 II

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

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

image.png

思路-使用map

假设我们有四个数组 A B C D, 遵循以下步骤:

  • 定义一个map集合,key 放 a + b 的和,value 放这个和出现的次数
  • 遍历 A 数组和 B 数组,统计两个数组中数字两两相加的和,以及出现的次数,放入map中
  • 定义一个变量count用来统计 a + b + c + d = 0的次数。
  • 遍历 C 数组和 D 数组,然后寻找key = 0 - c - d是否存在于map中,如果存在,count += map.get(key);否则就继续循环。
  • 最后返回count

注意:count += map.get(key)而不是count++
因为假设 a + b = 5 出现了2次,而 c + d = -5出现了1次。那么这样应该有2种情况 a + b + c + d = 0
例如:{a = 5, b = 0, c = -5, d = 0} {a = 2, b = 3, c = -5, d = 0};

代码实现

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int count = 0;
        Map <Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums1.length; ++i) {
            for (int j = 0; j < nums2.length; ++j) {
                int key = nums1[i] + nums2[j];
                map.put(key, map.getOrDefault(key, 0) + 1);
            }
        }

        for (int i = 0; i < nums3.length; ++i) {
            for (int j = 0; j < nums4.length; ++j) {
                int value = nums3[i] + nums4[j];
                int key = 0 - value;
                count += map.getOrDefault(key, 0);
            }
        }
        return count;
    }
}
  • 时间复杂度:O(n^2)
  • 空间复杂度: O(n^2),最坏情况下A和B的值各不相同,相加产生的数字个数为 n^2

383. 赎金信

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

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

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

image.png

思路

有效字母异位一个解法,只不过在第二循环判断条件的时候变成一旦dict数组中有任一个数小于0,那么ransomNote 就不能由 magazine 里面的字符构成。如果没有任何数小于0,则可以由magazine 里面的字符构成。

代码实现

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] dict = new int[26];
        for (int i = 0; i < magazine.length(); ++i ) {
            dict[magazine.charAt(i) - 'a']++;
        }

        for (int i = 0; i < ransomNote.length(); ++i) {
            if (--dict[ransomNote.charAt(i) - 'a'] < 0) {
                return false;
            }
        }
        
        return true;
    }
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

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

image.png

思路-双指针法

**思路参考下图: ** image.png
先排序
然后我们通过一个 for 循环用来先固定一个数,i 从下标 0 开始, left = i + 1, right = nums.length - 1
然后计算 nums[i] + nums[left] + nums[right] 是否等于0,如果等于0,就找到了一个结果集,如果大于0,我们就应该right--,因为我们对数组排过序,因此如果结果大于0,那么就应该减小nums[right],如果小于0,就left++
重复这个流程直到 i 遍历完整个数组。

去重操作

如果 nums[i] == nums[i - 1], 那么就代表以 nums[i] 为第一个数的结果集已经被收割过了,因此不用再检查了,可以直接跳过此次循环;
同时,如果 nums[left] == nums[left - 1]; nums[right] == nums[right + 1] 也是同理,如果遇到这两种情况也直接left++; right--;

代码实现

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        int left;
        int right;
        List<List<Integer>> list = new ArrayList<>();
        for (int i = 0; i < nums.length; ++i) {
            // 减枝操作,因为是从小到大排序的,因此如果第一个数已经大于0了
            // 那么不管怎么加,不可能等于0。
            if (nums[i] > 0) {
                continue;
            }
            // 去重操作
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            left = i + 1;
            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 {
                    list.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    left++;
                    right--;
                    // 去重操作
                    while ( left < right && nums[left] == nums[left - 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right + 1]) {
                        right--;
                    }
                }
            }
        }
        return list;
    }
}
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(1)

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

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

image.png

思路-双指针

三数之和 思路一样,只不过多了一层for循环,具体参考思路 三数之和

代码实现

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        List<List<Integer>> list = new ArrayList<>();
        for (int i = 0; i < nums.length; ++i) {
            // 剪枝:非必要操作,但可以优化效率
            if (nums[i] > 0 && nums[i] > target) {
                continue;
            }
            // 去重
            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[j] + nums[i] >= 0) {
                    break;
                }

                // 去重
                if (j > (i + 1) && nums[j] == nums[j - 1]) {
                    continue;
                }
                int left = j + 1;
                int right = nums.length - 1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum < target) {
                        left++;
                    } else if ( sum > target) {
                        right--;
                    } else {
                        list.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        left++;
                        right--;
                        // 去重
                        while (left < right && nums[left] == nums[left - 1]) {
                            left++;
                        } 
                        while (left < right && nums[right] == nums[right + 1]) {
                            right--;
                        }
                    }
                }
            }
        }
        return list;
    }
}
  • 时间复杂度: O(n^3)
  • 空间复杂度: O(1)