异或差分序列_[力扣303,304] 前缀和&差分

257690889bae50bd76982c505c5b1b25.png

题目链接

303. 区域和检索 - 数组不可变

题目描述

给定一个整数数组 nums,求出数组从索引 i j (ij) 范围内元素的总和,包含 i, j 两点。

1. 你可以假设数组不可变。
2. 会多次调用 sumRange 方法。

样例

给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3

算法: 前缀和

给定长度 n 的序列

, 给每个前缀求一次和,
。这些前缀和维护在一个长度 n + 1 数组 S 里。

之后 S 数组可以支持 O(1) 求区间 [i, j] 的和,答案是 S[j+1] - S[i], 其中 0 <= i <= j <= n - 1。

建 S 数组的过程需要额外 O(n) 空间,和总共 O(n), 摊销 O(1) 的时间。

题目 304. 二维区域和检索 - 矩阵不可变 是前缀和二维的版本,建好 S 数组之后求两个点之间的矩形区域和时需要借助容斥原理,稍微有点麻烦。

代码(C++)

一维版本

class NumArray {
public:
    NumArray(vector<int>& nums) {
        if(nums.empty())
            sums = vector<int>();
        else
        {
            int n = nums.size();
            sums = vector<int>(n + 1, 0);
            for(int i = 1; i <= n; ++i)
                sums[i] = sums[i - 1] + nums[i - 1];
        }
    }

    int sumRange(int i, int j) {
        // 0 <= i <= j <= n-1
        if(sums.empty()) return 0;
        return sums[j + 1] - sums[i];
    }

private:
    vector<int> sums;
};

二维版本

class NumMatrix {
public:
    NumMatrix(vector<vector<int>>& matrix) {
        if(matrix.empty())
            sums = vector<vector<int> >();
        else
        {
            int m = matrix.size();
            int n = matrix[0].size();
            sums = vector<vector<int> >(m + 1, vector<int>(n + 1, 0));
            for(int i = 1; i <= m; ++i)
                for(int j = 1; j <= n; ++j)
                    sums[i][j] = sums[i][j - 1] + sums[i - 1][j]
                        - sums[i - 1][j - 1] + matrix[i - 1][j - 1];
        }
    }

    int sumRegion(int row1, int col1, int row2, int col2) {
        if(sums.empty()) return -1;
        // row1 <= row2, col1 <= col2
        return sums[row2 + 1][col2 + 1] - sums[row2 + 1][col1]
            - sums[row1][col2 + 1] + sums[row1][col1];
    }

private:
    vector<vector<int> > sums;
};

引申1: 前缀的推广

1. 能推广的运算

满足区间减法的运算,区间 A = [i, j], 区间 B = [i, k] 区间 C = [k+1, j],那么有了大区间 A 上的结果 a 和其中一个小区间 B 上的结果 b, 要能够算出另一个小区间 C 上的结果 c。

异或:

=>

模下乘法:

=>

2. 前缀和 + 数据结构

问:

上有没有一个区间,其和为 target。 560. 和为K的子数组

当扫描到 i 时,

的前缀和都已经求过了,把它们维护到 unordered_set 中,求完当前值 a[i] 对应的前缀和 S[i+1], 在插入到 unordered_set 之前先问:S[i+1] - target 在 unordered_set 中是否出现,如果出现则找到答案。

上面的问题还可以有变种:

第1问:

上有多少个区间,其和为 target。

把 unordered_set 改成 unordered_map 就可以

第2问:

上有没有一个区间,其和大于/小于 target。327. 区间和的个数

把 unordered_set 改成线段树

第3问: 第一问的树形版本,437. 路径总和 III

dfs(前序遍历)时,栈里存的是当前节点到根的链,这条链上的和可以作为前缀和维护在 unordered_map 里。从左子树跳到右子树的时候,左子树的所有节点对应的前缀和要先从 unordered_map 中删掉。

引申2: 差分

前缀和序列

的差分序列
就等于原序列,其中

原序列

的差分序列为
,其中
。则对差分序列求前缀和序列,就得到原序列。

差分序列的好处是如果要对原序列的一个区间 [l, r] 上所有值加 val,原序列上要操作 r-l+1 次(a[l .. r] + val),在差分序列上只需要操作 2 次(b[l] + val, b[r+1] - val)。如果这种区间操作需要很多次,最后的查询只有一次的话,就非常适合在差分序列上操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值