服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

彻底理解动态规划:赚最多钱的兼职

日期: 来源:码农的荒岛求生收集编辑:码农的荒岛求生

大家好,我是小风哥,休息了将近一周后终于满血复活了,关于阳康的故事下篇再聊,今天主讲技术。

这是动态规划主题的第二篇,本文的题目是赚最多钱的兼职。

假设你是搞钱小能手,搬砖之余周末还想去兼职,现在有n份工作,每份工作的起始时间保存在数组startTime中、结束时间保存在数组endTime中、能获取的报酬保存在数组profit中,那么你该怎样挑选在时间上不冲突的减重工作从而获取最多的报酬,返回该报酬。

注意,在这里数组startTime已经按照从小到大的顺序排好序。

假定现在有5份工作,startTime = {1,2,3,4,6},endTime = {3,5,10,6,9},profit = {20,20,100,70,60},如图所示:

那么你应该挑选1、4和5这三份工作,其时间不冲突且能获得最多的报酬,其值为150。

想一想该怎样解决问题。


子问题与选择

和上一个题目一样,你首先应该找出子问题是什么,子问题与原始问题的依赖关系是什么。

找出子问题的关键在于每一步的选择。

我们首先考虑第一份工作,此时你有两种选择,接受和不接受。

如果接受第一份工作,那么这就意味着你不能再接受第二份工作,因为这两份工作在时间上是冲突的,此时问题就变为了在剩下的第3份工作中进行挑选从而确保获取最多的报酬,注意,该子问题的本质和原始问题一样。

如果不接受第一份工作,那么接下来的问题就变为了从剩下的4份工作中进行挑选从而确保获取最多的报酬,注意,该子问题的本质同样和原始问题一样。

现在我们找到了两个子问题,那么原始问题的解与子问题的解有什么关系呢?

很简单,原始问题的解无非就是这两个子问题解中较大的那个:

从这张图中你可以看到:

原始问题的解 = max(20 + 子问题1的解, 子问题2的解)

现在你应该能看出原始问题与子问题之间的关联了,实际上这张图状态空间树还可以继续画下去,但由于该树过大因此我们仅从上图中的第一种选择继续,那么其状态空间树为:

当所有的工作都选择完毕时就到达叶子节点,此时我们可以计算出从根节点到当前叶子节点整条路径上的选择能得到的报酬总和,从上图可以看到最多的报酬是150。

现在你应该清楚的知道该怎样我们是怎样一步步将问题不断的分解为更小的子问题,然后利用子问题的解来得到原始问题的解了。


自顶向下递归代码

上图中每个方框都是一个子问题,其决定因素在于当前需要对哪个工作进行选择,假设当前我们要对第i个工作进行选择,因此我们可以对问题进行定义:

int jobScheduling(int i);

该函数的含义是从第i个到最后一个工作中进行选择所能获取的最多报酬是多少,基于上述讨论以及状态空间树你可以很容易的写出这样的递归代码:

vector<int> startTime;
vector<int> endTime;
vector<int> profit;

int jobScheduling(int i) {
 // 递归出口:此时没有工作可选,因此可获得的报酬是0
 if (i == startTime.size()) return 0;

 // 第一种选择,接受当前的工作
 int next;
 bool find = false;
 int resa = 0;
 // 找到下一个与当前工作时间不冲突的工作
 for (next = i + 1; next < startTime.size(); next++) {
  if (endTime[i] <= startTime[next]) {
   find = true;
   break;
  }
 }
 resa = find ? jobScheduling(next) + profit[i] : profit[i];

    // 第二种选择,不接受当前的工作
 int resb = jobScheduling(i + 1);

 return max(resa, resb) ;
}

注意看该递归函数的结果仅仅由一个参数决定,那就是参数i,而i的取值范围为[0, startTime.size()],也就是说最多只有startTime.size() + 1个子问题,而上述递归代码存在大量重复计算问题,这点从上述状态空间树也能看出来:

图中标注的这两个子问题其实是完全一样的,但会被上述递归代码重复求解。

基于此我们可以增加cache进行优化:

int jobScheduling(int i) {
 if (i == startTime.size()) return 0;

  // 如果当前子问题之前解决过则直接返回
 if (cache[i]) return cache[i];

 int next;
 bool find = false;
 int resa = 0;
 for (next = i + 1; next < startTime.size(); next++) {
  if (endTime[i] <= startTime[next]) {
   find = true;
   break;
  }
 }
 resa = find ? jobScheduling(next) + profit[i] : profit[i];

 int resb = jobScheduling(i + 1);

  // 记录下当前问题的解
 return cache[i] = max(resa, resb) ;
}

现在每个子问题只需要被求解一次,接下来我们着手将上述自定向下的递归代码转为自底向上的非递归代码。


自底向上动态规划

注意看该递归函数,其决定因素只有参数i,参数i的所有可能的情况只有startTime.size() + 1个,因此我们可以定一个startTime.size() + 1大小的一维数组dp:

vector<int> dp(startTime_.size() + 1, 0);

接下来我们要求解最小子问题,最小子问题就是上述递归代码的递归出口:

if (i == startTime.size()) return 0;

也就是说dp[startTime.size()]应该等于0,而这已经包含在了数组初始化代码中了。

接下来我们利用for循环手动构造出所有参数i的可能,将上述递归代码中非递归出口部分置于该for循环中,最终我们到了完整的动态规划代码:

int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {
 vector<int>dp(startTime_.size() + 1, 0);
 for (int i = startTime.size() - 1; i >= 0; i--) {
     int next;
     bool find = false;
     int resa = 0;
     for (next = i + 1; next < startTime.size(); next++) {
      if (endTime[i] <= startTime[next]) {
       find = true;
       break;
      }
     }
     resa = find ? dp[next] + profit[i] : profit[i];

     int resb = dp[i + 1];
     dp[i] = max(resa, resb);
 }
 return dp[0];
}
最后,我建了微信技术群,扫描下方二维码备注写“加群”二字即可,一起见证我们的成长。

相关阅读

  • 彻底理解动态规划:编辑距离

  • 大家好,我是小风哥。这是动态规划主题的第三篇,本篇的题目非常经典,几乎是面试必备,即,编辑距离问题,edit distance;给定两个字符串word1以及word2,返回将word1转为word2需要的最少
  • 学会夸人

  • 我有个口头禅,熟悉的读者应该都知道,就是「很棒」。1.以前带团队,每次交互同学给过来一个方案,我看都没看就会先说:“为什么这么棒?!”看过后如果真的好,我会接着夸:“怎么能这么优秀
  • 作品集为心仪工作而做准备-私教指导

  • 我是张增顺,07年就开始做设计,在行业中已经十余年,从自我提升的前提下,从专业能力,通用能力以外利用自己的时间进行一些自己的影响力,通过自己多年的经验和沉淀为大家答疑解惑,帮助
  • 福利已到 | 抽本本

  • 2023福利“年初不放血 一年全白忙” 去年公司的生意较为惨淡,一年到头客户没增加几个不说,到年底了眼看着友商业绩夺冠,某司集体新冠......为求今年转个运,且在粉丝的呼声中,在
  • 行稳致远 | 节奏

  • 编者按看准三年翻一倍,也不能上来先深套买入当下就能涨,也不能电梯来回坐不光要深入地研究,基金经理还得理解市场的节奏才能有稳健的曲线。本文推送的心法就是至今还没羊过的张
  • 预防新冠感染重症发生!浙江印发最新通知

  • 日前,省卫生健康委办公室印发通知,要求进一步做好重点人群健康随访工作,预防和减少新冠病毒感染重症发生。>>>随访对象随访对象主要包括辖区65岁及以上老年人,重点针对前期新冠

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • 彻底理解动态规划:赚最多钱的兼职

  • 大家好,我是小风哥,休息了将近一周后终于满血复活了,关于阳康的故事下篇再聊,今天主讲技术。这是动态规划主题的第二篇,本文的题目是赚最多钱的兼职。假设你是搞钱小能手,搬砖之余
  • 阳了!我的实验失败了!

  • 大家好,我是小风哥!就在一周前管控放开后意识到可能感染只是时间问题,我开始有意做一个实验,要在自己能做的最极致的情况下看能坚持到什么时候。先说一下我的通勤方式—地铁,我的
  • 彻底理解动态规划:编辑距离

  • 大家好,我是小风哥。这是动态规划主题的第三篇,本篇的题目非常经典,几乎是面试必备,即,编辑距离问题,edit distance;给定两个字符串word1以及word2,返回将word1转为word2需要的最少
  • 为什么计算机需要操作系统?

  • 大家好,我是小风哥,提前祝大家新年快乐,这是年前的最后一篇技术文啦。今天我们从三个方面来简单聊聊为什么计算机系统操作系统这个话题。资源分配器如果你的CPU上只需要运行一
  • 系统调用与函数调用有什么区别?

  • 大家新年好,我是小风哥,这是今年的第一篇技术文,我们来聊聊系统调用与普通的函数调用之间的区别。作为程序员你肯定写过无数的函数,假设有这样两个函数:void funcB() {}void func
  • 深入理解Linux系统调用

  • 大家好,我是小风哥。在前两篇文章《为什么计算机需要操作系统》《系统调用与函数调用有什么区别》中我们了解了什么是系统调用、为什么需要系统调用、系统调用与函数调用有什