Lishengxie
  • Posts
  • About
  • Contact
  1. Home
  2. Posts
  3. 算法学习-差分数组

算法学习-差分数组

Dec 23, 2023 算法学习 Lishengxie

差分数组

  • 参考教程:https://wansuanfa.com/index.php/589
  • leetcode题目:1109. 航班预订统计

问题描述

给定一个数组,需要频繁地对某个区间内的元素做加减操作,并获取最后的操作结果。常规做法是每次都遍历整个区间然后修改区间内的元素,但是元素的访问需要时间、频繁访问元素会减慢程序的运行时间。因此,可以使用差分数组来进行优化。

一维差分数组定义

一维差分数组 d[n] 定义为原始数组 nums 相邻元素之间的差,即d[i]=nums[i]-nums[i-1],其中d[0]=nums[0]。这样原数组就是差分数组的前缀和

nums[i] = \sum_{k=0}^i d[k].

由此,我们可以改进原来的区间操作,如对区间[a,b]内每个元素加3,那么只需要在区间的两端进行操作即可,即

d[a] += 3, d[b+1] -= 3

证明

假设nums1是修改后的数组,d1是修改后的差分数组,其中d1[a]=d[a]+3, d1[b+1]=d[b+1]-3.

hugo里面换行公式需要\\\\四个反斜杠

  • 对于0\leq i \lt a, nums1[i] = \sum_{k=0}^i d1[k] = \sum_{k=0}^i d[k] = nums[i].
  • 对于a\leq i \leq b,
    \begin{aligned} nums1[i] & = \sum_{k=0}^i d1[k] \\\\ & = \sum_{k=0}^{a-1}d[k] + \sum_{k=a+1}^{b}d[k] + d[a] + 3 \\\\ & = \sum_{k=0}^i d[k] + 3 \\\\ & = nums[i]+3 \end{aligned}
  • 对于i \gt b,
    \begin{align} nums1[i] &= \sum_{k=0}^i d1[k]\\\\ &= \sum_{k=0}^{a-1}d[k] + \sum_{k=a+1}^{b}d[k] + \sum_{k=b+1}^{i}d[k] + d[a] + 3 + d[b] - 3\\\\ &= \sum_{k=0}^i d[k] + 3 - 3\\\\ &= nums[i]. \end{align}
    由此,可以证明只修改差分数组的两端即可以修改原数组的整个区间。

代码

class Solution {
public:
    vector<int> diff;
    vector<int> nums;

    void diffNums() {
        diff[0] = nums[0];
        for(int i = 1; i < nums.size(); ++i) {
            diff[i] = nums[i] - nums[i-1];
        }
    }

    void increment(int a, int b, int val) {
        diff[a] += val;
        if (b + 1 < diff.size())
            diff[b + 1] -= val;
    }

    void result() {
        nums[0] = diff[0];
        for (int i = 1; i < diff.size(); i++)
            nums[i] = diff[i] + nums[i - 1];
    }

    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        nums.resize(n, 0);
        diff.resize(n, 0);
        diffNums();
        for (int i = 0; i < bookings.size(); ++i) {
            increment(bookings[i][0]-1, bookings[i][1]-1, bookings[i][2]);
        }
        result();
        return nums;
    }
};

拓展 - 二维差分数组

二维差分数组可以在一维差分数组的基础上进行拓展,视为一个平面,定义如下

d[i][j] = nums[i][j] - nums[i-1][j] - nums[i][j-1] + nums[i-1][j-1]

那么相应地

nums[i][j] = nums[i-1][j] + nums[i][j-1] - nums[i-1][j-1] + d[i][j]

假设以 (x1,y1) 为左上角, (x2,y2) 为右下角构成一个区间S,如果对这个区间内的每个元素增加val,只需要执行下面四步即可。

\begin{align} &d[x1][y1] += val \\\\ &d[x1][y2+1] -= val \\\\ &d[x2+1][y1] -= val \\\\ &d[x2+1][y2+1] += val \end{align}

证明

同样假设num1和d1是修改后的数组和查分数组,以下分情况进行讨论

  • 对于(m,n)满足m或n, 容易知道该范围内查分数组未发生变化,求解该范围内的数组不需要用到区间S内的差分数组,因此该范围内数组不受影响;
  • 对于(m,n)\in S, 不妨设原数组num的元素均为a, 那么
    \begin{align} nums1[x1][y1] &= nums1[x1-1][y1] + nums1[x1][y1-1] - nums1[x1-1][y1-1] + d1[x1][y1]\\\\ &= nums[x1-1][y1] + nums[x1][y1-1] - nums[x1-1][y1-1] + d[x1][y1] + val\\\\ &= nums[x1][y1] + val; \end{align}
    对于S内的其他点,使用此递推公式同样可以推导出
    nums1[m,n] = nums[m][n]+val
  • 对于(m,n)满足x1\leq m \leq x2且n\gt y2, 我们可以知道
    \begin{align} nums1[x1][y2+1] &= nums1[x1-1][y2+1] + nums1[x1][y2] - nums1[x1-1][y2] + d1[x1][y2+1]\\\\ &= nums[x1-1][y2+1] + nums[x1][y2] + val - nums[x1-1][y2] + d[x1][y2+1] - val\\\\ &= nums[x1][y2+1]; \end{align}
    对于其他(m,n)使用递推公式同样可以发现数组的值没有发生变化。
  • 对于(m,n)满足m\gt x2且y1\leq n \leq y2, 可以使用类似上一种情况的方式推导发现数组数值未发生变化;
  • 最后,对于(m,n)满足m\gt x2且n\gt y2,我们考虑
    \begin{align} nums1[x2+1][y2+1] &= nums1[x2][y2+1] + nums1[x2+1][y2] - nums1[x2][y2] + d1[x2+1][y2+1]\\\\ &= nums[x2][y2+1] + nums[x2+1][y2] - nums[x2][y2] - val + d[x2+1][y2+1] + val\\\\ss &= nums[x2+1][y2+1]; \end{align}
    对于该区域其他点可以类推。

由此,我们得到,差分数组定义以及更新方式满足条件。

代码(来自参考链接)

// Java代码,需要注意边界情况
private int[][] d;// 差分数组。
private int[][] a;// 原数组。

public TwoDiffNums(int[][] a) {
    this.a = a;
    int m = a.length;
    int n = a[0].length;
    d = new int[m][n];
    // 求差分数组。
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++)
            add(i, j, i, j, a[i][j]);
}

public void add(int x1, int y1, int x2, int y2, int val) {
    d[x1][y1] += val;
    if (y2 + 1 < d[0].length)
        d[x1][y2 + 1] -= val;
    if (x2 + 1 < d.length)
        d[x2 + 1][y1] -= val;
    if (x2 + 1 < d.length && y2 + 1 < d[0].length)
        d[x2 + 1][y2 + 1] += val;
}

// 返回结果数组。
public int[][] result() {
    for (int i = 0; i < a.length; i++) {
        for (int j = 0; j < a[0].length; j++) {
            int x1 = i > 0 ? a[i - 1][j] : 0;
            int x2 = j > 0 ? a[i][j - 1] : 0;
            int x3 = i > 0 && j > 0 ? a[i - 1][j - 1] : 0;
            a[i][j] = x1 + x2 - x3 + d[i][j];
        }
    }
    return a;
}

Table of Contents

  • 差分数组
  • 问题描述
  • 一维差分数组定义
    • 证明
    • 代码
  • 拓展 - 二维差分数组
    • 证明
    • 代码(来自参考链接)

Recent Posts

  • 分布式ID生成方案全解析:从数据库到雪花算法 Jun 25, 2026
  • Let's Encrypt 免费申请 SSL 证书,并实现自动续期 Sep 14, 2025
  • Redis ziplist、quicklist 和 listpack Mar 3, 2025
  • Nginx禁止使用IP直接访问服务器上相应端口 May 11, 2024
  • LeetCode刷题 - KMP算法 Feb 4, 2024

Categories

  • Linux7
  • 算法学习7
  • 论文笔记6
  • C++4
  • 未分类3
  • Go学习2
  • Redis1
  • SystemC1
  • Verilog1

Tags

← LeetCode刷题-二叉树遍历迭代法 Transfer Server →

Related Posts

  • LeetCode刷题-二叉树遍历迭代法 Oct 17, 2023
  • LeetCode刷题 - 滑动窗口最大值 Oct 17, 2023
  • 多目标优化问题及两种常用解法 Apr 30, 2023
  • 启发式优化算法学习-模拟退火 Apr 30, 2023
皖ICP备2023003716号-1 | 公安备案皖公网安备34012202341113 | 违法和不良信息举报邮箱:1141751053@qq.com
Powered by Hugo & Explore Theme.