差分数组
本质上说,就是一个数组,但可以在O(1)的时间复杂度处理区间修改;
形式
假设有一数组a,差分数组为d,有:任一 i ∈ [ 2 , n ] i \in[2,n] i∈[2,n],都有 d [ i ] = a [ i ] − a [ i − 1 ] d[i] = a[i]-a[i-1] d[i]=a[i]−a[i−1],则可以发现有以下性质:原数组第a[n]项为: a [ n ] = ∑ i = 0 n d [ i ] a[n]=\sum_{i=0}^{n}d[i] a[n]=∑i=0nd[i],原数组前n项和Sum为: S u m ( n ) = ∑ i = 0 n a [ i ] = ∑ i = 0 n ∑ i = 0 n d [ i ] = ∑ i = 0 n ( n − i + 1 ) d [ i ] Sum(n) = \sum_{i=0}^{n}a[i]=\sum_{i=0}^{n}\sum_{i=0}^{n}d[i]=\sum_{i=0}^{n}(n-i+1)d[i] Sum(n)=i=0∑na[i]=i=0∑ni=0∑nd[i]=i=0∑n(n−i+1)d[i],
用法
主要支持两种操作:1.区间修改 2.单点查询
- 快速处理区间加减操作:对数列区间[L,R]中的每个数都加上x,根据性质一,只需要将查分数组中的d[L]+x,而a[R+1]后的数没有加上x,则需要将d[R+1]-x,这样对后面的数就没有影响;
- 询问区间和问题:根据性质二我们知道前缀和Sum,区间和[L,R]的和为:Sum® -Sum(L);
举例
原数组:3,6,8,7,4,3,2,9 ;查分数组:3,3,2,-1,-3,-1,-1,7
区间加减:比如对下标[3,6]之间的数字都加上2,原数组变为:3,6,8,9,6,5,4,9。而查分数组变为:3,3,2,1,-3,-1,-1,5
力扣题目(LeetCode)
1.1109.航班预定统计
2.798. 得分最高的最小轮调
3. 1094. 拼车
mycode and analysis
//简单思路版本,执行耗时:799 ms,击败了15.08% 的Java用户,
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] res = new int[n];
int size = bookings.length;
for (int i = 0; i < size; i++) {
int l = bookings[i][0],r= bookings[i][1],seats= bookings[i][2];
for (int j = l; j <= r; j++) {
res[j-1]+=seats;
}
}
return res;
}
//差分数组版本:执行耗时:2 ms,击败了100.00% 的Java用户
public int[] corpFlightBookings(int[][] bookings, int n) {
int[] d = new int[n];
int[] res=new int[n];
int size = bookings.length;
for (int i = 0; i < size; i++) {
int l = bookings[i][0],r = bookings[i][1],seats =bookings[i][2];
d[l-1]+=seats;
if(r<n)d[r]-=seats;
}
res[0]=d[0];
for (int i = 1; i < n; i++) {
res[i]=res[i-1]+d[i];
}
return res;
}
public int bestRotation(int[] nums) { //执行耗时:6 ms,击败了84.56% 的Java用户
//思路在于:我们可以找到nums中每个数 轮转得分的范围,然后再范围内都加一;
// 范围内加上一个固定的数,便可以想到使用 差分数组;
int res,n=nums.length;
int[] points = new int[n];
for (int i = 0; i < n; i++) {
//计算每个数 能得分的k 的范围
// 比如100个数,第5个数为34,能得分的下标范围为[34,99],那么移动的k值应该为[6,66]
// 比如 ,第26个数为5,能得分的下标范围为:[5,99],移动k值的范围为:[0,21]\[27,99];
// 那么一般情况下:nums[i] 能得分的范围为:[0,i],移动次数的范围要分类讨论
if(nums[i]>i){
//k值取值范围应该为:
int l =i+1,r=(n-nums[i]+i)%n;
points[l]+=1;
if(r+1 <n) points[r+1]-=1;
}else if(nums[i]<=i) { //两个区间
points[0]+=1;
int r = i-nums[i];
if(r+1<n) {
points[r + 1] -= 1;
//另外一个区间为[n-nums[i]+i,n-1]
if (i + 1 < n) points[i + 1] += 1;
}
}
}
//遍历获取最小的k值;
res= points[0];
int max = points[0];
int index=0;
for (int i = 1; i < n; i++) {
res+=points[i];
if(res>max){
max=res;
index = i;
}else if(res==max){
if(index >i) index=i;
}
}
return index;
}
public boolean carPooling(int[][] trips, int capacity) {
int[] station = new int[1000+1]; //差分数组
int maxMile =0; //记录最远距离;
int n = trips.length;
for (int i = 0; i < n; i++) {
int addPersons = trips[i][0]; //跟上面几题一样;
if(maxMile<trips[i][2]) maxMile = trips[i][2];
station[trips[i][1]]+=addPersons;
station[trips[i][2]]-=addPersons;
}
int personCar =0;
for (int i = 0; i <= maxMile; i++) {
personCar +=station[i];
if(personCar>capacity) return false;
}
return true;
}