Prefix Sum 总结

注意: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;
    }
}

Subarray Sum II

思路:固定右边界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;
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值