第 1 章:数组与字符串(2) --- 前缀和 & 差分数组

一维前缀和(Prefix Sum)

✅ 定义

前缀和用于快速求解数组中某段区间 [l, r] 的和。

前缀和数组 sum 定义为:

sum[0] = 0;
sum[i + 1] = sum[i] + nums[i];  // sum[i+1] 表示 nums[0] 到 nums[i] 的和

求区间和:

// 区间 [l, r] 的和
int rangeSum = sum[r + 1] - sum[l];

为什么是 sum[r+1] - sum[l]

  • sum[r+1] 包含了 nums[0] 到 nums[r] 的和。

  • sum[l] 包含了 nums[0] 到 nums[l-1] 的和。

  • 所以两者相减,就得到了 nums[l] 到 nums[r] 的区间和。

🧱 模板代码(C++)

vector<int> prefixSum(const vector<int>& nums) 
{
    int n = nums.size();
    vector<int> sum(n + 1, 0);
    for (int i = 0; i < n; ++i) 
    {
        sum[i + 1] = sum[i] + nums[i];
    }
    return sum;
}

🧪 示例题目

    1. 区域和检索 - 数组不可变
    1. 和为K的子数组

一维差分数组(Difference Array)

✅ 定义与作用

差分数组是前缀和的逆操作,它适用于:频繁修改区间的数值,但不需要频繁查询原始值的场景。

原数组 nums 转换为差分数组 diff

diff[0] = nums[0];
diff[i] = nums[i] - nums[i - 1];

恢复原数组:

nums[0] = diff[0];
nums[i] = nums[i - 1] + diff[i];

🤔 为什么差分可以快速区间加值?

要把某个区间 [l, r] 中的每个值都加上 val

我们可以仅做:

diff[l] += val;
diff[r+1] -= val;

这样,在恢复原数组时,会自动把区间 [l, r] 中的值全部加上 val

原理推导:

设初始数组为 [0, 0, 0, 0, 0],差分数组也是全 0。

现在我们想将区间 [1, 3] 加上 5:

diff[1] += 5;   // 从位置1开始,加5
diff[4] -= 5;   // 从位置4之后开始,减5,抵消前面的加法

恢复时:

nums[0] = diff[0]
nums[1] = nums[0] + diff[1] = 0 + 5 = 5
nums[2] = nums[1] + diff[2] = 5 + 0 = 5
nums[3] = nums[2] + diff[3] = 5 + 0 = 5
nums[4] = nums[3] + diff[4] = 5 - 5 = 0

=> [0, 5, 5, 5, 0]

即区间 [1,3] 成功加了 5,其他区域不变!

🧱 模板代码(C++)

vector<int> getDiffArray(const vector<int>& nums)
{
    int n = nums.size();
    vector<int> diff(n);
    diff[0] = nums[0];
    for (int i = 1; i < n; ++i)
        diff[i] = nums[i] - nums[i - 1];
    return diff;
}

void rangeAdd(vector<int>& diff, int l, int r, int val) 
{
    diff[l] += val;
    if (r + 1 < diff.size()) diff[r + 1] -= val;
}

vector<int> restoreArray(const vector<int>& diff) 
{
    int n = diff.size();
    vector<int> res(n);
    res[0] = diff[0];
    for (int i = 1; i < n; ++i)
        res[i] = res[i - 1] + diff[i];
    return res;
}

🧪 示例题目

    1. 拼车
    1. 航班预订统计
    1. 区间加法

二维前缀和(2D Prefix Sum)

✅ 定义

对一个二维矩阵 matrix 构建前缀和 sum

sum[i][j] 表示矩阵左上角 (0,0) 到 (i-1,j-1) 之间的子矩阵和

💡 构建公式:

sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] 
+ matrix[i][j];

✍️ 查询子矩阵和:

// 查询 (r1, c1) 到 (r2, c2) 的子矩阵和
int total = sum[r2 + 1][c2 + 1] - sum[r2 + 1][c1] - sum[r1][c2 + 1] 
+ sum[r1][c1];

📘 公式详解

我们查询的区域是矩形 (r1, c1)(r2, c2),如下图:

┌────────────┬──────────────┐
│     A      │      B       │
│ (0~r1-1,   │  (0~r1-1,    │
│  0~c1-1)   │   c1~c2)     │
├────────────┼──────────────┤
│     C      │      D       │ ← 查询目标区域
│ (r1~r2,    │  (r1~r2,     │
│  0~c1-1)   │   c1~c2)     │
└────────────┴──────────────┘

公式说明:

  • sum[r2+1][c2+1]: 包含整个大矩形 ABCD

  • - sum[r2+1][c1]: 去除 AB 区域

  • - sum[r1][c2+1]: 去除 AC 区域

  • + sum[r1][c1]: 加回重复减掉的 A 区域

🧱 模板代码

vector<vector<int>> compute2DPrefixSum(const vector<vector<int>>& matrix) {
    int m = matrix.size(), n = matrix[0].size();
    vector<vector<int>> sum(m + 1, vector<int>(n + 1, 0));
    for (int i = 0; i < m; ++i)
    {
        for (int j = 0; j < n; ++j)
        {
            sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + matrix[i][j];
        }
    }
    return sum;
}

🧪 示例题目

    1. 二维区域和检索 - 不可变

🎯 LeetCode 刷题 + C++ 代码

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

class NumArray 
{
    vector<int> sum;
public:
    NumArray(vector<int>& nums) 
    {
        int n = nums.size();
        sum.resize(n + 1, 0);
        for (int i = 0; i < n; ++i) sum[i + 1] = sum[i] + nums[i];
    }

    int sumRange(int left, int right) 
    {
        return sum[right + 1] - sum[left];
    }
};

560. 和为 K 的子数组

如果我们有一个前缀和数组 sum,那么子数组 [i, j] 的和是:

sum[j + 1] - sum[i] == k
=> sum[j + 1] - k == sum[i]

也就是说,只要我们能在之前出现过 sum[i] == sum[j+1] - k,就找到一个子数组

int subarraySum(vector<int>& nums, int k)
{
    unordered_map<int, int> prefixCount;    // key是sum, value:表示sum出现的次数
    prefixCount[0] = 1;   // 初始化前缀和为 0 的次数为 1
    int sum = 0, res = 0;
    for (int num : nums) 
    {
        sum += num;  // 累加当前元素到前缀和
        res += prefixCount[sum - k];  // 查找是否存在某个之前的前缀和为 sum - k, 
        ++prefixCount[sum];
    }
    return res;  // 子数组的个数
}

238. 除自身以外数组的乘积

(不使用前缀和,但属于“前后缀积”的技巧)

vector<int> productExceptSelf(vector<int>& nums) 
{
    int n = nums.size();
    vector<int> res(n, 1);
    int left = 1, right = 1;
    for (int i = 1; i < n; ++i)
    {
        left *= nums[i - 1];
        res[i] *= left;
    }
    for (int i = n - 2; i >= 0; --i)
    {
        right *= nums[i + 1];
        res[i] *= right;
    }
    return res;
}

1094. 拼车(差分数组)

bool carPooling(vector<vector<int>>& trips, int capacity) 
{
    vector<int> diff(1001, 0);
    for (auto& trip : trips) 
    {
        int val = trip[0], from = trip[1], to = trip[2];
        diff[from] += val;
        if (to < 1001) diff[to] -= val;
    }
    int cur = 0;
    for (int d : diff)
    {
        cur += d;
        if (cur > capacity) return false;
    }
    return true;
}

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

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

    int sumRegion(int r1, int c1, int r2, int c2)
    {
        return sum[r2 + 1][c2 + 1] - sum[r2 + 1][c1] - sum[r1][c2 + 1] + sum[r1][c1];
    }
};
posted @ 2025-05-06 11:08  DevByHe  阅读(28)  评论(0)    收藏  举报