注意:prefixsum一般都是用PrefixSum[n+1]的数组,prefix[0] = 0; 那么
因为i, j 是A里面的坐标,而prefixsum比A多了1;所以原来的sum[j] - sum[i-1]变成了
sum(i~j) = prefixsum(j + 1) - prefixsum(i);
Interval Sum 思路:因为这个题目不需要modify数组操作,所以用prefix sum array就可以解决,O(N + M) N是数组大小,M是query次数;
/**
* Definition of Interval:
* public classs Interval {
* int start, end;
* Interval(int start, int end) {
* this.start = start;
* this.end = end;
* }
* }
*/
public class Solution {
/**
* @param A: An integer list
* @param queries: An query list
* @return: The result list
*/
public List<Long> intervalSum(int[] A, List<Interval> queries) {
List<Long> list = new ArrayList<>();
int n = A.length;
long[] prefixsum = new long[n + 1];
prefixsum[0] = 0;
for(int i = 1; i <= n; i++) {
prefixsum[i] = prefixsum[i - 1] + A[i - 1];
}
for(Interval interval: queries) {
list.add(prefixsum[interval.end + 1] - prefixsum[interval.start]);
}
return list;
}
}
Subarray Sum 用prefixsum, A[i ~ j] = 0; 相当于 S[i-1] == S[j],也就是在数组里面找相等的数;hashmap, 存 S[i], i 就可以了;一定要存index;注意用hashmap put(0,0)因为是为了如果有sum直接== 0, 那么length应该是i - 0 = i;
public class Solution {
/**
* @param nums: A list of integers
* @return: A list of integers includes the index of the first number and the index of the last number
*/
public List<Integer> subarraySum(int[] nums) {
List<Integer> res = new ArrayList<>();
if(nums == null || nums.length == 0) {
return res;
}
int n = nums.length;
int[] prefixSum = new int[n + 1];
prefixSum[0] = 0;
for(int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
}
HashMap<Integer, Integer> hashmap = new HashMap<>();
hashmap.put(0, 0);
for(int i = 1; i <= n; i++) {
if(hashmap.containsKey(prefixSum[i])) {
// -1:prefixsum的index都要-1,才能map到原来的array index上;
// +1:A[i...j] == 0 -> sum[j] == sum[i - 1]; 求i, 所以要+1;
res.add(hashmap.get(prefixSum[i] - 1 + 1));
res.add(i - 1);
}
hashmap.put(prefixSum[i], i);
}
return res;
}
}
思路:固定右边界aj, 问题转换为找左边的两个边界,
XXXVVVVVVVVXXXXX aj
.....ai .................ak ........ aj
<=end >=start
这两个区间都是可以用双指针来计算得到的,O(N)
Prefix Sum 现在用n+1数组来求;
public class Solution {
/**
* @param A: An integer array
* @param start: An integer
* @param end: An integer
* @return: the number of possible answer
*/
public int subarraySumII(int[] A, int start, int end) {
if(A == null || A.length == 0) {
return 0;
}
int n = A.length;
int[] prefixSum = new int[n + 1];
prefixSum[0] = 0;
for(int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i - 1] + A[i - 1];
}
int res = 0;
/**
XXXXXXVVVVVVVVVXXXXXX j
>end <start
left right
*/
int left = 0, right = 0;
for(int j = 1; j <= n; j++) {
while(right < j && prefixSum[j] - prefixSum[right] >= start) {
right++;
}
while(left < j && prefixSum[j] - prefixSum[left] > end) {
left++;
}
res += (right - left);
}
return res;
}
}
Subarray Sum Closest 思路:将prefixsum的array,排序,然后比较相邻的两个sum,如果求diff最小的,注意;如果prefixsum == 0,则不用找了,找了个最佳答案,否则看最相近的;
public class Solution {
/*
* @param nums: A list of integers
* @return: A list of integers includes the index of the first number and the index of the last number
*/
private class Node {
public int index;
public int sum;
public Node(int index, int sum) {
this.index = index;
this.sum = sum;
}
}
public int[] subarraySumClosest(int[] nums) {
int[] res = new int[2];
if(nums == null || nums.length == 0) {
return res;
}
int n = nums.length;
int[] sum = new int[n + 1];
sum[0] = 0;
List<Node> list = new ArrayList<Node>();
for(int i = 1; i <= n; i++) {
sum[i] = sum[i - 1] + nums[i - 1];
if(sum[i] == 0) {
res[0] = 0;
res[1] = i - 1;
return res;
}
list.add(new Node(i, sum[i]));
}
Collections.sort(list, (a, b) ->(a.sum - b.sum));
int diff = Integer.MAX_VALUE;
for(int i = 0; i < list.size() - 1; i++) {
int curdiff = list.get(i + 1).sum - list.get(i).sum;
if(curdiff < diff) {
diff = curdiff;
// sum[i, j] = S[j] - S[i - 1], 所以真正的区间是: [i + 1, j]
if(list.get(i).index < list.get(i + 1).index) {
res[0] = list.get(i).index;
res[1] = list.get(i + 1).index - 1;
} else {
res[0] = list.get(i + 1).index;
res[1] = list.get(i).index - 1;
}
}
}
return res;
}
}
Submatrix Sum 用prefixsum,代表是从(0,0) 到(i,j) 的区域sum。然后固定上边界和下边界,然后扫描j,用hashmap来找有没有重复的区域,如果有就找到了区域为0的,然后记录答案,注意,因为prefixsum是+1了的index,所以,最后右下角的点需要减去1,左上角的点因为默认+1,所以不用减去1了。 O(N^3); Space O(N^2);
public class Solution {
/*
* @param matrix: an integer matrix
* @return: the coordinate of the left-up and right-down number
*/
public int[][] submatrixSum(int[][] matrix) {
int[][] res = new int[2][2];
if(matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return res;
}
int m = matrix.length;
int n = matrix[0].length;
int[][] prefixSum = new int[m + 1][n + 1];
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= n; j++) {
if(i == 0 || j == 0) {
prefixSum[i][j] = 0;
} else {
prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1] + matrix[i - 1][j - 1];
}
}
}
for(int x1 = 0; x1 < m; x1++) {
for(int x2 = x1 + 1; x2 <= m; x2++) {
// 记住,这里每次循环都要新建一个hashmap;
HashMap<Integer, Integer> hashmap = new HashMap<>();
for(int j = 0; j <= n; j++) {
int area = prefixSum[x2][j] - prefixSum[x1][j];
if(hashmap.containsKey(area)) {
int k = hashmap.get(area);
res[0][0] = x1;
res[0][1] = k;
res[1][0] = x2 - 1;
res[1][1] = j - 1;
} else {
hashmap.put(area, j);
}
}
}
}
return res;
}
}
Lintcode: Continuous Subarray Sum 思路:
思路:用prefixsum来做,求最大,那么就跟股票那题一样,存一个最小,然后每次跟最小比差,如果大,记录下来。注意prefixsum的index默认是+1了的,所以result是[start, i - 1];
是因为sum[i, j] = S[j] - S[i - 1], = prefixsum[j + 1] - prefixsum[i], prefixsum里面的index是原来数组的index + 1所形成。所以在prefixsum的index要还原成原来array的index都要减去1,然而,S[j] - S[i - 1] == Sum[i,j],所以求i的范围,需要把start +1,这样start + 1 - 1=start, end - 1,就是对应原来数组的index了;
public class Solution {
/*
* @param A: An integer array
* @return: A list of integers includes the index of the first number and the index of the last number
*/
public List<Integer> continuousSubarraySum(int[] A) {
List<Integer> list = new ArrayList<Integer>();
if(A == null || A.length == 0) {
return list;
}
int sum = 0;
int minsum = 0;
int max = Integer.MIN_VALUE;
int start = 0, end = 0;
list.add(start);
list.add(end);
int n = A.length;
for(int i = 1; i <= n; i++) {
sum += A[i - 1];
if(sum - minsum > max) {
max = sum - minsum;
list.set(0, start);
list.set(1, i - 1);
}
if(sum < minsum) {
minsum = sum;
start = i;
}
}
return list;
}
}
[LeetCode Continuous Subarray Sum] 思路:这题算法是根据(a + n*k) % k == a % k, 所以 [23,2,6,4,7] k = 6, the running sum is [23,25,31,35,42] and the remainders are [5,1,1,5,0]. We got remainder 5 at index 0 and at index 3. That means, in between these two indexes we must have added a number which is multiple of the k. 也就是跟two sum类似,如果存 ( cursum % k )余数和index,那么如果我又遇见了同样的余数,那么代表中间肯定有个subarray 和是n * k. 注意这题tricky的地方是: [0,0] 0 或者[6,6] 6 这种,如果map.put(0,0)会返回false,处理的方法就是map.put(0,-1)这样代表余数是0的情况可以有1 个,我遇见的下一个,肯定可以判断是len > 1 从而返回true
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
if(nums == null || nums.length == 0) {
return false;
}
HashMap<Integer, Integer> hashmap = new HashMap<>();
// value = -1 is due to the fact that the size of the subarray is required to be least two
// 因为key 是sum,value是index,题目要求array at least 2.所以,如果找到一个sum,另外一个index 减去current index,就必须 > 1; 所以存-1;
hashmap.put(0, -1);
int cursum = 0;
for(int i = 0; i < nums.length; i++) {
cursum += nums[i];
if(k != 0) {
cursum = cursum % k;
}
Integer pre = hashmap.get(cursum);
if(pre != null) {
if(i - pre > 1) {
return true;
}
} else {
hashmap.put(cursum, i);
}
}
return false;
}
}
Range Sum Query - Immutable Prefix 模板,Sum[i, j] = prefixsum[j + 1] - prefixsum[i];
class NumArray {
private int[] prefixsum;
public NumArray(int[] nums) {
int n = nums.length;
this.prefixsum = new int[n + 1];
for(int i = 1; i <= n; i++) {
prefixsum[i] = prefixsum[i - 1] + nums[i - 1];
}
}
public int sumRange(int i, int j) {
return prefixsum[j + 1] - prefixsum[i];
}
}
/**
* Your NumArray object will be instantiated and called as such:
* NumArray obj = new NumArray(nums);
* int param_1 = obj.sumRange(i,j);
*/
Subarray Sum Equals K 思路:用prefixsum prefixsum[j+1] - prefixsum[i] = k, O(N^2) 优化就是:类似2sum,用hashmap优化,注意先判断是否含有,再加入,否则会有重复计算;O(N) Space O(N);
class Solution {
public int subarraySum(int[] nums, int k) {
if(nums == null || nums.length == 0) {
return 0;
}
int n = nums.length;
int cursum = 0;
// sum frequency;
HashMap<Integer, Integer> hashmap = new HashMap<>();
int count = 0;
for(int i = 0; i < nums.length; i++) {
cursum += nums[i];
if(cursum == k) {
count++;
}
// 存prefixsum - k, 也就是之前的比较小的数,这样在后面一旦碰见,那么就表示相吻合,a - b = k;
// count已经加过1了,那么这里只需要加入之前的频率。
if(hashmap.containsKey(cursum - k)) {
count += hashmap.get(cursum - k);
}
// hashmap里面的频率要+1;
hashmap.put(cursum, hashmap.getOrDefault(cursum, 0) + 1);
}
return count;
}
}
Number of Submatrices That Sum to Target 这类矩阵的题目,就矩阵和,都是prefixsum的思路,这里比较巧妙的是,用每一行的prefixsum来求解,i, j 是start col, end col, k是row,相当于从右往左投影计算array[k], 把j往左投影,然后array 其实是一个竖着的矩阵往左投影sum的结果。问题就变成了560. Subarray Sum Equals K , 注意hashmap每次都要重新生成,因为i, j 变化了,那么相当于每次生成一个新的array了,所以每次都是一次新的计算; Time : O(col ^2 * row);
class Solution {
public int numSubmatrixSumTarget(int[][] matrix, int target) {
int n = matrix.length;
int m = matrix[0].length;
int[] array = new int[n];
int res = 0;
for(int i = 0; i < m; i++) {
Arrays.fill(array, 0);
for(int j = i; j < m; j++) {
for(int k = 0; k < n; k++) { // j每次增加一格,k从上到下,都要加入对应的array[k];
array[k] += matrix[k][j]; // 把每一行k, [i,j] sum起来;
}
res += subarraySum(array, target);
}
}
return res;
}
public int subarraySum(int[] nums, int k) {
if(nums == null || nums.length == 0) {
return 0;
}
HashMap<Integer, Integer> hashmap = new HashMap<>();
int sum = 0;
int count = 0;
for(int i = 0; i < nums.length; i++) {
sum += nums[i];
if(sum == k) {
count++;
}
if(hashmap.containsKey(sum - k)) {
count += hashmap.get(sum - k);
}
hashmap.put(sum, hashmap.getOrDefault(sum, 0) + 1);
}
return count;
}
}
Find Pivot Index 思路:prefixsum的模板,注意index可以是最后一个,因为整个array sum == 0,也是可以的;
class Solution {
public int pivotIndex(int[] nums) {
if(nums == null || nums.length == 0) {
return -1;
}
int n = nums.length;
int[] prefixsum = new int[n + 1];
prefixsum[0] = 0;
for(int i = 1; i <= n; i++) {
prefixsum[i] = prefixsum[i - 1] + nums[i - 1];
}
for(int i = 1; i <= n; i++) {
int leftsum = prefixsum[i - 1];
int rightsum = prefixsum[n] - prefixsum[i];
if(leftsum == rightsum) {
return i - 1;
}
}
return -1;
}
}
Split Array with Equal Sum 思路:先固定j,然后左边分两半,sum相等就存入hashset,然后右边分两半,sum相等,就看sum在不在hashset里面;这里presum用0开始的index,这样写prefixsum好写。严格按照// 0 < i, i + 1 < j, j + 1 < k < n - 1; 这个提示来写。
class Solution {
public boolean splitArray(int[] nums) {
if(nums == null || nums.length == 0) {
return false;
}
int n = nums.length;
int[] prefixsum = new int[n];
prefixsum[0] = nums[0];
for(int i = 1; i < n; i++) {
prefixsum[i] = prefixsum[i - 1] + nums[i];
}
// 0 < i, i + 1 < j, j + 1 < k < n - 1
for(int j = 3; j < n - 3; j++) {
HashSet<Integer> set = new HashSet<>();
for(int i = 1; i + 1 < j; i++) {
if(prefixsum[i - 1] == prefixsum[j - 1] - prefixsum[i]) {
set.add(prefixsum[i - 1]);
}
}
for(int k = j + 2; k < n - 1; k++) {
if(prefixsum[k - 1] - prefixsum[j] == prefixsum[n - 1] - prefixsum[k]) {
if(set.contains(prefixsum[n - 1] - prefixsum[k])) {
return true;
}
}
}
}
return false;
}
}
Product of the Last K Numbers 思路:prefix product array,注意默认的第一位是个1,然后往后面加 list.get( list.size() - 1) * num;最后,k == n的时候,因为前面有1,所以return 0,超过了输入num的个数范围;
class ProductOfNumbers {
private ArrayList<Integer> list;
public ProductOfNumbers() {
list = new ArrayList();
list.add(1); // 跟prefix一样,第一个元素add为1;
}
public void add(int num) {
if(num == 0) {
list.clear();
list.add(1);
} else {
list.add(list.get(list.size() - 1) * num);
}
}
public int getProduct(int k) {
int n = list.size();
//因为前面加了默认1,跟prefixsum一样,所以k == n 相当于k 大于了元素个数了,直接return 0;
if(k >= n) {
return 0;
} else {
return list.get(n - 1) / list.get(n - 1 - k);
}
}
}
/**
* Your ProductOfNumbers object will be instantiated and called as such:
* ProductOfNumbers obj = new ProductOfNumbers();
* obj.add(num);
* int param_2 = obj.getProduct(k);
*/
Find Two Non-overlapping Sub-arrays Each With Target Sum 这题很类似于560. Subarray Sum Equals K, 不同在于要找两个不同的subarray,那么如何找两个array就是利用dp[i]来找,dp[i]存的物理意义是,到目前subarry sum为target的最小的length,如果pre != -1 && dp[pre] != Integer.MAX_VALUE代表找到了第二array,之前还有一个array和为target;因为dp[i] = dp[i - 1];所以只有一个的话,dp[pre]会一直是integer.MAX_VALUE;
注意:计算prefixsum的index,必须加入-1,因为前几个数和就是target,如果要算length,就是i - (-1) = i + 1;因为i是0开头的;560. Subarray Sum Equals K 那个题是用hashmap存prefixsum , frequency. 计算频率,就(0,1)如果计算index或者长度就是(0,-1)。
class Solution {
public int minSumOfLengths(int[] arr, int target) {
if(arr == null || arr.length == 0) {
return -1;
}
HashMap<Integer, Integer> hashmap = new HashMap<>();
// 计算prefixsum的index,必须加入-1,因为前几个数和就是target,如果要算length,就是i - (-1) = i + 1;因为i是0开头的;
hashmap.put(0, -1);
int n = arr.length;
int[] dp = new int[n];
int res = Integer.MAX_VALUE;
int cursum = 0;
for(int i = 0; i < arr.length; i++) {
cursum += arr[i];
dp[i] = i > 0 ? dp[i - 1] : Integer.MAX_VALUE;
if(hashmap.containsKey(cursum - target)) {
int pre = hashmap.get(cursum - target);
dp[i] = Math.min(dp[i], i - pre);
// 如何求得两个区间,就是用dp[i] 记录到目前为止,cursum == target的array的最小的length;
// 如果 pre == -1 或者dp[pre] == Integer.MAX_VALUE,代表之前只找到一个区间;如果不是,那么就是之前已经找到一个区间,现在找到了第二个区间;
if(pre != -1 && dp[pre] != Integer.MAX_VALUE) {
res = Math.min(res, dp[pre] + i - pre);
}
}
hashmap.put(cursum, i);
}
return res == Integer.MAX_VALUE ? -1 : res;
}
}
Max Sum of Rectangle No Larger Than K 核心思想就是:每列column,从左向右投影,left, right, 是左右边界,然后sums[n] 是长度为row的投影sum,也就是prefixsum。每次移动left, right,计算出sums array之后,问题就转换为,在一个array里面,找subarray sum最接近K的sum;这里用了treeset ceiling是第一个大于input的value,num >= currsum - k; currsum - num <= k; 每次update这个currsum - num 就可以了。currsum 是累加的sum;
注意:currusm - num 代表的就是下面的block的matrix;set.add(0); // 需要padding,是为了解决只有一个row value的情况;比如说cursum = 10, k = 10,那么就只有一个sum的话,就是要返回10,如果没有0,就会返回null,得不到sum就等于k的情况;
class Solution {
public int maxSumSubmatrix(int[][] matrix, int k) {
if(matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int n = matrix.length;
int m = matrix[0].length;
int result = Integer.MIN_VALUE;
for(int left = 0; left < m; left++) {
int[] arr = new int[n];
for(int right = left; right < m; right++) {
for(int i = 0; i < n; i++) {
arr[i] += matrix[i][right];
}
TreeSet<Integer> treeset = new TreeSet<>();
// 需要padding,是为了解决只有一个row value的情况;
// 比如说cursum = 10, k = 10,那么就只有一个sum的话,就是要返回10,如果没有0,就会返回null,得不到sum就等于k的情况;
treeset.add(0);
int cursum = 0;
for(int prefixsum: arr) {
cursum += prefixsum;
// num >= cursum - k;
// k >= cursum - num;
Integer num = treeset.ceiling(cursum - k);
if(num != null) {
// k >= cursum - num;
// currusm - num 代表的就是下面的block的matrix;
result = Math.max(result, cursum - num);
}
treeset.add(cursum);
}
}
}
return result;
}
}
Shortest Subarray with Sum at Least K 思路:这题跟Minimum Size Subarray Sum 很相似,不同的是,加入了负数,使得问题变复杂了。思路就是:求区间和,很容易想到prefixsum,这个很好求出,但是怎么扫描去求sum>=k的最小区间长度,sum[i] .... sum[j].... sum[k] 如果sum[i] > sum[j], sum[k] - sum[i] >=k, 那么sum[k] - sum[j] 更加>=k,同时j > i,那么j到k区间会更小,那么sum[i]就没必要存,发现这个性质那么就是单调栈,前面的数sum[i], 比当前的数sum[j]要大,就没必要存,那么栈里面就是单调递增的,我们要保持里面是递增的,那么后面进来的数,如果比last要小,则踢掉前面的数,为什么?是因为如果存了,last > i, future - last >= k, 那么future - i 更加>= k, 所以 last比i大,没必要存;
More detailed on this, we always add at the LAST position
B[d.back] <- B[i] <- ... <- B[future id]
B[future id] - B[d.back()] >= k && B[d.back()] >= B[i]
B[future id] - B[i] >= k too
前后都要进出,那么用deque可以满足要求。这里要注意一点,长度是 i - deque.peekFirst()。为什么,是因为sum[j] - sum[i] ,subarray的长度其实是i + 1,...j,那么就是j - i + 1 - 1 = j - i;
class Solution {
public int shortestSubarray(int[] A, int K) {
if(A == null || A.length == 0) {
return -1;
}
int n = A.length;
int[] prefixsum = new int[n + 1];
prefixsum[0] = 0;
for(int i = 1; i <= n; i++) {
prefixsum[i] = prefixsum[i - 1] + A[i - 1];
}
Deque<Integer> arrayDeque = new ArrayDeque<Integer>();
int res = Integer.MAX_VALUE;
for(int i = 0; i <= n; i++) {
while(!arrayDeque.isEmpty() && prefixsum[i] - prefixsum[arrayDeque.peekFirst()] >= K) {
res = Math.min(res, i - arrayDeque.peekFirst());
arrayDeque.pollFirst();
}
while(!arrayDeque.isEmpty() && prefixsum[i] <= prefixsum[arrayDeque.peekLast()]) {
arrayDeque.pollLast();
}
arrayDeque.offer(i);
}
return res == Integer.MAX_VALUE ? -1 : res;
}
}
Maximum Side Length of a Square with Sum Less than or Equal to Threshold 思路:build 矩阵的prefixsum ,记住建立的时候是 sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
利用prefixsum求正方形的时候是:sum[i][j] - sum[i - len][j] - sum[i][j - len] + sum[i - len][j - len]
建立好prefixsum之后,在0,math.min(n,m)之间做binary search, T: n * m * log(min(n,m));
生成prefixsum的时候,是从上到下生成,计算区间的时候,是相减的;不要记忆公式,逻辑推理就行;
class Solution {
public int maxSideLength(int[][] mat, int threshold) {
int n = mat.length;
int m = mat[0].length;
int[][] sum = new int[n + 1][m + 1];
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
}
}
int start = 0; int end = Math.min(n, m);
while(start + 1 < end) {
int mid = start + (end - start) / 2;
if(canhave(mat, mid, threshold, sum)) {
start = mid;
} else {
end = mid;
}
}
if(canhave(mat, end, threshold, sum)) {
return end;
}
if(canhave(mat, start, threshold, sum)) {
return start;
}
return 0;
}
private boolean canhave(int[][] mat, int len, int threshold, int[][] sum) {
for(int i = len; i <= mat.length; i++) {
for(int j = len; j <= mat[0].length; j++) {
if(sum[i][j] - sum[i - len][j] - sum[i][j - len] + sum[i - len][j - len] <= threshold) {
return true;
}
}
}
return false;
}
}
思路2:这个比较巧妙,就是在建立prefixsum 矩阵的时候,一起判断,是否有比len更大的合法正方形,有就len++;O(m * n);
class Solution {
public int maxSideLength(int[][] mat, int threshold) {
if(mat == null || mat.length == 0 || mat[0].length == 0) {
return 0;
}
int n = mat.length;
int m = mat[0].length;
int[][] prefixsum = new int[n + 1][m + 1];
int len = 1;
int res = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
prefixsum[i][j] = prefixsum[i - 1][j] + prefixsum[i][j - 1] - prefixsum[i - 1][j - 1] + mat[i - 1][j - 1];
if(i - len >= 0 && j - len >= 0
&& prefixsum[i][j] - prefixsum[i - len][j] - prefixsum[i][j - len] + prefixsum[i - len][j - len] <= threshold) {
res = len;
len++;
}
}
}
return res;
}
}
Maximum Size Subarray Sum Equals k 思路:prefixsum,这里可以不用array存,直接sum加起来,用hashmap就变成了<Integer, Integer> sum, index 只要查sum - target 是否存在就可以,存在就是当前index - i,0...i ...j 长度就是j - i; O(N)
class Solution {
public int maxSubArrayLen(int[] nums, int k) {
if(nums == null || nums.length == 0) {
return 0;
}
int res = 0;
int cursum = 0;
// cursum 0 based index
HashMap<Integer, Integer> hashmap = new HashMap<>();
hashmap.put(0, -1); // 是为了array刚好 0...j 元素和是target,那么长度是j - (-1) = j + 1用的;
for(int i = 0; i < nums.length; i++) {
cursum += nums[i];
if(hashmap.containsKey(cursum - k)) {
res = Math.max(res, i - hashmap.get(cursum - k));
}
// 这行很关键,就是之前有,就不要存,因为是找最大的length,所以前面的不要覆盖掉;
if(!hashmap.containsKey(cursum)) {
hashmap.put(cursum, i);
}
}
return res;
}
}
Matrix Block Sum 思路:这题其实考点就是个prefixSum,Sum[i..j] = PrefixSum[j] - PrefixSum[i-1],注意这里是i - 1;
// 这个点是i - k + 1的上面一个点,根据prefixSum 定义
// Sum[i, j] = PrefixSum[j] - PrefixSum[i-1];
// i - 1,也就是这个点;所以这里r1, c1没有+1;
r1 c1
r2 c2
r1, c1 的点,其实是区域范围 (i - k + 1, j - k + 1)的上一个点;
class Solution {
public int[][] matrixBlockSum(int[][] mat, int k) {
if(mat == null || mat.length == 0 || mat[0].length == 0) {
return mat;
}
int m = mat.length;
int n = mat[0].length;
int[][] prefixSum = new int[m + 1][n + 1];
for(int i = 0; i <= m; i++) {
for(int j = 0; j <= n; j++) {
if(i == 0 || j == 0) {
prefixSum[i][j] = 0;
} else {
prefixSum[i][j] = prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1]
+ mat[i - 1][j - 1];
}
}
}
int[][] res = new int[m][n];
for(int i = 0; i < m; i++) {
for(int j = 0; j < n; j++) {
int r1 = Math.max(0, i - k);
int c1 = Math.max(0, j - k);
int r2 = Math.min(m, i + k + 1);
int c2 = Math.min(n, j + k + 1);
res[i][j] = prefixSum[r2][c2] - prefixSum[r1][c2] - prefixSum[r2][c1] + prefixSum[r1][c1];
}
}
return res;
}
}
Maximum Sum of 3 Non-Overlapping Subarrays 思路:这个题目还是比较巧妙的,要求三段的和最大,核心算法就是:固定中间的一段subarry,分别用O(1)的时间去求左边部分的最大的subarry和,右边部分的最大的subarry的和。怎么用O(1)的时间去求subarry,很简单,prefixsum。但是怎么O(1)的去求一个大区间里面,哪一段的subarry是最大的,这个点就要用dp来pre compute,用leftdp记录到目前为止,从左往右看,到第i为止的最大的k size的subarry的起点index,同理:rightdp记录从右往左看 k size最大和的起点;这样有了这两个array,我就可以一口气算出
[0, i] [i, i + k -1] [i + k, n - 1] 三个区间段里面,哪三段的和最大,左边和右边可以O(1)算出来,中间的i在动,但是也可以O(1)计算出来;
prefixsum index的推倒,要熟练;
sum[i, j] = prefixsum[j] - prefixsum[i - 1] 由于 array +1了,所以 prefixsum[j + 1] - prefixsum[i]
i 跟j之间有k,那么 j = i + k - 1 <==> i = j - k + 1;
从左往右计算,是j在移动,所以消除 i,prefixsum[j + 1] - prefixsum[j - k + 1];
从右往左计算,是i在移动,所以消除 j,prefixsum[i + k - 1 + 1] - prefixsum[i] = prefixsum[i + k] - prefixsum[i];
class Solution {
public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
int n = nums.length;
int[] presum = new int[n + 1];
for(int i = 0; i < n; i++) {
presum[i + 1] = presum[i] + nums[i];
}
// 记录到目前i为止的,从左往右看,最大的subarry的和的起始位子;
int[] leftdp = new int[n];
// 记录到目前i为止的,从右往左看,最大的subarry的和的起始位子;
int[] rightdp = new int[n];
leftdp[k] = 0;
for(int j = k, total = presum[k] - presum[0]; j < n; j++) {
if(presum[j + 1] - presum[j - k + 1] > total) {
total = presum[j + 1] - presum[j - k + 1];
leftdp[j] = j - k + 1;
} else {
leftdp[j] = leftdp[j - 1];
}
}
rightdp[n - k] = n - k;
for(int i = n - k - 1, total = presum[n] - presum[n - k]; i >= 0; i--) {
if(presum[i + k] - presum[i] >= total) {
total = presum[i + k] - presum[i];
rightdp[i] = i;
} else {
rightdp[i] = rightdp[i + 1];
}
}
// left, middle, right;
// [0, i], [i, i + k - 1], [i + k, n - 1];
int[] res = {-1,-1,-1};
int maxsum = 0;
// 左边最起码要有k个,所以start = k,右边也是要有k个,那么起点就是倒数第二个k个,也就是 n - 2*k;
// 核心算法:就是固定中间的subarry,然后一口气算出各个subarry的和,怎么算,
// 也就是pre calculate的leftdp, rightdp,
// 然后用leftdp 和rightdp O(1)去算各个subarry的和;
for(int i = k; i <= n - 2 * k; i++) {
int l = leftdp[i - 1];
int r = rightdp[i + k];
int cursum = (presum[l + k] - presum[l])
+ (presum[i + k] - presum[i])
+ (presum[r + k] - presum[r]);
if(cursum > maxsum) {
maxsum = cursum;
res[0] = l;
res[1] = i;
res[2] = r;
}
}
return res;
}
}