当前位置: 首页 > ops >正文

差分数组:原理与应用

一、什么是差分数组

差分数组是一种高效处理区间更新操作的数据结构技巧,特别适用于需要对数组的某个区间进行频繁增减操作的场景。差分数组的核心思想是通过存储相邻元素的差值而非元素本身,将区间操作转化为端点操作,从而将时间复杂度从O(n)降低到O(1)。

给定一个原始数组arr,其对应的差分数组diff定义为:

diff[i] = arr[i] - arr[i-1] (i > 0)
diff[0] = arr[0] (i = 0)

例如:
原始数组: [1, 3, 6, 8, 5]
差分数组: [1, 2, 3, 2, -3]

二、差分数组的优势

差分数组的主要优势在于它可以高效处理区间更新操作。对于传统的数组,如果我们想对区间[i, j]内的所有元素增加一个值val,需要遍历整个区间进行逐个修改,时间复杂度为O(n)。而使用差分数组,我们只需要修改两个端点:

  1. diff[i] += val
  2. diff[j+1] -= val (如果j+1在数组范围内)

这样就将区间操作的时间复杂度从O(n)降低到了O(1)。

三、差分数组的实现

1. 构建差分数组

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

2. 区间更新操作

void updateRange(vector<int>& diff, int i, int j, int val) {diff[i] += val;if (j + 1 < diff.size()) {diff[j+1] -= val;}
}

3. 还原原始数据

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

四、差分数组的应用场景

差分数组特别适用于以下场景:

  1. 频繁的区间增减操作
  2. 需要多次区间操作后查询最终结果
  3. 处理大量区间覆盖问题
  4. 航班预订、会议室安排等问题

五、LeetCode例题解析

1. 航班预订统计(1109. Corporate Flight Bookings)

题目链接:https://leetcode.com/problems/corporate-flight-bookings/

题目描述:有n个航班,编号从1到n。给定一个航班预订表bookings,其中bookings[i] = [firsti, lasti, seatsi]表示在firsti到lasti的每个航班上预订了seatsi个座位。返回一个长度为n的数组answer,表示每个航班上预订的座位总数。

vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {vector<int> diff(n + 1, 0); // 使用n+1大小避免边界检查for (const auto& booking : bookings) {int first = booking[0] - 1; // 转换为0-basedint last = booking[1] - 1;int seats = booking[2];diff[first] += seats;if (last + 1 < n) {diff[last + 1] -= seats;}}vector<int> answer(n);answer[0] = diff[0];for (int i = 1; i < n; ++i) {answer[i] = answer[i-1] + diff[i];}return answer;
}

2. 拼车(1094. Car Pooling)

题目链接:https://leetcode.com/problems/car-pooling/

题目描述:一辆车有capacity个座位,给定trips数组,其中trips[i] = [numPassengers, from, to]表示有numPassengers乘客在from上车,在to下车。判断车辆是否能完成所有行程(任何时候乘客数不超过capacity)。

bool carPooling(vector<vector<int>>& trips, int capacity) {vector<int> diff(1001, 0); // 题目中0 <= from < to <= 1000for (const auto& trip : trips) {int passengers = trip[0];int from = trip[1];int to = trip[2];diff[from] += passengers;diff[to] -= passengers;}int current = 0;for (int i = 0; i <= 1000; ++i) {current += diff[i];if (current > capacity) {return false;}}return true;
}

3. 区间加法(370. Range Addition)

题目链接:https://leetcode.com/problems/range-addition/

题目描述:给定一个初始长度为length的全0数组,和一系列更新操作updates,其中updates[i] = [startIdx, endIdx, inc],表示将子数组[startIdx, endIdx]中的每个元素增加inc。返回执行完所有操作后的数组。

vector<int> getModifiedArray(int length, vector<vector<int>>& updates) {vector<int> diff(length, 0);for (const auto& update : updates) {int start = update[0];int end = update[1];int inc = update[2];diff[start] += inc;if (end + 1 < length) {diff[end + 1] -= inc;}}vector<int> result(length);result[0] = diff[0];for (int i = 1; i < length; ++i) {result[i] = result[i-1] + diff[i];}return result;
}

六、差分数组的变种与扩展

1. 二维差分数组

差分数组的概念可以扩展到二维,用于处理二维矩阵的区间更新问题。二维差分数组的更新需要修改四个角:

// 更新矩形区域(x1,y1)到(x2,y2)
void update2D(vector<vector<int>>& diff, int x1, int y1, int x2, int y2, int val) {diff[x1][y1] += val;if (x2 + 1 < diff.size()) {diff[x2+1][y1] -= val;}if (y2 + 1 < diff[0].size()) {diff[x1][y2+1] -= val;}if (x2 + 1 < diff.size() && y2 + 1 < diff[0].size()) {diff[x2+1][y2+1] += val;}
}

2. 差分数组与线段树的比较

差分数组和线段树都可以处理区间更新问题,但各有优劣:

特性差分数组线段树
区间更新复杂度O(1)O(logn)
单点查询复杂度O(n)O(logn)
空间复杂度O(n)O(n)
实现难度简单较复杂

选择依据:

  • 如果只有区间更新,最后才查询结果,选择差分数组
  • 如果需要边更新边查询,选择线段树

七、总结

差分数组是一种简单而强大的技巧,特别适合处理大量区间更新操作后查询结果的场景。它通过将区间操作转化为端点操作,大幅提高了算法效率。掌握差分数组不仅可以帮助我们解决许多LeetCode问题,在实际工程中也有广泛应用,如资源调度、时间线处理等场景。

通过本文的三个LeetCode例题,我们可以看到差分数组如何优雅地解决看似复杂的问题。建议读者在理解基本原理后,多练习相关题目,以加深对这一技巧的掌握。

http://www.xdnf.cn/news/7645.html

相关文章:

  • 文献分享-临床预测模型-基于围手术期时间数据肝切除术后肝衰竭早期检测
  • CSS 背景全解析:从基础属性到视觉魔法
  • MinIO集群故障,其中一块driver-4异常
  • 网络安全之带正常数字签名的后门样本分析
  • 软件测试之环境搭建及测试流程
  • 见多识广10:大模型的一些基础概念
  • Python训练营打卡——DAY31(2025.5.20)
  • 类和对象------2
  • Leetcode百题斩-字典树
  • MySQL 安全更新大量数据
  • MySQL高可用之ProxySQL + MGR 实现读写分离实战
  • 面向AI研究的模块化即插即用架构综述与资源整理全覆盖
  • 数据库实验——备份与恢复
  • 【普及−】洛谷P1862 ——输油管道问题
  • 【latex】文本颜色修改
  • 【QT】QTableWidget获取width为100,与真实值不符问题解决
  • C++ 网络编程(9)字节序处理和消息队列的控制
  • 缺乏进度跟踪机制,如何掌握项目状态?
  • MyBatis常用方法
  • 零售EDI:Belk Stores EDI需求分析
  • 阅读笔记---城市计算中用于预测学习的时空图神经网络研究综述
  • 《从零开始构建高可用MySQL架构:全流程实战指南》
  • 无人机避障——深蓝学院浙大Fast-planner学习部分(轨迹生成B-Spline部分)
  • Spring是如何实现scope作用域支持
  • 家用和类似用途电器的安全 第1部分:通用要求 与2005版差异(6)
  • pmap中的mode列,脏页,写时复制
  • 公路水运安全员C证用途及重要性
  • 测试工程师要如何开展单元测试
  • JavaSenderMail发送邮件(QQ及OFFICE365)
  • 如何使用通义灵码玩转Python - AI编程助手提升效率