零. 差分数组工具类
1. 思路和代码
- diff 存在的意义就是想要通过构建 diff 数组来实现对原数组(nums)频繁的加减操作。
- 差分数组的构建代码:
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
- 来看看东哥这个例子-------------------想对区间 nums[ 1 … 3 ] 的元素全部加 3:
- 构建差分数组如下图所示。
- 让diff [ i ] += 3,再让diff[ j + 1 ] -= 3,就搞定了,是不是不太好理解,我们一步一步来看。
- 我们知道 nums[ i ] = nums[ i - 1 ] + diff[ i ]。nums的每一个元素值(除了第一个)都是通过前一个加上diff得到的,也就是说,如果我们相对nums进行批量操作,其实就是对diff进行操作就可以了,具体接着看这个例子:
- diff[ 1 ] = -6 +3 = -3
- nums[ 1 ] = nums[ 0 ] + diff[ 1 ] = 8 - 3 = 5
- nums[ 2 ] = nums[ 1 ] + diff[ 2 ] = 5 + 4 = 9
- nums[ 3 ] = nums[ 2 ] + diff[ 3 ] = 9 - 3 = 6,这三个值相较于原来的值都增加了3,没毛病。
- diff[ 4 ] = -2 - 3 = -5
- nums[ 4 ] = nums[ 3 ] + diff[ 4 ] = 6 - 5 = 1,数字没有变化,如果数组更长的话,后面的元素还是不会有变化,因为diff[ 4 ]发生了改变,后面所有的nums都将发生改变。
- 构建差分数组如下图所示。
// 差分数组工具类
class Difference {
// 差分数组
private int[] diff;
/* 输入一个初始数组,区间操作将在这个数组上进行 */
public Difference(int[] nums) {
assert nums.length > 0;
diff = new int[nums.length];
// 根据初始数组构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
}
/* 给闭区间 [i, j] 增加 val(可以是负数)*/
public void increment(int i, int j, int val) {
diff[i] += val;
/*
如果j + 1 >= diff.length,意味着将i以后所有的数组元素全部加val,
也就不存在-val的操作了
*/
if (j + 1 < diff.length) {
diff[j + 1] -= val;
}
}
/* 返回结果数组 */
public int[] result() {
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
}
2. 总结
- 只要花费 O(1) 的时间修改 diff 数组,就相当于给 nums 的整个区间做了修改。多次修改 diff,然后通过 diff 数组反推,即可得到 nums 修改后的结果。
- 差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。
- 前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。
一. 区间加法
- 题目链接:https://leetcode.cn/problems/range-addition/
1. 思路和代码
I. 博主的做法:
- 简化版的差分数组,初始元素都是0,也就省去了构造diff这个步骤,diff直接就全0。
class Solution {
public int[] getModifiedArray(int length, int[][] updates) {
int nums[] = new int[length];
int diff[] = new int[length];
for(int i = 0; i < updates.length; i++){
diff[updates[i][0]] += updates[i][2];
if(updates[i][1] + 1 < length)
diff[updates[i][1] + 1] -= updates[i][2];
}
nums[0] = diff[0];
for(int i = 1; i < length; i++)
nums[i] = nums[i-1] + diff[i];
return nums;
}
}
- 这里犯了个小错误:diff[updates[i][1] + 1],没有 + 1,导致结果的错误。
- 看了东哥的代码,for循环那一块也可以改成下面:
for(int[] temp : updates){
diff[temp[0]] += temp[2];
if(temp[1] + 1 < length)
diff[temp[1] + 1] -= temp[2];
}
II. 东哥的做法:
- 东哥这个做法更像是工程的做法,将差分数组封装成一个类,然后new 对象进行操作,值得学习
// 差分数组工具类
class Difference {
// 差分数组
private int[] diff;
/* 输入一个初始数组,区间操作将在这个数组上进行 */
public Difference(int[] nums) {
assert nums.length > 0;
diff = new int[nums.length];
// 根据初始数组构造差分数组
diff[0] = nums[0];
for (int i = 1; i < nums.length; i++) {
diff[i] = nums[i] - nums[i - 1];
}
}
/* 给闭区间 [i, j] 增加 val(可以是负数)*/
public void increment(int i, int j, int val) {
diff[i] += val;
if (j + 1 < diff.length) {
diff[j + 1] -= val;
}
}
/* 返回结果数组 */
public int[] result() {
int[] res = new int[diff.length];
// 根据差分数组构造结果数组
res[0] = diff[0];
for (int i = 1; i < diff.length; i++) {
res[i] = res[i - 1] + diff[i];
}
return res;
}
}
class Solution {
public int[] getModifiedArray(int length, int[][] updates) {
int[] nums = new int[length];
Difference df = new Difference(nums);
for(int[] temp : updates){
int i = temp[0];
int j = temp[1];
int val = temp[2];
df.increment(i, j, val);
}
return df.result();
}
}
2. 总结
- 这里犯了个小错误:diff[updates[i][1] + 1],没有 + 1,导致结果的错误。
- 东哥这个做法更像是工程的做法,将差分数组封装成一个类,然后new 对象进行操作,值得学习
二. 航班预订统计
- 题目链接:https://leetcode.cn/problems/corporate-flight-bookings/
1. 思路和代码
I. 博主的做法:
- 跟上个题很像很像,就是传进去的下标不再是从0开始,而是从1开始
class Solution {
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] nums = new int[n];
int[] diff = new int[n];
for(int[] temp : bookings){
diff[temp[0]-1] += temp[2];
if(temp[1] < n)
diff[temp[1]] -= temp[2];
}
nums[0] = diff[0];
for(int i = 1; i < n; i++)
nums[i] = nums[i-1] + diff[i];
return nums;
}
}
- 还是出现了问题要修改diff[ i…j ]之间的值,一定动的是 j 后面的元素也就是j + 1,这里,处理下标处理着处理着就忘了。
II. 东哥的做法:
- 和上面题很像,注意索引 -1 就完了。
/*
差分数组工具类
......
*/
int[] corpFlightBookings(int[][] bookings, int n) {
// nums 初始化为全 0
int[] nums = new int[n];
// 构造差分解法
Difference df = new Difference(nums);
for (int[] booking : bookings) {
// 注意转成数组索引要减一哦
int i = booking[0] - 1;
int j = booking[1] - 1;
int val = booking[2];
// 对区间 nums[i..j] 增加 val
df.increment(i, j, val);
}
// 返回最终的结果数组
return df.result();
}
2. 总结
- 修改diff[ i…j ]之间的值,一定动的是 j 后面的元素也就是 j + 1。
三. 拼车
- 题目链接:https://leetcode.cn/problems/car-pooling/
1. 思路和代码
I. 博主的做法:
- 博主看了看数据,上面写着:0 <= fromi < toi <= 1000,好嘛,用差分数组。
- 直接开1001个空间,不断的上下人,如果有一站的人数 > capacity(也就是座位数),那么就返回false。
- 这里有个比较坑的点就是,直接看例子:
- eg:[[1,2,3],[1,3,4]],capacity = 1,这个例子,返回的是true,相当于下车的那一站车上就没这个人,也可以理解为提前一站就下车了,那么在if(temp[2] < 1001)这一步,就不用+1了,因为提前一站就下了。
class Solution {
public boolean carPooling(int[][] trips, int capacity) {
int nums[] = new int[1001];
int diff[] = new int[1001];
for(int[] temp : trips){
diff[temp[1]] += temp[0];
//这里就不用+1了,因为提前一站就下了
if(temp[2] < 1001)
diff[temp[2] ] -= temp[0];
}
nums[0] = diff[0];
if(nums[0] > capacity)
return false;
for(int i = 1; i < 1001; i++){
nums[i] = nums[i-1] + diff[i];
if(nums[i] > capacity)
return false;
}
return true;
}
}
II. 东哥的做法:
/*
差分数组工具类
......
*/
boolean carPooling(int[][] trips, int capacity) {
// 最多有 1001 个车站
int[] nums = new int[1001];
// 构造差分解法
Difference df = new Difference(nums);
for (int[] trip : trips) {
// 乘客数量
int val = trip[0];
// 第 trip[1] 站乘客上车
int i = trip[1];
// 第 trip[2] 站乘客已经下车,
// 即乘客在车上的区间是 [trip[1], trip[2] - 1]
int j = trip[2] - 1;
// 进行区间操作
df.increment(i, j, val);
}
int[] res = df.result();
// 客车自始至终都不应该超载
for (int i = 0; i < res.length; i++) {
if (capacity < res[i]) {
return false;
}
}
return true;
}
- 差分数组的工具类当中的increment()方法要求传入的是闭区间(本题当中就是乘车的区间,也就是[ trip[1],trip[2] - 1 ])
2. 总结
- 这题两个注意的点:
- 转化为差分数组:将所有的车站封装成nums数组,而每一站上来乘客的数量作为批量加减的值。
- 乘车区间的问题,下车会提前一站!!!!!!
参考:https://labuladong.github.io/algo/di-yi-zhan-da78c/shou-ba-sh-48c1d/xiao-er-me-c304e/