leetcode题解-363. Max Sum of Rectangle No Larger Than K

来源:互联网 发布:mac mini 固态 编辑:程序博客网 时间:2024/06/07 23:05

题目:

Given a non-empty 2D matrix matrix and an integer k, find the max sum of a rectangle in the matrix such that its sum is no larger than k.Example:Given matrix = [  [1,  0, 1],  [0, -2, 3]]k = 2The answer is 2. Because the sum of rectangle [[0, 1], [-2, 3]] is 2 and 2 is the max number no larger than k (k = 2).Note:The rectangle inside the matrix must have an area > 0.What if the number of rows is much larger than the number of columns?

这是binary-search里面的hard难度的题目,确实很难,只想到了最暴力的解法,那就是对矩阵中的每个元素遍历其所有可能存在的矩阵,然后求出矩阵的和,然后取所有小于k的最大的和返回。时间复杂度是O(n^4)。思路就是先遍历矩阵将每个元素到左上角的矩阵的和保存在矩阵中,然后遍历每个元素,对每个元素再遍历所有可能存在的矩阵,对每个矩阵都求其和即可。代码入下:

    public int maxSumSubmatrix(int[][] matrix, int k) {        if (matrix == null || matrix.length == 0 || matrix[0].length == 0)            return 0;        int rows = matrix.length, cols = matrix[0].length;        int[][] areas = new int[rows][cols];        //计算每个元素到左上角元素的和,方便后面使用        for (int r = 0; r < rows; r++) {            for (int c = 0; c < cols; c++) {                int area = matrix[r][c];                if (r-1 >= 0)                    area += areas[r-1][c];                if (c-1 >= 0)                    area += areas[r][c-1];                if (r-1 >= 0 && c-1 >= 0)                    area -= areas[r-1][c-1];                areas[r][c] = area;            }        }        int max = Integer.MIN_VALUE;        for (int r1 = 0; r1 < rows; r1++) {            for (int c1 = 0; c1 < cols; c1++) {                //遍历数组中的每一个元素,然后下面两个循环是得到其所有可能的矩阵                for (int r2 = r1; r2 < rows; r2++) {                    for (int c2 = c1; c2 < cols; c2++) {                        //计算当前矩阵的和                        int area = areas[r2][c2];                        if (r1-1 >= 0)                            area -= areas[r1-1][c2];                        if (c1-1 >= 0)                            area -= areas[r2][c1-1];                        if (r1-1 >= 0 && c1 -1 >= 0)                            area += areas[r1-1][c1-1];                        if (area <= k)                            max = Math.max(max, area);                    }                }            }        }        return max;    }

自然,这种方法的效率也很低,击败了30%多的用户。然后看别人的方法发现一般采用按列求和的方式,并使用两个数组来保存矩阵的和。先看代码再结合代码进行解释(这里默认行数小于列数)。

    public int maxSumSubmatrix2(int[][] matrix, int k) {        int m = matrix.length, n = matrix[0].length, ans = Integer.MIN_VALUE;        long[] sum = new long[m+1]; // stores sum of rect[0..p][i..j]        //注意这里的三个循环,第一层是矩阵的左边,也就是起始位置,第二层是矩阵的右边,也就是结束为止,对每个起始位置,都遍历所有可能的结束位置,得到不同的矩阵。然后第三层是遍历每一行,也就是前面两层确定了矩阵的边界,但是还可能包含不同行的情况。        for (int i = 0; i < n; ++i) {            long[] sumInRow = new long[m];            for (int j = i; j < n; ++j) { // for each rect[*][i..j]                for (int p = 0; p < m; ++p) {                    //对每个小矩阵求和。sunInRow是对每一行的所有元素求和,sum是对行之间求和,也就是矩阵的和。                    sumInRow[p] += matrix[p][j];                    sum[p+1] = sum[p] + sumInRow[p];                }                //对sum数组进行归并搜索,找到小于k的值。                ans = Math.max(ans, mergeSort(sum, 0, m+1, k));                if (ans == k) return k;            }        }        return ans;    }    static int mergeSort(long[] sum, int start, int end, int k) {        if (end == start + 1) return Integer.MIN_VALUE; // need at least 2 to proceed        int mid = start + (end - start) / 2, cnt = 0;        int ans = mergeSort(sum, start, mid, k);        if (ans == k) return k;        ans = Math.max(ans, mergeSort(sum, mid, end, k));        if (ans == k) return k;        long[] cache = new long[end - start];        for (int i = start, j = mid, p = mid; i < mid; ++i) {            while (j < end && sum[j] - sum[i] <= k) ++j;            if (j - 1 >= mid) {                ans = Math.max(ans, (int) (sum[j - 1] - sum[i]));                if (ans == k) return k;            }            while (p < end && sum[p] < sum[i]) cache[cnt++] = sum[p++];            cache[cnt++] = sum[i];        }        System.arraycopy(cache, 0, sum, start, cnt);        return ans;    }

上面这种方法通过三层搜索加一层归并将时间复杂度降低到了O(N^2Mlog(M))。可以击败90%以上的用户。原因在于第一种方法遍历一个矩阵需要四层迭代,而这种方法简化为三层。可以画图结合这理解一下函数运行的过程。

此外,还有一种居中的方法,思路与这种方法类似,不过使用Tree结构来保存内层循环的值并进行二叉搜索得到小于k的最大值,可以击败60%的用户。代码如下所示,这种方法考虑了行数大于列数的情况。

    public int maxSumSubmatrix1(int[][] matrix, int target) {        int row = matrix.length;        if(row==0)return 0;        int col = matrix[0].length;        int m = Math.min(row,col);        int n = Math.max(row,col);        //indicating sum up in every row or every column        boolean colIsBig = col>row;        int res = Integer.MIN_VALUE;        for(int i = 0;i<m;i++){            int[] array = new int[n];            // sum from row j to row i            for(int j = i;j>=0;j--){                int val = 0;                TreeSet<Integer> set = new TreeSet<>();                set.add(0);                //traverse every column/row and sum up                for(int k = 0;k<n;k++){                    array[k]=array[k]+(colIsBig?matrix[j][k]:matrix[k][j]);                    val = val + array[k];                    //use  TreeMap to binary search previous sum to get possible result                    Integer subres = set.ceiling(val-target);                    if(null!=subres){                        res=Math.max(res,val-subres);                    }                    set.add(val);                }            }        }        return res;    }