LeetCode 2008. 出租车的最大盈利(背包DP)

题目描述

  你驾驶出租车行驶在一条有 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 endistarti+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 endjstartj+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];  // 终点站的最优解即最终答案
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值