题目描述
你驾驶出租车行驶在一条有 n 个地点的路上。这 n 个地点从近到远编号为 1 到 n ,你想要从 1 开到 n ,通过接乘客订单盈利。你只能沿着编号递增的方向前进,不能改变方向。
乘客信息用一个下标从 0 开始的二维数组 rides 表示,其中 r i d e s [ i ] = [ s t a r t i , e n d i , t i p i ] rides[i] = [start_i, end_i, tip_i] rides[i]=[starti,endi,tipi] 表示第 i 位乘客需要从地点 starti 前往 endi ,愿意支付 tipi 元的小费。
每一位 你选择接单的乘客 i ,你可以 盈利 e n d i − s t a r t i + t i p i end_i - start_i + tip_i endi−starti+tipi 元。你同时 最多 只能接一个订单。
给你 n 和 rides ,请你返回在最优接单方案下,你能盈利 最多 多少元。
注意:你可以在一个地点放下一位乘客,并在同一个地点接上另一位乘客。
示例
示例 1
输入:n = 5, rides = [[2,5,4],[1,5,1]]
输出:7
解释:我们可以接乘客 0 的订单,获得 5 - 2 + 4 = 7 元。
示例 2
输入:n = 20, rides = [[1,6,1],[3,10,2],[10,12,3],[11,12,2],[12,15,2],[13,18,1]]
输出:20
解释:我们可以接以下乘客的订单:
- 将乘客 1 从地点 3 送往地点 10 ,获得 10 - 3 + 2 = 9 元。
- 将乘客 2 从地点 10 送往地点 12 ,获得 12 - 10 + 3 = 5 元。
- 将乘客 5 从地点 13 送往地点 18 ,获得 18 - 13 + 1 = 6 元。
我们总共获得 9 + 5 + 6 = 20 元。
提示
1 <= n <= 105
1 <= rides.length <= 3 * 104
rides[i].length == 3
1 <= s t a r t i start_i starti < e n d i end_i endi <= n
1 <= t i p i tip_i tipi <= 105
题解
思路
我第一眼想到的就是动态规划,然后再仔细分析了一下题目,发现每一个 ride 的组成本质上就是 成本+价值,并且最终的目的就是在满足条件的情况下将 rides 装进“背包”(每个只能拿一次)以获得最大的总“价值”,所以直接往背包DP上想了。
在代码实现之前,我们可以先确定一下dp的状态转移方程。假设 dp[i] 为到达地点 i 时的最优决策值,那么最终我们要得到的结果就是 dp[n]。套用背包dp的模板,我们在遍历到第 j 个商品(即 ride)时,状态转移方程为
d
p
[
e
n
d
j
]
=
m
a
x
(
d
p
[
e
n
d
j
]
,
d
p
[
s
t
a
r
t
j
]
+
w
o
r
t
h
j
)
.
dp[end_j] = max(dp[end_j], dp[start_j]+worth_j).
dp[endj]=max(dp[endj],dp[startj]+worthj).
其中,
w
o
r
t
h
j
worth_j
worthj 依题意等于
e
n
d
j
−
s
t
a
r
t
j
+
t
i
p
j
end_j-start_j+tip_j
endj−startj+tipj。
于是我们只需要从地点 1 遍历到地点 n 不断地更新当前地点的最优解,最终的 dp[n] 就是我们所求的答案。那么,从状态转移方程中可以看出,当我们遍历到地点 i 时,我们需要考虑的只有 end==i 的 ride;如果没有对应的ride能够“装入背包”,说明当前没有需要做的决策,当前的最优解 dp[i] = dp[i-1]。
如此的话,与经典的背包dp问题不同的是,我们可以把 rides 数组根据 end 的大小进行排序,同时不断维护 rides 中 end 的最大值始终大于当前地点 i,这样我们只需要不断从排序后的 rides 数组的一端取出用于决策的 ride,而无需像经典背包dp一样每次决策都遍历所有“商品”。
代码实现
class Solution {
public:
long long maxTaxiEarnings(int n, vector<vector<int>>& rides) {
vector<long long> dp(n+1, 0);
sort(rides.begin(), rides.end(), [](const vector<int> &a, const vector<int> &b) {
return a[1] > b[1];
}); // 这里我选择按照 end 的值从大到小排序,这样每次可以只从数组尾部取出ride,节省数组整体移动的复杂度
for (int i=1; i<=n; i++) { // dp[i] 表示到达第 i 个站点时的最大收益
dp[i] = dp[i-1]; // 默认继承上一站点的最优解
while (!rides.empty() && rides.back()[1]==i) { // 满足 end==i 的 ride,将其取出,并加入最优解的决策中
long long start=rides.back()[0], end=rides.back()[1];
long long worth=end-start+rides.back()[2];
dp[i] = max(dp[i], dp[start]+worth);
rides.pop_back();
}
}
return dp[n]; // 终点站的最优解即最终答案
}
};