一维前缀和
前缀和:
就是一个数组,要快速静态查询区间和,我们只要处理一个数组时A[i]=a[0]+a[1]+a[2]+...+a[i-1].
A[0]=0
A[1]=a[0]
A[2]=a[1]+a[0]
A[3]=a[2]+a[1]+a[0]
A[4]=a[3]+a[2]+a[1]+a[0]
......
例如:求[0,3]的区间和,A[4]-A[0]=a[0]+a[1]+a[2]+a[3]=a[0]+a[1]+a[2]+a[3]-(a[0])
那么查询区间[l,r]的时候只要输出A[r+1]-A[l].
那么这是时候预处理是O(n)的,查询一次是O(1).
在很多情况下这种算法都是可行的,但是必须满足区间减法的性质.
代码预处理如下:
for (int i=1;i<=n;i++)
A[i]=A[i-1]+a[i];
303. 区域和检索 - 数组不可变
题目描述
给定一个整数数组 nums,求出数组从索引 left 到索引 right(包含 left 和 right)之间所有元素的和,包含多次这样的查询。
示例:
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // 返回 9,即 nums[0] + nums[1] + nums[2]
numArray.sumRange(2, 2); // 返回 5,即 nums[2]
numArray.sumRange(0, 1); // 返回 4,即 nums[0] + nums[1]
解答
class NumArray {
int A[];
public NumArray(int[] nums) {
A = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
A[i] = nums[i - 1] + A[i - 1];
}
}
public int sumRange(int l, int r) {
return A[r + 1] - A[l];
}
}
303. 区域和检索 - 数组不可变(变种题:累加和为给定值的最长子数组长度)
题目描述
给定一个无序数组 arr,其中元素可以是正数、负数或零。再给定一个整数 k,要求找出 arr 中所有子数组中累加和为 k 的最长子数组长度。
输入描述
- 第一行包含两个整数
N和k,其中N表示数组的长度,k是给定的累加和。 - 第二行包含
N个整数,表示数组arr内的元素。
输出描述
- 输出一个整数,表示累加和为
k的最长子数组的长度。
示例
示例1
输入:
5 0
1 -2 1 1 1
输出:
3
解释
在示例1中,数组 arr 为 [1, -2, 1, 1, 1],目标累加和 k 为 0。
- 子数组
[1, -2, 1]的累加和为0,长度为3。 - 其他子数组如
[1, 1, -1]的累加和也为0,但长度为3,没有更长的子数组满足条件。 - 因此,最长子数组的长度为
3。
解题思路
这个问题可以使用前缀和(Prefix Sum)和哈希表(HashMap)来高效解决。
- 初始化数组的前缀和
- 前缀和,index 加入map中,并计算出最大长度。index使用前最早前缀和的位置。
public static int maxKLen(int nums[], int k) {
int[] A = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
A[i] = nums[i - 1] + A[i - 1];
}
Map<Integer,Integer> map = new HashMap<>();
map.put(0,0);
int maxLen = 0;
for (int j = 1; j < A.length; j++) {
int target = A[j]-k;
if (map.containsKey(target)){
maxLen = Math.max(j-map.get(target),maxLen);
}
map.putIfAbsent(A[j],j);
}
return maxLen;
}
- 和为 K 的子数组
给定一个整数数组 nums 和一个整数 k,你需要找出该数组中和为 k 的连续子数组的个数。
示例
示例 1:
输入: nums = [1,1,1], k = 2
输出: 2
解释: [1,1] 和 [1,1] 是两个和为 2 的子数组。
示例 2:
输入: nums = [1,2,3], k = 3
输出: 2
解释: [1,2] 和 [3] 是两个和为 3 的子数组。
解题思路
- 前缀和 + 哈希表:
- 使用一个哈希表(字典)来存储从数组起始位置到当前位置的前缀和及其出现的次数。
- 遍历数组,计算当前位置的前缀和
prefix_sum。 - 检查
prefix_sum - k是否在哈希表中,如果在,则说明存在一个子数组的和为k。 - 更新哈希表,将当前的前缀和及其出现次数存入。
- 时间复杂度为 O(n),空间复杂度为 O(n)。
public int subarraySum(int[] nums, int k) {
int[] A = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
A[i] = nums[i - 1] + A[i - 1];
}
Map<Integer,Integer> map = new HashMap<>();
map.put(0,1);
int cnt = 0;
for (int j = 1; j < A.length; j++) {
int target = A[j]-k;
if (map.containsKey(target)){
cnt += map.get(target);
}
map.put(A[j],map.getOrDefault(A[j],0)+1);
}
return cnt;
}
题目描述
给定一个无序数组 arr,其中元素可以是正数、负数或零。要求找出 arr 中所有子数组中,正数与负数个数相等的最长子数组的长度。
输入描述
- 第一行一个整数
N,表示数组的长度。 - 接下来一行有
N个数,表示数组arr中的元素。
输出描述
- 输出一个整数,表示满足条件的最长子数组的长度。
示例
示例1
输入:
5
1 -2 1 1 1
输出:
2
解题思路
要解决这个问题,我们可以利用前缀和与哈希表的方法,但需要对“前缀和”进行一定的抽象和转化。在这里,我们可以将正数视为 +1,负数视为 -1,零则不影响计数,可以视为 0。计算数组长度时要包含0.
接下来,我们计算一个“特殊的前缀和”,它表示从数组起始位置到当前位置的正数与负数的“差值”(正数视为 +1,负数视为 -1)。当我们找到两个相同的前缀和值时,它们之间的子数组中正数与负数的个数必然相等(因为它们的差值都是零)。
public static int subArray(int[] nums) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
nums[i] = 1;
} else if (nums[i] < 0) {
nums[i] = -1;
}
}
int[] A = new int[nums.length + 1];
for (int i = 1; i <= nums.length; i++) {
A[i] = nums[i - 1] + A[i - 1];
}
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 0);
int maxLen = 0;
for (int j = 1; j < A.length; j++) {
int target = A[j];
if (map.containsKey(target)) {
maxLen = Math.max(j - map.get(target), maxLen);
}
map.putIfAbsent(A[j], j);
}
return maxLen;
}

浙公网安备 33010602011771号