【算法/前缀和/二维前缀和】题解+详细备注(共8题)

304.二维区域和检索-矩阵不可变

class NumMatrix {
public:
    vector<vector<int>> pre;
    vector<vector<int>> nums;
    NumMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        pre.resize(m+1,vector<int>(n+1));

        for(int i{};i<m;++i){
            for(int j{};j<n;++j){
            	// 前缀和的面积等于左侧面积+上侧面积-左上角面积 + 该点数值
                pre[i+1][j+1] = pre[i][j+1] + pre[i+1][j] - pre[i][j] + matrix[i][j];
            }
        }

        nums = matrix;
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
    	// 已知整体面积,上面面积,左边面积,左上面积,求右下角矩形的面积。 右下角矩形的面积=整体面积-上面面积-左边面积+左上面积
        return pre[row2+1][col2+1] - pre[row1][col2+1] - pre[row2+1][col1] + pre[row1][col1] ;
    }
};

1314.矩阵区域和

class Solution {
public:
	// 304. 二维区域和检索 - 矩阵不可变的变形;只是需要处理子矩阵的边界条件
    vector<vector<int>> nums;
    vector<vector<int>> pre;

    int get(int m, int n, int x, int y) {
        x = max(min(x, m), 0);
        y = max(min(y, n), 0);
        return pre[x][y];
    }

    vector<vector<int>> matrixBlockSum(vector<vector<int>>& mat, int k) {
        nums = mat;

        int m = mat.size();
        int n = mat[0].size();
        pre.resize(m+1,vector<int>(n+1));

        for(int i{};i<m;++i){
            for(int j{};j<n;++j){
                pre[i+1][j+1] = pre[i][j+1] + pre[i+1][j] - pre[i][j] + mat[i][j];
            }
        }

        vector<vector<int>> result(m,vector<int>(n));

        for(int i{};i<m;++i){
            for(int j{};j<n;++j){
                result[i][j] =get(m, n, i + k + 1, j + k + 1)  - get(m, n, i - k, j + k + 1) - get(m, n, i + k + 1, j - k) + get(m, n, i - k, j - k);
            }
        }

        return result;
    }
};

1074.元素和为目标值的子矩阵数量

class Solution {
public:
	// 这里是560.和为K的子数组的源代码
    // [j..i]的子数组和:pre[i+1] - pre[j]
	// pre[i+1]-pre[j] == k
	// 关键点是想到:考虑以 i 结尾的和为 k 的连续子数组个数时只要统计有多少个前缀和为pre[i+1]−k 的 pre[j] 即可
    int subarraySum(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        mp[0] = 1;
        int count = 0;
		int n = nums.size();
		vector<int> pre(n+1);
		for(int i{};i<n;++i){
			pre[i+1] = pre[i] + nums[i]; 
			if (mp.find(pre[i+1]- k) != mp.end()) {
                count += mp[pre[i+1]- k];
            }
			mp[pre[i+1]]++;
		}	

        return count;
    }
	
	// 首先掌握560.和为K的子数组
    // 然后我们枚举子矩阵的上下边界,并计算出该边界内每列的元素和,则原问题转换成了如下一维问题: 给定一个整数数组和一个整数 target,计算该数组中子数组和等于 target 的子数组个数,即题560.和为K的子数组的解
    int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();

        int result{};
        for(int i{};i<m;++i){ 
        	// 这里的一维数组其实就是一个子矩阵
        	// 例如矩阵 123
        	//      	456
        	//      	789
        	// sums 5,7,9 起始就相当于前两行的子矩阵 
            vector<int> sums(n);
            for(int j = i;j<m;++j){
                for(int c{};c<n;++c){
                    sums[c] += matrix[j][c]; // 按照列计算前缀和
                }
                // 根据列的前缀和构建的一维数组、计算子矩阵的和是否满足条件
                result += subarraySum(sums, target); 
            }
        }

        return result;
    }
};

363.矩形区域不超过K的最大数值和

class Solution {
public:
	// 先掌握1074.元素和为目标值的子矩阵数量
    // 关键是想到pre[i+1] - pre[j] <= k
    // 即求:满足pre[j] >= pre[i+1] -k
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int m = matrix.size();
        int n = matrix[0].size();

        int result{ INT_MIN};
        for(int i{};i<m;++i){ 
        	// 这里的一维数组其实就是一个子矩阵
        	// 例如矩阵 123
        	//      	456
        	//      	789
        	// sums 5,7,9 起始就相当于前两行的子矩阵 
            vector<int> sums(n);
            for(int j = i;j<m;++j){
                for(int c{};c<n;++c){
                    sums[c] += matrix[j][c]; // 按照列计算前缀和
                }
                // 根据列的前缀和构建的一维数组、计算子矩阵的和是否满足条件
                set<int> sumSet{0};
                // 等同于vector<int> pre();这里为了节省内存,只使用一个int即可
                int pre{};
                for(int &num : sums){
                	pre += num;
                	// lower_bound用于在指定区域内查找不小于目标值的第一个元素。即求<=X的最大值时使用
                    // 求pre[j] >= pre[i+1] -k
                	auto lb = sumSet.lower_bound(pre-k);
                	if(lb != sumSet.end()){
                        // 将pre[i+1]与result比较,取较大值
                		result = max(result,pre-*lb);
                	}
                	sumSet.insert(pre);
                }	
            }
        }

        return result;
    }
};

面试题17.24.最大子矩阵

class Solution {
public:
    vector<int> getMaxMatrix(vector<vector<int>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();

        int result{ INT_MIN};
        vector<int> resultV;
        for(int top{};top<m;++top){ 
        	// 这里的一维数组其实就是一个子矩阵
        	// 例如矩阵 123
        	//      	456
        	//      	789
        	// sums 5,7,9 起始就相当于前两行的子矩阵 
            vector<int> sums(n);
            for(int bottom = top;bottom<m;++bottom){
                for(int c{};c<n;++c){
                    sums[c] += matrix[bottom][c]; // 按照列计算前缀和(top行-bottom行,c列的和)
                }

                int left{};
                int tempValue{};// 记录子矩阵的和
                for(int right{};right < n;++right){
                    tempValue += sums[right];
                    if(tempValue > result){
                        resultV = {top,left,bottom,right};
                        result = tempValue;
                    }

                    if(tempValue < 0){
                        left = right +1;
                        // 很关键,当子矩阵的和小于0时,要置0
                        // 能够这么舍弃的原因是题意包含0,左边界重置后
                        tempValue = 0;
                    }
                }
            }
        }

        return resultV;
    }
};

1292.元素和小于等于阈值的正方形的最大边长

class Solution {
public:
	// 先掌握1314. 矩阵区域和
    vector<vector<int>> nums;
    vector<vector<int>> pre;
    int maxSideLength(vector<vector<int>>& mat, int threshold) {
        nums = mat;

        int m = mat.size();
        int n = mat[0].size();
        pre.resize(m+1,vector<int>(n+1));

        for(int i{};i<m;++i){
            for(int j{};j<n;++j){
                pre[i+1][j+1] = pre[i][j+1] + pre[i+1][j] - pre[i][j] + mat[i][j];
            }
        }

        int result{};

        for(int i{};i<m;++i){
            for(int j{};j<n;++j){
            	// 唯一与1314. 矩阵区域和不同的地方
            	// 直接枚举边长即可
                for (int k=0;(k+i<m) && (k+j<n);k++){
                	// [i,j]为起点,边长为k的矩阵的面积
                    long long sum=pre[i+k+1][j+k+1]-pre[i+k+1][j]-pre[i][j+k+1]+pre[i][j];
                    if (sum<=threshold) {
                        result=max(result,k+1);
                    }
                }
            }
        }

        return result;
    }
};

1738.找出第K大的异或坐标值

class Solution {
public:
	// 二维前缀和+排序
	// 设二维前缀和 pre(i,j) 表示矩阵matrix 中所有满足0≤x<i 且0≤y<j 的元素执行按位异或运算的结果。即:pre(i,j)=pre(i−1,j)⊕pre(i,j−1)⊕pre(i−1,j−1)⊕matrix(i,j)

    int kthLargestValue(vector<vector<int>>& matrix, int k) {
        int m = matrix.size(), n = matrix[0].size();
        vector<vector<int>> pre(m + 1, vector<int>(n + 1));
        vector<int> results;
        for (int i = 1; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                pre[i][j] = pre[i - 1][j] ^ pre[i][j - 1] ^ pre[i - 1][j - 1] ^ matrix[i - 1][j - 1];
                results.push_back(pre[i][j]);
            }
        }

        sort(results.begin(), results.end(), greater<int>());
        return results[k - 1];
    }
};

2245.转角路径的乘积中最多能有几个尾随零

int c25[1001][2];
int init = []() {
    for (int i = 2; i <= 1000; ++i) { // 预处理:递推算出每个数的因子 2 的个数和因子 5 的个数
        if (i % 2 == 0) c25[i][0] = c25[i / 2][0] + 1;
        if (i % 5 == 0) c25[i][1] = c25[i / 5][1] + 1;
    }
    return 0;
}();

class Solution {
public:
	// 关键是想到:尾零的个数就是路径上的数的因子2的个数和,与因子5的个数之和的较小值
    int maxTrailingZeros(vector<vector<int>> &grid) {
        int m = grid.size(), n = grid[0].size(), ans = 0;
        // m,n分别是行列、第三维是因子2与因子5的个数和
        int s[m][n + 1][2];
        for (int i = 0; i < m; ++i) {
            s[i][0][0] = s[i][0][1] = 0;
            for (int j = 0; j < n; ++j) {
                s[i][j + 1][0] = s[i][j][0] + c25[grid[i][j]][0]; // 每行的因子 2 的前缀和
                s[i][j + 1][1] = s[i][j][1] + c25[grid[i][j]][1]; // 每行的因子 5 的前缀和
            }
        }

        for (int j = 0; j < n; ++j) {
            for (int i = 0, s2 = 0, s5 = 0; i < m; ++i) { // 从上往下,枚举左拐还是右拐
                s2 += c25[grid[i][j]][0];
                s5 += c25[grid[i][j]][1];
                // 左拐:min(s2 + s[i][j][0], s5 + s[i][j][1])
                // 右拐:min(s2 + s[i][n][0] - s[i][j + 1][0], s5 + s[i][n][1] - s[i][j + 1][1])
                ans = max(ans, max(min(s2 + s[i][j][0], s5 + s[i][j][1]),
                                   min(s2 + s[i][n][0] - s[i][j + 1][0], s5 + s[i][n][1] - s[i][j + 1][1])));
            }
            for (int i = m - 1, s2 = 0, s5 = 0; i >= 0; --i) { // 从下往上,枚举左拐还是右拐
                s2 += c25[grid[i][j]][0];
                s5 += c25[grid[i][j]][1];
                ans = max(ans, max(min(s2 + s[i][j][0], s5 + s[i][j][1]),
                                   min(s2 + s[i][n][0] - s[i][j + 1][0], s5 + s[i][n][1] - s[i][j + 1][1])));
            }
        }
        return ans;
    }
};
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一二三o-0-O

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值