差分法介绍
原题
我们计划在多个电商平台销售某件商品,给出n
个不同的电商平台的该商品的售价区间,请给出该商品在所有电商平台的售价总和最高的最低商品定价。有如下示例,我们有3个平台的售价区间prices = {{7,9},{8,10},{9,10}}
,可以将商品定价在9元,商品总售价是9 * 3 = 27元,如下图所示
基本思路
题目的意思是先保证总售价最高,在这个前提下使得商品的单价最低,比较直观的思路,建立一个hash表,unordered_map<int, int> price
,表示某个price的平台数目,比如示例中price[7] = 1, price[8] = 2, price[9] = 3, price[10] = 1
,然后将hash表中的键和值相乘,求最大值,如果有多个值相同,取键比较小的那个。本题目的难点在于如何以比较低的复杂度建立hash表,常规思路是\(O(n^2)\),可以参考下面的题目。
参考题
先看这道题,1109. 航班预订统计,有n
个航班,它们分别从 1 到 n 进行编号。我们这儿有一份航班预订表,表中第 i
条预订记录 bookings[i] = [i, j, k]
意味着我们在从 i
到 j
的每个航班上预订了 k
个座位。请你返回一个长度为 n 的数组 answer
,按航班编号顺序返回每个航班上预订的座位数。参考示例如下,
输入:bookings = [[1,2,10],[2,3,20],[2,5,25]], n = 5
输出:[10,55,45,25,25]
解题思路
将这道题的示例画一张表格表示一下,就是下面的结果
booking | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 10 | 10 | 0 | 0 | 0 |
2 | 0 | 20 | 20 | 0 | 0 |
3 | 0 | 25 | 25 | 25 | 25 |
total | 10 | 55 | 45 | 25 | 25 |
常规思路就是以航班号为基本坐标,计算每一个航班增加的座位数,然后逐项汇总相加即可。
- 设置初始结果
vector<int> res(n, 0)
; - 遍历
bookings
,每次取其中的航班的预定数,添加到res
对应的数组中,比如第1个booking,那么res[0]+=10; res[1]+=10
,依次类推,直到遍历截止。
上面的算法比较简单直观,但是可以分析发现,算法的复杂度有点高,两层遍历算法时间复杂度是\(O(n^2)\),空间复杂度是\(O(n)\)不甚理想。
有没有复杂度更简单的思路呢?这里有一个类比公交站的思路,可以将航班号码比作公交站牌,比如1号公交站,2号公交站,假定这些公交站是依次按顺序分布在一条直线公路上,第i
个航班的飞机的预定数目就是公交车在第i
个公交站发车时候的乘客数目(包括了上车和下车的乘客数)。
举例说明,第1行表示,第1站交车上人数是10,说明公交车行驶到第1站时上车10人,到第2站时候车上的乘客仍然是10人,说明没有乘客上下车,到第3站时候车上乘客0人,说明此时有10人下车。如果使用长度为N
的数组count
表示每一站上下乘客的变化量(count[i]
表示第i + 1
站上下车的乘客变化量),
对于
booking = [i,j,k]
,
- 表示在公交站第
i
站上车k
人,count[i - 1] += k
;- 第
i + 1
站直到第j
站都没有乘客上下车,count[i],...,count[j - 1]
无操作;- 在第
j + 1
站下车k
人,所以count[j] -= k
为了方便起见,我们缩小问题的规模,以具体的数字代替抽象的代数字母,假如我们就只有3个公交站,取示例中的前2行,
- 公交车刚开始上的人数是0,
vector<int> count(4, 0)
; - 读取第1行,到达第1站,公交车上10人,说明上车10人,无人下车,
count[0]+= 10
,到达第2站公交车上依然是10人,说明也无人上车和下车,到达第3站,公交车上0人,说明10人下车,count[2] -= 10
; - 读取第2行,公交车到达第2站,公交车上20人,说明上车20人无人下车,
count[1] += 20
,第3站车上20人,说明无人下车,第4站车上0人,说明有20人下车,count[3]-=20
。
遍历结束,得到count = {10, 20, -10,-20}
,那么最后每个站点的乘客数就很清楚了,到达第1站前车上乘客0人,到达后上车10人,所以第1站发车前车上10人,第2站到站后上车20人,所以第2站发车前车上乘客10 + 20 = 30人,第3站到站后下车10人,所以发车前车上乘客 30 - 10 = 20人。意思搞清楚之后,代码就很好写了。
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
vector<int> res(n, 0);
vector<int> counter(n + 1, 0);
for (auto &booking : bookings)
{
int start = booking[0];
int end = booking[1];
// 记录每个booking的开始和结尾即可,中间的站点人数无变化
counter[start - 1] += booking[2]; // start站上车
counter[end] -= booking[2]; // end + 1站下车
}
res[0] = counter[0];
for (int i = 1; i < n; i++)
{
res[i] = res[i - 1] + counter[i];
}
return res;
}
时间复杂度为\(O(n)\)。
原题代码
借鉴参考题的代码,原题的代码如下
int GetBestPrice(vector<vector<int>>& prices) {
// find the minimum and maximum price
int minVal = INT_MAX;
int maxVal = INT_MIN;
unordered_map<int, int> priceTable;
for (auto& price : prices) {
minVal = min(price[0], minVal);
maxVal = max(price[1], maxVal);
}
for (int i = minVal; i < maxVal + 1; i++) {
priceTable[i] = 0;
}
// counter[0]代表第minVal站,注意此处的对应关系
int len = maxVal - minVal + 1;
vector<int> counter(len + 1, 0);
for (auto& price : prices)
{
int start = price[0] - minVal + 1;
int end = price[1] - minVal + 1;
counter[start - 1] += 1; // start站上车
counter[end] -= 1; // end + 1站下车
}
priceTable[minVal] = counter[0];
int bestPrice = minVal;
maxVal = minVal * priceTable[minVal];
for (int i = 1; i < len; i++)
{
priceTable[minVal + i] = priceTable[minVal + i - 1] + counter[i];
if (priceTable[minVal + i] * (minVal + i) > maxVal) {
maxVal = priceTable[minVal + i] * (minVal + i);
bestPrice = minVal + i;
}
}
return bestPrice;
}