差分数组知识点

差分数组 (Difference Array)

差分数组是前缀和的逆运算,适用于频繁对数组某个区间进行增减操作的场景。

  • 暴力做法:每次区间修改 O(n),m 次操作 O(n*m)
  • 差分数组:每次修改 O(1),最后还原 O(n),总复杂度 O(n + m)

1
diff[i] = nums[i] - nums[i-1]
索引01234
nums25143
diff23-43-1

对区间 [i, j] 的所有元素 +val:

1
2
diff[i] += val;
diff[j + 1] -= val;   // 注意边界:j+1 不能越界

原理:

  • diff[i] += val → 从 i 开始,后面所有元素都加了 val
  • diff[j+1] -= val → 从 j+1 开始,抵消前面的增量
  • 综合效果:只有 [i, j] 区间加了 val

对 diff 做一次前缀和,得到最终的 nums:

1
2
3
4
res[0] = diff[0];
for (int i = 1; i < n; i++) {
    res[i] = res[i - 1] + diff[i];
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Difference {
private:
    vector<int> diff;

public:
    // 构造差分数组
    Difference(vector<int>& nums) {
        diff.resize(nums.size());
        diff[0] = nums[0];
        for (int i = 1; i < nums.size(); i++) {
            diff[i] = nums[i] - nums[i - 1];
        }
    }

    // 区间 [i, j] 增加 val(可为负数)
    void increment(int i, int j, int val) {
        diff[i] += val;
        if (j + 1 < diff.size()) {
            diff[j + 1] -= val;
        }
        // j+1 越界说明加到末尾,不用减
    }

    // 返回最终结果数组
    vector<int> result() {
        vector<int> res(diff.size());
        res[0] = diff[0];
        for (int i = 1; i < diff.size(); i++) {
            res[i] = res[i - 1] + diff[i];
        }
        return res;
    }
};

例题: 假设你有一个长度为 n 的数组,初始都为 0。给你 k 个更新操作,每个操作表示对区间 [start, end] 增加 inc。返回最终数组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
vector<int> getModifiedArray(int n, vector<vector<int>>& updates) {
    vector<int> diff(n, 0);
    for (auto& op : updates) {
        int s = op[0], e = op[1], val = op[2];
        diff[s] += val;
        if (e + 1 < n) diff[e + 1] -= val;
    }
    // 还原
    vector<int> res(n);
    res[0] = diff[0];
    for (int i = 1; i < n; i++) {
        res[i] = res[i - 1] + diff[i];
    }
    return res;
}

LeetCode 1109: 这里有 n 个航班,预订记录 [i, j, k] 表示从 i 到 j 航班增加 k 个座位。返回每个航班的最终预订数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
    vector<int> diff(n, 0);
    for (auto& b : bookings) {
        int first = b[0] - 1;   // 注意题目是 1-based
        int last = b[1] - 1;
        int seats = b[2];
        diff[first] += seats;
        if (last + 1 < n) diff[last + 1] -= seats;
    }
    // 前缀和还原
    for (int i = 1; i < n; i++) {
        diff[i] += diff[i - 1];
    }
    return diff;
}

LeetCode 1094: 车上有 capacity 个空位,乘客上下车,问能不能全部装下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
bool carPooling(vector<vector<int>>& trips, int capacity) {
    vector<int> diff(1001, 0);  // 站点最多 1000
    for (auto& t : trips) {
        int num = t[0], from = t[1], to = t[2];
        diff[from] += num;
        diff[to] -= num;   // 到 to 站下车,不用 to+1
    }

    int cur = 0;
    for (int x : diff) {
        cur += x;
        if (cur > capacity) return false;
    }
    return true;
}

对于矩阵的区间加减,也有二维差分:

1
2
3
4
5
6
7
// 对矩形 (x1,y1) ~ (x2,y2) 加 val
diff[x1][y1] += val;
diff[x2 + 1][y1] -= val;
diff[x1][y2 + 1] -= val;
diff[x2 + 1][y2 + 1] += val;

// 最后做二维前缀和还原

题目类型难度
370. 区间加法模板题中等
1109. 航班预订统计模板题中等
1094. 拼车差分 + 扫描线中等
253. 会议室 II差分思想中等
731. 我的日程安排表 II差分 + TreeMap中等
598. 范围求和 II二维差分变种简单

  1. 判断要不要用差分: 题目出现「多次区间加减,最后求结果」,直接想差分
  2. 注意下标: 题目给的是 1-based 还是 0-based,要转换
  3. 边界处理: j+1 越界时不用减,说明加到末尾
  4. 不需要还原整个数组: 比如拼车问题,边累加边判断即可
  5. 数据范围大时: 用 map 存离散的差分点(扫描线思想)

相关内容