首页 > 其他分享 >【LeetCode 2008】出租车的最大盈利

【LeetCode 2008】出租车的最大盈利

时间:2024-05-06 23:33:20浏览次数:30  
标签:end int 行程 start 出租车 2008 rides LeetCode dp

题目描述

原题链接: LeetCode.2008 出租车的最大盈利

解题思路

  • 按二分查找和动态规划标签搜题库翻到的题目, 相当于揣着俩提示来做题了;
  • 总体来说可以从下车地点编号或者前i个行程这两种维度进行DP;
  • 思路一:
    • 将行程按照下车地点编号由小到大排序;
    • dp[i]定义为排序后在[0...i]行程中选择接单能获得的最大盈利值;
    • 不接行程i能得到的最大盈利值是dp[i-1], 选择接行程i这单能得到的最大盈利值是 dp[k] + 当前行程可盈利值, 其中k是[0, i-1]范围内满足下车地点不晚于当前行程上车地点的最后一个行程下标;
    • 即递推公式为 dp[i] = Math.max(dp[i-1], dp[k] + ride[1] - ride[0] + ride[2]);
    • 设行程个数为m, 时间复杂度是排序O(m\(log_2 m\))+DP过程中二分对应的也是O(m\(log_2 m\)), 空间复杂度O(m)。
  • 思路二:
    • 将行程按照下车地点编号由小到大排序;
    • dp[i]定义为到达地点i时能获得的最大盈利值, 但是由于行程即使有序下车地点仍然是离散不连续的, 所以再定义List idxList记录哪些地点已经处理过会有乘客下车的行程;
    • 遍历有序行程数据, 记当前行程上车地点为start, 下车地点为end, 可盈利值为profit, 考虑dp[end]有三种情况:
      1. 前面处理过在start位置下车的行程, 从该行程结束立刻接单, 能得到盈利值是 dp[start] + profit;
      2. dp[start]为初始值0则表示没有在start位置下车的前置行程, 要在idxList中通过二分查找到小于start的最大值idx中将 dp[idx] + profit 赋值给dp[end], 含义是在送完idx下车的乘客后一直空车到start位置再接当前这单所得到的的盈利值;
      3. 还有种情况是存在冲突的前置行程而且放弃当前行程就能得到更大盈利值, 这里就要最初就定义最大值变量记录所有已处理行程的最大盈利值。
    • 时间复杂度也是O(m\(log_2 m\)), 但是空间复杂度是O(n);
    • 思路解释起来比较绕(可能是表达能力有限)但是我个人感觉这种以地点编号维度进行递推是更容易想到的思路。虽然在昨天 LeetCode.1235 规划兼职工作 那题卡在时间不连续没有顺着思路写出正确代码, 但是今天这道类似问题能写出来就算是有进步了, 值得记录一下。
  • 思路三:
    • 写对数器自行验证上述两种代码都是正确解法后才发现性能并不能谈得上优秀, 翻了示例代码才发现这题并不需要二分;
    • 先用数组哈希将行程信息按照下车地点聚合起来, 数组元素是所有在该地点下车的行程信息所组成的一个链表, 这一步也是保证行程信息有序但是基于桶排序直接把排序的时间复杂度优化到O(m);
    • 仍然是以地点维度进行DP, dp[i]定义为到达地点i时能获得的最大盈利值;
    • 仍然定义变量max记录当前获得的最大盈利值, 从地点编号2开始处理dp数据, 只需要考虑两种情况:
      1. i位置没有需要乘客下车的行程, 空车到达i位置能获得的最大盈利是前面其它行程带来的, 即dp[i] = max;
      2. i位置有乘客下车的行程, 遍历链表上所有行程, max = Math.max(max, dp[r.start] + r.profit), 因为是连续遍历更新dp数组, 所以每个dp[start]都是正确的最大值, 简化了思路二中需要在idxList中二分查找的步骤;
    • 时间复杂度O(m + n), 空间复杂度也是O(m + n);
  • 证明了以地点维度进行DP确实是正确也可以是高效的, 选择哪种维度要看具体问题给出的数据规模;
  • 虽然自行写出了前面两种思路的代码, 但还是没能进一步想到最优的第三种思路, 学无止境要继续努力, 只能说学到就是赚到了, 这题值得记录。

解题代码

  • 排序后按照前i个行程进行DP:

      /**
       * 以行程个数为维度的DP+二分法, 但是性能不是最优的
       * 执行用时: 72 ms , 在所有 Java 提交中击败了 50.25% 的用户
       * 内存消耗: 70.94 MB , 在所有 Java 提交中击败了 7.39% 的用户
       */
      public long maxTaxiEarnings(int n, int[][] rides) {
          if (rides.length == 1) {
              return profit(rides[0]);
          }
          int cnt = rides.length;
          Arrays.sort(rides, (r1, r2) -> r1[1] - r2[1]);
          // dp[i]表示在[0..i]行程中选择接单能盈利的最大值
          long[] dp = new long[cnt];
          dp[0] = profit(rides[0]);
          for (int i = 1; i < cnt; i++) {
              int[] ride = rides[i];
              int start = ride[0];
              // 二分法找到满足rides[idx][1]<=start的最大idx值
              int left = 0, right = i - 1, idx = -1;
              while (left <= right) {
                  int mid = left + ((right - left) >> 1);
                  if (rides[mid][1] > start) {
                      right = mid - 1;
                  }
                  else {
                      idx = mid;
                      left = mid + 1;
                  }
              }
              dp[i] = Math.max(dp[i - 1], (idx == -1 ? 0 : dp[idx]) + profit(ride));
          }
          return dp[cnt - 1];
      }
    
      private int profit(int[] ride) {
          return ride[1] - ride[0] + ride[2];
      }
    
  • 排序后按照下车地点进行DP:

      /**
       * 以下车地点编号为维度的DP+二分, 性能不算好
       * 执行用时: 89 ms , 在所有 Java 提交中击败了 16.26% 的用户
       * 内存消耗: 63.29 MB , 在所有 Java 提交中击败了 87.69% 的用户
       */
      public long maxTaxiEarnings(int n, int[][] rides) {
          if (rides.length == 1) {
              return rides[0][1] - rides[0][0] + rides[0][2];
          }
          long[] dp = new long[n + 1];
          Arrays.sort(rides, (r1, r2) -> r1[1] - r2[1]);
          List<Integer> idxList = new ArrayList<>();
          int preEnd = 0;
          long max = 0;
          for (int[] ride : rides) {
              int end = ride[1], start = ride[0];
              /*
              * 接这单分为两种情况
              * 1、前面已经计算过以当前start为end的最大盈利值, 接单后的盈利值直接累加
              * 2、否则要找到距离当前start最近且已经计算过最大盈利的一个下车地点编号
              */
              int idx = dp[start] > 0 ? start : binarySearchIdx(idxList, start);
              dp[end] = Math.max(dp[end], dp[idx] + end - start + ride[2]);
              if (max > dp[end]) {// 存在某个与当前行程冲突但是下车位置能获得更大盈利的情况, 即不接这单能获得更大盈利
                  dp[end] = max;
              }
              else {
                  max = dp[end];
              }
              if (end != preEnd) {
                  idxList.add(end);
                  preEnd = end;
              }
          }
          return max;
      }
    
      /**
       * 在集合中二分查找不超过start的最大编号
       */
      private int binarySearchIdx(List<Integer> list, int start) {
          int left = 0, right = list.size() - 1, ans = 0;
          while (left <= right) {
              int mid = left + ((right - left) >> 1);
              if (list.get(mid) > start) {
                  right = mid - 1;
              }
              else {
                  ans = list.get(mid);
                  left = mid + 1;
              }
          }
          return ans;
      }
    
  • 哈希/桶排序后按照下车地点进行DP:

      /**
       * 从时间最优示例代码中看到的另一种基于哈希表的DP解法
       * 执行用时: 8 ms , 在所有 Java 提交中击败了 100.00% 的用户
       * 内存消耗: 65.93 MB , 在所有 Java 提交中击败了 67.49% 的用户
       */
      public long maxTaxiEarnings3(int n, int[][] rides) {
          if (rides.length == 1) {
              return rides[0][1] - rides[0][0] + rides[0][2];
          }
          // arr[i]是所有在i处下车的行程链表信息
          Ride[] arr = new Ride[n + 1];
          for (int[] r : rides) {
              int end = r[1];
              arr[end] = new Ride(r[0], end - r[0] + r[2], arr[end]);
          }
          long max = 0;
          long[] dp = new long[n + 1];
          for (int i = 2; i <= n; i++) {
              for (Ride r = arr[i]; r != null; r = r.next) {
                  max = Math.max(dp[r.start] + r.profit, max);
              }
              dp[i] = max;
          }
          return max;
      }
    
      private class Ride {
          int start;
          int profit;
          Ride next;
    
          public Ride(int start, int profit, Ride next) {
              this.start = start;
              this.profit = profit;
              this.next = next;
          }
      }
    

标签:end,int,行程,start,出租车,2008,rides,LeetCode,dp
From: https://www.cnblogs.com/coding-memory/p/18176214

相关文章

  • LeetCode 2597. The Number of Beautiful Subsets
    原题链接在这里:https://leetcode.com/problems/the-number-of-beautiful-subsets/description/题目:Youaregivenanarray nums ofpositiveintegersanda positive integer k.Asubsetof nums is beautiful ifitdoesnotcontaintwointegerswithanabsolut......
  • LeetCode 1373. Maximum Sum BST in Binary Tree
    原题链接在这里:https://leetcode.com/problems/maximum-sum-bst-in-binary-tree/description/题目:Givena binarytree root,return themaximumsumofallkeysof any sub-treewhichisalsoaBinarySearchTree(BST).AssumeaBSTisdefinedasfollows:Thel......
  • P3193 [HNOI2008] GT考试 题解
    之前学矩阵乘的时候做的题,当时因为不会\(kmp\)搜索一稀里糊涂过去了,现在填个坑。头图是\(Logos\)!P3193[HNOI2008]GT考试题链:洛谷题库题目大意:求有多少个长度为\(n\)的数字串的子串中不包含给出的长度为\(m\)位的串,范围\(n<=1e9\),$m<=20$。思路:首先考虑DP,令\(......
  • 【LeetCode 1235】规划兼职工作
    题目描述原题链接:LeetCode.1235规划兼职工作解题思路想到了按照结束时间排序后用动态规划来处理,但是又局限在了以结束时间为维度进行递推,又卡在了时间不连续无法高效计算到最晚结束时间范围内所有时间对应值这一问题上,看了题解才知道用排序后的兼职工作数量为维度去递推......
  • 106. 从中序与后序遍历序列构造二叉树(leetcode)
    https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/要点是明白中序和后序如何构造二叉树的,并且需要理清当前递归函数的语义,不要一开始就陷入细节,而是思考整棵树与其左右子树的关系,语义是:即构造当前节点,给当前节点左右子树赋值,明......
  • [leetcode 87 扰乱字符串] [剪枝搜索]
    importjava.util.HashMap;importjava.util.Map;classSolution{publicstaticvoidmain(String[]args){Solutionsolution=newSolution();booleanres=solution.isScramble("eebaacbcbcadaaedceaaacadccd","eadcaacabad......
  • leetcode算法热题--最长连续序列
    题目给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)的算法解决此问题。示例1:输入:nums=[100,4,200,1,3,2]输出:4解释:最长数字连续序列是[1,2,3,4]。它的长度为4。示例2:输入:nums=......
  • leetcode算法热题--最长连续序列
    题目给定一个未排序的整数数组nums,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。请你设计并实现时间复杂度为O(n)的算法解决此问题。示例1:输入:nums=[100,4,200,1,3,2]输出:4解释:最长数字连续序列是[1,2,3,4]。它的长度为4。示例2:输入:nums=......
  • leetcode算法热题--盛最多水的容器
    题目给定一个长度为n的整数数组height。有n条垂线,第i条线的两个端点是(i,0)和(i,height[i])。找出其中的两条线,使得它们与x轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。说明:你不能倾斜容器。示例1:输入:[1,8,6,2,5,4,8,3,7]输出:49解释......
  • leetcode算法热题-爬楼梯
    题目假设你正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或2个台阶。你有多少种不同的方法可以爬到楼顶呢?示例1:输入:n=2输出:2解释:有两种方法可以爬到楼顶。1阶+1阶2阶示例2:输入:n=3输出:3解释:有三种方法可以爬到楼顶。1阶+1阶+1阶1......