【每日一题】出租车的最大盈利

Tag

【递归】【记忆化搜索】【动态规划】【数组】【2023-12-08】


题目来源

2008. 出租车的最大盈利


解题思路

以下题解与思路参考 教你一步步思考动态规划:从记忆化搜索到递推(Python/Java/C++/Go/JS/Rust)

寻找子问题

假设 n = 9。我们要解决的问题是从 1 开车到 9 最多可以赚多钱。

会有以下两种情况出现:

  • 情况一:如果没有乘客在 9 下车,或者我们不搭载在 9 下车的乘客,那么问题就变成:从 1 开车到 8 最多可以赚多少钱;
  • 情况二:如果有至少一位乘客在 9 下车,我们可以枚举载哪位乘客。假设所载乘客在 5 上车,那么从 5 到 9 不能载其它乘客(题目要求同时最多只能接一个订单),问题变成:从 1 到 5 最多可以赚多少钱。

以上两种情况都会将原问题变成 「和原问题相似的、规模更小的子问题」,这意味着可以使用递归来解决问题。

方法一:递归

思路

递归函数定义为 dfs(i),表示从 1 开车到 i 最多可以赚多少钱。

情况一中问题变为:从 1 开车到 i-1 最多可以赚钱多少,有转移关系式:

d f s ( i ) = d f s ( i − 1 ) dfs(i) = dfs(i - 1) dfs(i)=dfs(i1)

在情况二中,我们可以枚举搭载哪位乘客,取其中最赚钱的方案,有转移关系式:

d f s ( i ) = m a x ( d f s ( s t a r t ) + i − s t a r t + t i p ) dfs(i) = max(dfs(start) + i - start + tip) dfs(i)=max(dfs(start)+istart+tip)
其中,start 表示到 i 的乘客的上车点,tip 为从 start 到 i 这一段路的小费。

以上两种情况取最大值得到 dfs(i),即

d f s ( i ) = m a x ( d f s ( i − 1 ) , m a x ( d f s ( s t a r t ) + i − s t a r t + t i p ) ) dfs(i) = max(dfs(i - 1), max(dfs(start) + i - start + tip)) dfs(i)=max(dfs(i1),max(dfs(start)+istart+tip))

递归边界:dfs(1) = 0,因为没有在 1 下车的乘客。

递归入口:dfs(n),也是最后需要返回的答案。

算法

在实现中,将 rides 数组按照下车点进行分组,方便枚举所有在 i 下车点下车的乘客。在分组中,使用 pair 记录每一个分组中的 startend - start + tip

class Solution {
public:
    long long maxTaxiEarnings(int n, vector<vector<int>>& rides) {
        vector<vector<pair<int, int>>> g(n+1);
        for (auto ride : rides) {
            int start = ride[0], end = ride[1], tip = ride[2];
            g[end].push_back(make_pair(start, end - start + tip));
        }

        function<long long(int)> dfs = [&](int i) -> long long {
            if (i == 0) {
                return 0;
            }
            long long res = dfs(i - 1);
            for (auto [s, t] : g[i]) {
                res = max(res, dfs(s) + t);
            }
            return res;
        };
        return dfs(n);
    }
};

该方法超时。

方法二:递归+记录数组=记忆化搜索

思路

由于在递归中存在某些 dfs(i) 重复计算的问题,因此可以使用一个数组 memo 记录计算结果,在递归中使用数据记录计算出的值的方法被称为「记忆化搜索」。

算法

class Solution {
public:
    long long maxTaxiEarnings(int n, vector<vector<int>>& rides) {
        vector<vector<pair<int, int>>> g(n+1);
        for (auto ride : rides) {
            int start = ride[0], end = ride[1], tip = ride[2];
            g[end].push_back(make_pair(start, end - start + tip));
        }

        vector<long long> memo(n+1, -1);    // -1 表示没有计算过
        function<long long(int)> dfs = [&](int i) -> long long {
            if (i == 0) {
                return 0;
            }
            auto& res = memo[i];    // 这里是引用,因为后面需要将更新后的 res 更新到 memo 中
            if (res != -1) {
                return res;
            }
            res = dfs(i - 1);
            for (auto [s, t] : g[i]) {
                res = max(res, dfs(s) + t);
            }
            return res;
        };
        return dfs(n);
    }
};

复杂度分析

时间复杂度: O ( n + m ) O(n+m) O(n+m)

空间复杂度: O ( n + m ) O(n+m) O(n+m)

方法三:动态规划(递推)

思路

将递归对应翻译为动态规划。

状态方程 f[i] 表示从 1 开车到 i 最多可以赚多少钱。

状态转移关系:

f [ i ] = m a x ( f [ i − 1 ] , m a x ( f [ s t a r t ] + i − e n d + t i p ) ) f[i] = max(f[i-1], max(f[start] + i - end + tip)) f[i]=max(f[i1],max(f[start]+iend+tip))

base case:f[1] = 0。

最后返回:return f[n]。

算法

class Solution {
public:
    long long maxTaxiEarnings(int n, vector<vector<int>>& rides) {
        vector<vector<pair<int, int>>> g(n+1);
        for (auto ride : rides) {
            int start = ride[0], end = ride[1], tip = ride[2];
            g[end].push_back(make_pair(start, end - start + tip));
        }

        vector<long long> f(n+1);    
        for (int i = 2; i <= n; ++i) {
            f[i] = f[i-1];
            for (auto [s, t] : g[i]) {
                f[i] = max(f[i], f[s] + t);
            }
        }
        return f[n];
    }
};

复杂度分析

时间复杂度: O ( n + m ) O(n+m) O(n+m),其中 m m mrides 的长度,n 是地点数目。动态规划转移需要 O ( n ) O(n) O(n) 的时间,查询乘客信息需要 O ( m ) O(m) O(m) 的时间。

空间复杂度: O ( n + m ) O(n+m) O(n+m)


写在最后

如果您发现文章有任何错误或者对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度的方法,欢迎评论区交流。

最后,感谢您的阅读,如果有所收获的话可以给我点一个 👍 哦。

  • 23
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wang_nn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值