LeetCode第 363 题:矩形区域不超过 K 的最大数值和 (C++)

363. 矩形区域不超过 K 的最大数值和 - 力扣(LeetCode)

第一感觉是和LeetCode第 862 题:和至少为K的最短子数组 (C++)_qq_32523711的博客-CSDN博客比较类似,而且矩阵元素和子数组都有连续的特点,所以这题可以先计算前缀和试一试。

暴力解

不过还是先看看暴力解吧,要注意题目提示的行数远大于列数,方法很简单,固定左右(列)边界,这样就得到了一个贯穿头尾的(窄)矩形,计算该矩形内每一行的和存储在一个vector内,最后在vector中遍历(记住矩阵内的元素可以为负),不断记录并更新符合要求的最大数值和就行,这样就计算完了一个窄矩阵(一对左右边界)。

下面更新左右边界即可。

记住help函数的vector参数最好用引用,可以大量节省空间。

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int row = matrix.size();
        int col = matrix[0].size();
        int result = INT_MIN;   //最小的int值
        // 行数远大于列数
        // left, right为左右边界
        for(int left=0; left < col; ++left){
            vector<int> sum(row, 0); //用于存储该矩形的行的和
            for(int right=left; right < col; ++right){
                for(int i=0; i < row; ++i)
                    sum[i] += matrix[i][right];
                result = max(result, helper(row, sum, k));
            }
        }
         return result;
    }
    // 用于计算数值和
    int helper(const int row, const vector<int> &vec, int k){
        int max_sum = INT_MIN;
        for(int i=0; i < row; ++i){
            int sum = 0;
            for(int j = i; j < row; ++j){
                sum += vec[j];
                if(sum <= k && sum > max_sum)    max_sum =  sum;
            }
        }
        return max_sum;
    }
};

好像暴力解结果看着也还行:

在这里插入图片描述

优化

因为矩阵中可能有负数的存在,所以help函数中当没有找到最大值的时候,是有必要一直遍历到vector结尾的,但是如果提前找到的数值和sum == k的话,就可以立即返回了,因为可以确定已经是最大了。

对主函数中的result也是如此。

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int row = matrix.size();
        int col = matrix[0].size();
        int result = INT_MIN;   //最小的int值
        // 行数远大于列数
        // left, right为左右边界
        for(int left=0; left < col; ++left){
            vector<int> sum(row, 0); //用于存储该矩形的行的和
            for(int right=left; right < col; ++right){
                for(int i=0; i < row; ++i)
                    sum[i] += matrix[i][right];
                result = max(result, helper(row, sum, k));
                if(result == k) return result;
            }
        }
         return result;
    }
    // 用于计算数值和
    int helper(const int row, const vector<int> &vec, int k){
        int max_sum = INT_MIN;
        for(int i=0; i < row; ++i){
            int sum = 0;
            for(int j = i; j < row; ++j){
                sum += vec[j];
                if(sum == k)    return sum;
                if(sum < k && sum > max_sum)    max_sum =  sum;
            }
        }
        return max_sum;
    }
};

不过结果来看这点小优化掀不起什么波澜。

接着优化:

我们的目的是,在众多矩形块中不断地累加他们的值,使得和值越接近k越好,越快越好!

help函数中,当sum累加的时候:

如果时刻t有:sum < 0sum为当前已累加的值,那么接着累加下去必然会使得后面那部分更小:可以理解为当前小于0的sum只会拖累我们继续向目标(更快地接近k)前进,那就不如舍弃k,然后在接下来的位置从`0``开始累加。

注意k可以小于0.。。

把k想象成山顶,sum < 0就相当于爬山的时候掉到了悬崖底下。

参考这个题解
暴力+O(n)优化+二分+二分剪枝 - 矩形区域不超过 K 的最大数值和

不过这个思路总感觉怪怪的,加的那一段是否有效和测试数据关系很大。

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int row = matrix.size();
        int col = matrix[0].size();
        int result = INT_MIN;   //最小的int值
        // 行数远大于列数
        // left, right为左右边界
        for(int left=0; left < col; ++left){
            vector<int> sum(row, 0); //用于存储该矩形的行的和
            for(int right=left; right < col; ++right){
                for(int i=0; i < row; ++i)
                    sum[i] += matrix[i][right];
                result = max(result, helper(row, sum, k));
                if(result == k) return result;
            }
        }
         return result;
    }
    // 用于计算数值和
    int helper(const int row, const vector<int> &vec, int k){
        int helper_sum=vec[0], max_sum=helper_sum;
        for (int i=1; i<row; ++i)
        {
            if (helper_sum<=0)
                helper_sum = vec[i];
            else
                helper_sum += vec[i];
            if (helper_sum > max_sum)
                max_sum = helper_sum;
        }
        if (max_sum <= k)
            return max_sum;

        max_sum = INT_MIN;
        for(int i=0; i < row; ++i){
            int sum = 0;
            for(int j = i; j < row; ++j){
                sum += vec[j];
                if(sum == k)    return sum;
                if(sum < k && sum > max_sum)    max_sum =  sum;
            }
        }
        return max_sum;
    }
};

但是效果挺好的:
在这里插入图片描述

还要再接着优化的话,其实在代码中左右边界移动的过程中存在的大量的重复运算,理论上左边界为0的时候,将右边界一直右移到col末尾,这样就已经将整个矩阵遍历了一遍,如果加以记录(典型的空间换时间)的话,后续任意的左右边界之间的矩形产生的vector都是已知的,而不用移动左边界的时候再进行计算,举个例子:

假设矩阵为4列,n行,那么左右边界的所有组合有:

  • left = 0: (0,0) (0,1) (0,2) (0,3)
  • left = 1: (1,1) (1,2) (1,3)
  • left = 2: (2,2) (2,3)
  • left = 3: (3,3)

刚好对应着left和right的两层循环,每次循环都对矩阵元素的值进行了累加,但是只有第一次(黄色那一行)是必要的,因为剩下的都可以利用第一行在很少的计算下得到:

(0,0)代表第1行的和,(0,1)代表1,2行的和,(0, 2)代表1,2,3行的和…
(1,1)代表第2行的和,可以由 (0,1) - (0,0)得到,同理:
(1,2)= (0,2) - (0, 0)
(1,3)= (0,3) - (0, 0)

所以第一次遍历的时候,所有的和值其实就已经得到了,如果存储第一次遍历的结果(增加空间),就可以省去后面很多的计算(减少时间)。

其实这就是前缀和的思路嘛!!!

现在就来思考怎么进行存储:

  • 首先可以用一个vector<vector<int>> 来保存第一行的col(列数)个vector<int> ,利用第一行就可以计算出第二,三,四行。
class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int row = matrix.size();
        int col = matrix[0].size();
        int result = INT_MIN;   //最小的int值
        
        vector<vector<int>> sum_v;
        vector<int> sum(row, 0); //用于存储矩形的行的和,大小为行数
        // 此时左边界0,右边界从0到col走了一遍(寻找k的任务也过了一遍)
        for(int idx=0; idx < col; ++idx){
                for(int i=0; i < row; ++i)
                    sum[i] += matrix[i][idx];
                //此时就可以顺便进行判别,如果找到就节省很多时间,找不到也无所谓。
                result = max(result, helper(row, sum, k));              
                if(result == k) return result;
                sum_v.push_back(sum);
        }

        for(int i = 0; i < sum_v.size()-1; ++i){
            for(int j = i+1; j < sum_v.size(); ++j){
                for(int p = 0; p != row; ++p){
                    // 重复利用sum,节省空间
                    sum[p] = sum_v[j][p] - sum_v[i][p];
                }
                result = max(result, helper(row, sum, k));              
                if(result == k) return result;
            }
        }
        return result;
    }
    // 用于计算数值和
    int helper(const int row, const vector<int> &vec, int k){
        int helper_sum=vec[0], max_sum=helper_sum;
        for (int i=1; i<row; ++i)
        {
            if (helper_sum<=0)
                helper_sum = vec[i];
            else
                helper_sum += vec[i];
            if (helper_sum > max_sum)
                max_sum = helper_sum;
        }
        if (max_sum <= k)
            return max_sum;

        max_sum = INT_MIN;
        for(int i=0; i < row; ++i){
            int sum = 0;
            for(int j = i; j < row; ++j){
                sum += vec[j];
                if(sum == k)    return sum;
                if(sum < k && sum > max_sum)    max_sum =  sum;
            }
        }
        return max_sum;
    }
};

不过代码倒是看起来越来越乱了,非要再优化下去也还是有空间的,利用前缀和相减生成vector, 然后又将vector传入help函数进行遍历的这一步,其实没必要,中间生成的vector就仅仅起到传递的作用,那我将help函数的代码直接挪上来不就省了这一步了嘛?

不过这样会让代码更多更杂。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值