前缀和的两道leetcode

文章介绍了如何实现二维矩阵的子矩阵元素求和功能,通过构建二维前缀和数组,将求和操作优化为O(1)时间复杂度。同时,文章提出了寻找整数数组中优美子数组(含有k个奇数的连续子数组)的算法,首先将数组元素对2取模,然后利用前缀和和哈希表优化,降低时间复杂度至O(n)。
摘要由CSDN通过智能技术生成

题目描述:

给定一个二维矩阵 matrix,以下类型的多个请求:

计算其子矩形范围内元素的总和,该子矩阵的 左上角 为 (row1, col1) ,右下角 为 (row2, col2) 。
实现 NumMatrix 类:

NumMatrix(int[][] matrix) 给定整数矩阵 matrix 进行初始化
int sumRegion(int row1, int col1, int row2, int col2) 返回 左上角 (row1, col1) 、右下角 (row2, col2) 所描述的子矩阵的元素 总和 。

示例:

输入: 
["NumMatrix","sumRegion","sumRegion","sumRegion"]
[[[[3,0,1,4,2],[5,6,3,2,1],[1,2,0,1,5],[4,1,0,1,7],[1,0,3,0,5]]],[2,1,4,3],[1,1,2,2],[1,2,2,4]]
输出: 
[null, 8, 11, 12]
算法思想:

在数组matrix中,构建二维前缀和数组sum,从而将对matrix上(row2,col2)到(row1,col1)之间元素的和转化为O(1)时间的求和。

二维前缀和构造公式:

S [ i ] [ j ] = ∑ x = 1 i ∑ y = 1 j A [ x ] [ y ] = S [ i − 1 ] [ j ] + S [ i ] [ j − 1 ] − S [ i − 1 ] [ j − 1 ] + A [ i ] [ j ] S[i][j] = \sum_{x = 1}^{i} \sum_{y=1}^{j}A[x][y] = S[i-1][j] + S[i][j-1] -S[i-1][j-1] + A[i][j] S[i][j]=x=1iy=1jA[x][y]=S[i1][j]+S[i][j1]S[i1][j1]+A[i][j]

二维前缀和计算公式:

s u m ( p , q , i , j ) = ∑ x = p i ∑ y = q j A [ x ] [ y ] = S [ i ] [ j ] − S [ i ] [ q − 1 ] − S [ p − 1 ] [ j ] + S [ p − 1 ] [ q − 1 ] sum(p,q,i,j) = \sum_{x = p}^{i} \sum_{y=q}^{j} A[x][y] = S[i][j] - S[i][q-1] - S[p-1][j] + S[p-1][q-1] sum(p,q,i,j)=x=piy=qjA[x][y]=S[i][j]S[i][q1]S[p1][j]+S[p1][q1]

c++代码实现:
class NumMatrix {
public:

    NumMatrix(vector<vector<int>>& matrix) {
        int n = matrix.size();
        int m = matrix[0].size();
        // 将sum二维矩阵resize成 n+1 行 m+1 列且初始化为 0 的写法:
        sum.resize(n + 1,vector<int>(m+1,0));
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
            // 构建 二维前缀和矩阵, 由于matrix数组是从0开始的,所有构建前缀和数组时要将下标都-1挪一位
                sum[i][j] = sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1] + matrix[i-1][j-1];
    }
    
    int sumRegion(int row1, int col1, int row2, int col2) {
        row1 ++, col1++, row2++, col2++;
        // 返回二维前缀和数组 (row2,col2)到(row1,col1)之间的元素和
        return sum[row2][col2] - sum[row2][col1 -1] - sum[row1-1][col2] + sum[row1-1][col1-1];
    }
private:
    vector<vector<int>> sum;
};

题目描述:

给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。

请返回这个数组中 「优美子数组」 的数目。

示例:

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。

算法思路:

1、将数组nums中每个元素对2取模,则数组中奇数元素变为1,偶数元素变为0,原问题连续子数组中有k个奇数的问题就转化为求和为k的连续子数组的个数。对一段连续数组求和可以使用 前缀和 来简化问题。
2、对数组nums对2取余后求其前缀和数组后,问题转化为求 s [ r ] − s [ l − 1 ] = = k s[r] - s[l-1] == k s[r]s[l1]==k的连续子段个数,可以分别遍历i,j求满足上式的个数。

代码实现:
class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        int ans = 0;
        vector<int> a(nums.size()+1);
        vector<int> s(nums.size()+1);
        for(int i = nums.size(); i >= 1; i--)    a[i] = nums[i-1] % 2;
        s[0] = 0;
        for(int i = 1; i <= nums.size(); i++)    s[i] = s[i-1] + a[i];
        
        for(int i = 1; i <= nums.size(); i++){
            for(int j = 1; j <= i; j++)
                if(s[i] - s[j-1] == k)
                    ans ++;
        }
        return ans;
    }
};

这种方法时间复杂度是O(n^2)会超时,所以考虑优化。
3、固定 r,观察l的变化:

1、r的取值范围是1~n 在前缀和数组上从1开始到n,l的取值范围是 1 ~ r;
2、由于求的是s[r] - s[l-1] == k的值,所以考虑 换元令i=r, j = l-1,可知,i的范围是 1~n, j 的范围是 0 ~ i-1;
3、问题转变为 求s[i] - s[j] == k的个数,即满足s[j] == s[i] - k 的 j的个数,其中 i 的取值范围是1~n; j的取值范围是 0~i-1;
4、问题变为求s[] 数组中等于 s[i]- k 的值的个数,可以使用hash表统计s[]数组各个值的个数,即可求出s数组中等于 s[i] -k的个数。 

C++代码实现:

class Solution {
public:
    int numberOfSubarrays(vector<int>& nums, int k) {
        int n = nums.size();
        vector<int>sum(n+1,0);
        // 构造nums数组对2取模后的前缀和数组sum,细节 nums[i-1] 
        for(int i = 1; i <= n; i++) sum[i] = sum[i-1] + nums[i-1] % 2;

        // cout << " 打印sum数组:"  << endl;
        // for(int i = 1; i <= n; i++) cout << sum[i] << " ";
        int ans = 0;
        // 使用hash数组统计sum数组各个元素值的个数。
        unordered_map<int,int> hash;
        // 由于数组中s[i] - k = 0的值,所以需要记录sum数组中元素0的个数。
        for(int i = 0; i <= n; i++){
            // j 范围是 0 ~ i-1, 所以得满足sum[i] - k >= 0; 
            // 求sum数组中等于 sum[i] - k 的值的个数。
            if(sum[i] - k >= 0) ans += hash[sum[i] - k];
            hash[sum[i]] ++;
        }

        return ans;    
    }

};

时间复杂度降为O(n),空间复杂度:O(n)。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值