给定一个数组: a 1 , a 2 , a 3 , . . . a n a_1, a_2, a_3, ... a_n a1,a2,a3,...an
前缀和数组: s 1 , s 2 , s 3 , s 4 , . . . s n s_1, s_2, s_3, s_4,... s_n s1,s2,s3,s4,...sn
如何求前缀和数组: s i = a 1 + a 2 + a 3 + . . . + a i s_i = a_1 + a_2 + a_3 + ... + a_i si=a1+a2+a3+...+ai
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
作用:快速地求出某段区间的和
给你一个区间[l, r]
,求出该区间的和,只需要s[r] - s[l - 1]
s[r] = a[1] + a[2] + a[3] + ... a[l - 1] + a[l] + ... + a[r]
s[l-1] = a[1] + a[2] + a[3] + ... + a[l - 1]
s[r] - s[l - 1] = a[l] + a[l + 1] + ... + a[r]
定义:s[0] = 0
因为当l = 1
时,求[l, r] => [1, r]
此时s[0] = 0
, 所以s[r] - s[l - 1] = s[r] - s[0] = s[r]
公式仍然成立
一维前缀和可以通过O(n)的预处理,在O(1)的时间内求出某个区间的和
// 303 Java代码
class NumArray {
int[] s;
public NumArray(int[] nums) {
int n = nums.length;
s = new int[n + 1];
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + nums[i - 1];
}
public int sumRange(int left, int right) {
return s[right + 1] - s[left];
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* int param_1 = obj.sumRange(left,right);
*/
// 560 Java参考代码
class Solution {
public int subarraySum(int[] nums, int k) {
int n = nums.length;
int[] s = new int[n + 1];
for (int i = 1; i <= n; i++) {
s[i] = s[i - 1] + nums[i - 1];
}
int ans = 0;
Map<Integer, Integer> map = new HashMap<>();
map.put(0, 1);
for (int r = 1; r <= n; r++) {
int t = s[r];
if (map.containsKey(t - k)) ans += map.get(t - k);
map.put(t, map.getOrDefault(t, 0) + 1);
}
return ans;
}
}
原数组: a [ 1 ] [ 1 ] , a [ 1 ] [ 2 ] , . . . , a [ 1 ] [ m ] , a [ 2 ] [ 1 ] , a [ 2 ] [ 2 ] , . . . , a [ n ] [ m ] a[1][1], a[1][2], ..., a[1][m], a[2][1], a[2][2], ..., a[n][m] a[1][1],a[1][2],...,a[1][m],a[2][1],a[2][2],...,a[n][m]
前缀和数组: s [ 1 ] [ 1 ] , s [ 1 ] [ 2 ] , s [ 1 ] [ 3 ] , . . . , s [ n ] [ m ] s[1][1], s[1][2], s[1][3], ..., s[n][m] s[1][1],s[1][2],s[1][3],...,s[n][m]
// 求前缀和数组
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
// 求出任意区间的和[x1, y1],[x2, y2]
s = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
二维前缀和可以通过O(nm)的预处理,在O(1)的时间内求出某个区间的和
// 304 Java代码
class NumMatrix {
int[][] s;
public NumMatrix(int[][] matrix) {
int n = matrix.length, m = matrix[0].length;
s = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
public int sumRegion(int row1, int col1, int row2, int col2) {
row1++;
col1++;
row2++;
col2++;
return s[row2][col2] - s[row2][col1 - 1] - s[row1 - 1][col2] + s[row1 - 1][col1 - 1];
}
}
/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix obj = new NumMatrix(matrix);
* int param_1 = obj.sumRegion(row1,col1,row2,col2);
*/
// 363 Java代码
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
int n = matrix.length, m = matrix[0].length;
int[][] s = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
int ans = Integer.MIN_VALUE;
for (int x2 = 1; x2 <= n; x2++) {
for (int x1 = 1; x1 <= x2; x1++) {
TreeSet<Integer> map = new TreeSet<>();
map.add(0);
for (int y2 = 1; y2 <= m; y2++) {
int t = sumRegion(s, x1, 1, x2, y2);
Integer x = map.ceiling(t - k);
if (x != null) {
ans = Math.max(ans, t - x);
}
map.add(t);
}
}
}
return ans;
}
public int sumRegion(int[][] s, int row1, int col1, int row2, int col2) {
return s[row2][col2] - s[row2][col1 - 1] - s[row1 - 1][col2] + s[row1 - 1][col1 - 1];
}
}
通过知识点,我们可以猜出出题人会怎么考?
既然前缀和能快速的求出某段区间的和,那如果我不给你区间呢?我反过来直接给你某个区间和的值,但是我不告诉你是哪个区间,让你求出该区间,这时你会怎么做?
聪明的你应该想到了,那我就暴力枚举 l l l 和 r r r,这时就需要 O ( n 2 ) O ( n^2 ) O(n2) 的时间复杂度,如果出题人给出一个 1 0 5 10^5 105 量级的计算量,那么你就超时啦。
这时你最多枚举 l l l 或者 r r r ,另一个最多给你 O ( l o g n ) O(logn) O(logn) 是时间复杂度枚举,这时你就应该往二分或者哈希表上面去想了,挖掘出题目中的某些信息,从而求出答案。