前言
今日练习目的:掌握前缀和思维。前缀和的核心价值是能实现在O(1)时间求区间和,是各类子数组问题的常用工具
560:和为k的子数组
题目要求:给定一个整数数组nums和整数k
要求:统计并返回和为k的子数组
核心思路(前缀和+哈希表)
利用前缀和sum[i]表示数组前i个元素和,结合哈希表优化
- 定义sum[i]表示前i个元素和
- 一个子数组[j+1,i]的和为k,等价于sum[i]−sum[j]=k⇒sum[j]=sum[i]−k
- 用一个哈希表map记录前缀和出现的次数
代码实现
java
class Solution {
public int subarraySum(int[] nums, int k) {
Map<Integer, Integer> prefixSum = new HashMap<>();
prefixSum.put(0, 1); // 初始化前缀和 0 出现一次
int count = 0;
int currSum = 0;
for (int num : nums) {
currSum += num; // 更新前缀和
// 如果存在前缀和 currSum - k,则说明找到一个子数组
count += prefixSum.getOrDefault(currSum - k, 0);
// 更新哈希表
prefixSum.put(currSum, prefixSum.getOrDefault(currSum, 0) + 1);
}
return count;
}
}
总结
本题本质:通过前缀和+哈希表的方式。哈希表用来统计前缀和出现的次数
本题有点像两数之和,都是用哈希表快速查找差值 不同点在于
两数之和是找出下标
本题是统计出现的次数
209:长度最小的子数组
题目要求:给定一个正整数数组nums和一个整数target
要求:找出长度最小的连续子数组,使得子数组元素之和>=target。不存在则返回0
核心思路(前缀和+二分查找)
- 用前缀和快速求区间和
- 将问题转化为二分查找,找出 最小的右边界 j
- 使用Arrays.binarySearch 找 j
代码实现
java
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n=nums.length;
int[] prefix=new int[n+1];
//计算前缀和
for(int i=0;i<n;i++){
prefix[i+1]=prefix[i]+nums[i];
}
int minLen=Integer.MAX_VALUE;
//遍历每个左边界
for(int i=0;i<n;i++){
int key=prefix[i]+target;
//二分查找最小j,使prefix[j]>=key
int j=Arrays.binarySearch(prefix,key);
if(j<0){
// binarySearch 返回 -(插入点)-1
j=-j-1;
}
if(j<=n){
minLen=Math.min(minLen,j-i);
}
}
return minLen==Integer.MAX_VALUE?0:minLen;
}
}
238:除了自身以外数组的乘积
题目要求:给定一个整数数组nums,返回一个数组answer
要求:answer[i]=nums 中除 nums[i] 之外所有元素的乘积
核心思路
- 前缀积:prefix[i]表示nums[0...i-1]的乘积
- 后缀积:suffix[i]表示nums[i+1...n-1]的乘积
- 那么answer[i]=prefix[i]*suffix[i]
代码实现
java
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] answer = new int[n];
// 前缀积
answer[0] = 1;
for (int i = 1; i < n; i++) {
answer[i] = answer[i-1] * nums[i-1];
}
// 后缀积
int R = 1;
for (int i = n-1; i >= 0; i--) {
answer[i] = answer[i] * R;
R *= nums[i];
}
return answer;
}
总结
核心技巧:
用前缀积记录左边乘积
用后缀积记录右边乘积
相乘即可得到每个位置除自身外的乘积
优化空间:
先存前缀积在 answer
用一个变量 R 代替后缀积数组