举例
考虑数组 a = [1,3,3,5,8],对其中的相邻元素两两做差(右边减左边),得到数组[2,0,2,3]。然后在开头补上a[0],得到差分数组
d = [1,2,0,2,3]
如果从左到右累加d中的元素,我们就还原回了a数组[1,3,3,5,8]。现在把连续数组a[1],a[2],a[3]都加上10,得到a' = [1,13,13,15,18],再次两两作差,并在开头补上a'[0],得到差分数组
d' = [1,12,0,2,-7]
对比d和d',你会发现,对a中连续子数组的操作,可以转变为差分数组d中两个数的操作。
定义和性质
对于数组a,定义其差分数组为
性质 1:从左到右累加 d 中的元素,可以得到数组 a。
性质 2:如下两个操作是等价的。
区间操作:把 a 的子数组a[i],a[i+1],⋯,a[j] 都加上 x。
单点操作:把d[i] 增加 x,把 [+1]d[j+1] 减少 x。特别地,如果 j+1=n,则只需把 [d[i] 增加 x。(n 为数组 a 的长度)
利用性质 2,我们只需要 (1)O(1) 的时间就可以完成数组 a 上的区间操作。最后利用性质 1 从差分数组复原出数组 a。
代码模板
// 你有一个长为 n 的数组 a,一开始所有元素均为 0。
// 给定一些区间操作,其中 queries[i] = [left, right, x],
// 你需要把子数组 a[left], a[left+1], ... a[right] 都加上 x。
// 返回所有操作执行完后的数组 a。
vector<int> solve(int n, vector<vector<int>> queries) {
vector<int> diff(n); // 差分数组
for (auto &q: queries) {
int left = q[0], right = q[1], x = q[2];
diff[left] += x;
if (right + 1 < n) {
diff[right + 1] -= x;
}
}
for (int i = 1; i < n; i++) {
diff[i] += diff[i - 1]; // 直接在差分数组上复原数组 a
}
return diff;
}
lc1094 拼车
车上最初有 capacity
个空座位。车 只能 向一个方向行驶(也就是说,不允许掉头或改变方向)
给定整数 capacity
和一个数组 trips
, trip[i] = [numPassengersi, fromi, toi]
表示第 i
次旅行有 numPassengersi
乘客,接他们和放他们的位置分别是 fromi
和 toi
。这些位置是从汽车的初始位置向东的公里数。
当且仅当你可以在所有给定的行程中接送所有乘客时,返回 true
,否则请返回 false
。
class Solution {
public:
bool carPooling(vector<vector<int>>& trips, int capacity) {
int dist[1001]{};
for(auto &t : trips) {
int num = t[0], from = t[1], to = t[2];
dist[from] +=num;
dist[to] -=num;
}
int s = 0;
for(int v : dist) {
s+=v;
if(s>capacity) {
return false;
}
}
return true;
}
};
lc1109 航班预定统计
这里有 n
个航班,它们分别从 1
到 n
进行编号。
有一份航班预订表 bookings
,表中第 i
条预订记录 bookings[i] = [firsti, lasti, seatsi]
意味着在从 firsti
到 lasti
(包含 firsti
和 lasti
)的 每个航班 上预订了 seatsi
个座位。
请你返回一个长度为 n
的数组 answer
,里面的元素是每个航班预定的座位总数。
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
vector<int> ans(n,0);
for(auto b : bookings) {
int first = b[0],last = b[1], num = b[2];
ans[first-1] +=num;
if(last<n)
ans[last] -=num;
}
for(int i =1;i<n;i++){
ans[i] = ans[i-1] + ans[i];
}
return ans;
}
};
lc2381. 字母移位II
给你一个小写英文字母组成的字符串 s
和一个二维整数数组 shifts
,其中 shifts[i] = [starti, endi, directioni]
。对于每个 i
,将 s
中从下标 starti
到下标 endi
(两者都包含)所有字符都进行移位运算,如果 directioni = 1
将字符向后移位,如果 directioni = 0
将字符向前移位。
将一个字符 向后 移位的意思是将这个字符用字母表中 下一个 字母替换(字母表视为环绕的,所以 'z'
变成 'a'
)。类似的,将一个字符 向前 移位的意思是将这个字符用字母表中 前一个 字母替换(字母表是环绕的,所以 'a'
变成 'z'
)。
请你返回对 s
进行所有移位操作以后得到的最终字符串。
class Solution {
public:
string shiftingLetters(string s, vector<vector<int>>& shifts) {
int size = s.size();
vector<int> ans(size+1,0);
for(auto s : shifts) {
int left = s[0],right = s[1], n = s[2];
if(n) {
ans[left]++;
ans[right+1]--;
}else{
ans[left]--;
ans[right+1]++;
}
}
for(int i =1;i<(size+1);i++){
ans[i] += ans[i-1];
}
for(int i = 0 ;i<size;i++){
s[i] = ((s[i] - 'a') + (ans[i])%26+26) %26+'a';
}
return s;
}
};
lc2251 花期内花的数目
给你一个下标从 0 开始的二维整数数组 flowers
,其中 flowers[i] = [starti, endi]
表示第 i
朵花的 花期 从 starti
到 endi
(都 包含)。同时给你一个下标从 0 开始大小为 n
的整数数组 people
,people[i]
是第 i
个人来看花的时间。
请你返回一个大小为 n
的整数数组 answer
,其中 answer[i]
是第 i
个人到达时在花期内花的 数目 。
差分优化前:
class Solution {
public:
vector<int> fullBloomFlowers(vector<vector<int>>& flowers, vector<int>& people) {
map<int,int > dist;
for(auto f : flowers) {
dist[f[0]]++;
dist[f[1]+1]--;
}
vector<int> ans;
int curr = 0;
for(auto p : people) {
auto it = dist.begin();
curr=0;
while(it!=dist.end()&&it->first<=p){
curr+=it->second;
it++;
}
ans.push_back(curr);
}
return ans;
}
};
class Solution {
public:
vector<int> fullBloomFlowers(vector<vector<int>>& flowers, vector<int>& people) {
map<int,int > dist;
for(auto f : flowers) {
dist[f[0]]++;
dist[f[1]+1]--;
}
int m = people.size();
vector<int> indices(m);
iota(indices.begin(),indices.end(),0);
sort(indices.begin(),indices.end(),[&](int a,int b){
return people[a] < people[b];
});
vector<int> ans(m);
int curr = 0;
auto it = dist.begin();
for(auto x : indices) {
while(it!=dist.end()&&it->first<=people[x]){
curr+=it->second;
it++;
}
ans[x] = curr;
}
return ans;
}
};