两数之和
我们先从一个极其简单 的问题开始吧!
给定一个整数数组nums和一个目标值target,数组中是否存在两个数,它们的和为target?
举个例子,nums=[5, 3, 1, 2, 6],target=9,那么存在这样的两个数(3,6),它们的和为9。
你立即有了思路,然后写出代码一气呵成:
class Solution {
public boolean twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return true;
}
}
}
return false;
}
}
思路很明确,双重for循环嵌套,O(n²)的时间复杂度。
接着思考,如何对其进行优化呢?
思考ing…
思考ing…
思考ing…
没错,空间换时间。看看下面的代码,你会恍然大悟:
class Solution {
public boolean twoSum2(int[] nums, int target) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if (set.containsKey(target - nums[i])) {
return true;
}
set.add(nums[i]);
}
return false;
}
}
这有点奇怪,但是这的确将时间复杂度降低到了O(n)。
玄机在于,在第二层中,O(n)的遍历变成了O(1)的哈希。
和为K的子数组
给定一个整数数组nums和一个目标值target,你需要找到该数组中和为 k 的连续的子数组的个数。
举个例子,nums=[5, 3, 1, 2, 2, 4],target=8,[5, 3]、[3, 1, 2, 2]、[2, 2, 4]都满足条件,一共有3个。
似乎很简单,前缀和优化模板题。
但我们是否可以沿用上面的哈希降维思路呢?请看下面的思路转化过程:
在两数之和问题中:
num1 + num2 = target ----> num2 = target - num1
因此我们可以通过哈希去寻找num2,而无需进行遍历。
我们借助前缀和将本题转化为两数之差问题!
在本题中:
preSum1 - preSum2 = target ----> preSum2 = preSum1 - target
和上面一样,我们可以通过哈希去寻找preSum2,而无需进行遍历。
class Solution {
public int subarraySum(int[] nums, int target) {
// 前缀和
int len = nums.length;
int[] preSum = new int[len + 1];
for (int i = 0; i < len; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}
// 计数
int count = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
for (int i = 0; i < len; i++) {
if (map.containsKey(preSum[i + 1] - target)) {
count += map.get(preSum[i + 1] - target);
}
map.put(preSum[i + 1], map.getOrDefault(preSum[i + 1], 0) + 1);
}
return count;
}
}
更加优雅的写法:
class Solution {
public int subarraySum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
map.put(0, 1);
int count = 0;
int preSum = 0;
for(int n : nums) {
preSum += n;
if (map.containsKey(preSum - target)) {
count += map.get(preSum - target);
}
map.put(preSum, map.getOrDefault(preSum, 0) + 1);
}
return count;
}
}
和为K的子矩阵
给出矩阵matrix[][]和目标值target,返回元素总和等于target的子矩阵的数量。
读本篇文章之前,你的思路可能是这样的:
计算二维前缀和,for遍历出所有的子矩阵,计算出子矩阵和,与target比较并计数——O(m²n²)。
读本篇文章之后,你的思路就会是这样的:
先将二维问题转化为多个一维子问题,对于每个一维子问题,使用哈希优化的算法——O(m²n)。
看看代码吧:
class Solution {
public int numSubmatrixSumTarget(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
// 构造列前缀和
int[][] preColSum = new int[m + 1][n + 1];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
preColSum[i + 1][j] = preColSum[i][j] + matrix[i][j];
}
}
// 合并列
int count = 0;
for (int top = 0; top < m; top++) {
for (int bottom = top; bottom < m; bottom++) {
// 构造一维矩阵
int[] nums = new int[n];
for (int j = 0; j < n; j++) {
nums[j] = preColSum[bottom + 1][j] - preColSum[top][j];
}
// 子问题
count += subarraySum(nums, target);
}
}
return count;
}
private int subarraySum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
int count = 0;
int preSum = 0;
for(int n : nums) {
preSum += n;
if (map.containsKey(preSum - target)) {
count += map.get(preSum - target);
}
map.put(preSum, map.getOrDefault(preSum, 0) + 1);
}
return count;
}
}
E N D END END