最近有个猎头突然给我推荐一份工作,面试流程是先在网上做几道测试题。我突然发现这类网上测试有一个规律,如果面试的是外企那么通常在HakerRank上做题,例如亚马逊,如果面试的是国内企业,例如华为等,通常在牛客网上做题,无论是哪种情况,考察的题型几乎都遵守同样的规律,通常而言面试时长是90分钟,编程题目大概3道作用,前两道通常考察循环,数组,字符串,栈,队列,第三道最难,同时它在95%以上的情况属于动态规划。

前两道虽然不难,但是题目会给你准备一些特殊的测试用例,因此你必须花不少时间修改代码才能通过所有用例,等到你做到第3题时该概率只有30分钟左右了。如果你不能在十分钟内洞穿第三道题的解法,那你基本不可能完成它。我想把面试算法题中的动态规划作为一个专门课题,通过一系列文章详细剖析其结题套路,原本计划在完成手头几个课题后再处理,但想到过完年后可能有很多同学要换工作,因此必然会面临网上做算法题的挑战,所以提前将这个课题推出来,希望对大家换工作有帮助,如果你因此而赢得心仪的工作,记得苟富贵,莫相忘。

动态规划的算法题虽然难度较大,但却有固定的套路可循,我们通过训练掌握其解题技巧后,在十分钟内找到其解法并不难,同时动态规划难在找到思路,有了解法后编码量往往很小。我们先看我最近在HankerRank做面试题是遇到的一个情况,它的题目如下:

一个投资者想把自己的积蓄投资到股市。他有一系列股票可以选择,但是对任何一只股票它只能买一手,购买股票的前不能超过它的积蓄总额。他有一个股神朋友能非常准确的预测股票当前的价格和一年后的价格,假设预测是完全正确的情况下,请你找出股票的最佳购买方案,使得投资者的收益最大化。例如投资者当前积蓄为saving=250,当前股票价格为current_value=[175, 133, 109, 210, 97],股票一年后价格为future_value=[200, 125, 128, 228, 133], 于是最佳投资方法为购买下标为2和4的两只股票,所得收益为 (128-109) + (133 - 97) = 55,请你设计算法,在给定saving, current_value, future_value的情况下计算最优收益。

我们先看最简单的处理方法那就是遍历所有可能的组合,对于下标为i的股票而言,某个组合要不包含它,要不不包含它,因此暴力遍历的话,对于n只股票而言,时间复杂度是(2^n),使用暴力遍历法是绝对过不了关,因为它有时间限制,例如我遇到的这道题时间限制为10秒,使用暴力查找所耗费的时间远远超过10秒。

动态规划问题的结题套路一个核心是使用空间换时间,因此遇到这类问题时我们首先要想到构造一张表来存储中间数据;第二个套路是,将问题分解成多个小问题,各个击破后再把小问题的结果结合起来得到当前问题的答案;第3个套路是,在进入递归时,先查表看看是否在当前给定条件下是否已经有了答案,如果有了答案就立即返回,注意这一步是加快速度的关键。

根据上面套路,我们分析一下题目,首先思考如何将问题分解成规模更小但是本质一样的子问题。我们从头开始遍历每只股票,假设当前遍历到第i只股票,在获取最佳收益时针对它只有两种选择,一种是购买它,一种是不购买它,于是问题分解成两种情况,如果购买它,那么将储蓄额减去当前股票价格,然后要用剩余的储蓄在第i只股票之后的股票中中获取最大利润,如果买它,那么我们必须要在当前储蓄额的基础从,从第i只股票之后的股票中找到最大例如。

如果我们用selectStock(saving, index, current_value, future_value)表示储蓄额为saving的情况下,从下标为index开始的股票中所获得的最大收益,于是针对第i只股票,在购买了它的情况下,所能获得的收益就是 future_value[i] - current_value[i] + selectStock(saving - current_value[i], i + 1, current_value, future_value)。
如果不购买第i只股票,那么所得最大收益就是selectStock(saving, i+1, current_value, future_value),于是我们计算出两个值后,选取最大那个就是针对第i只股票的最好决策。这里的思路针对的是套路2,我们看看套路1,3怎么实现。

我们要使用一个表,用来记录在储蓄额为saving,并且当前可选择股票的下标为i时所能获得的最优例如,于是我们构造一个哈希表profit_map = {}, 键值是一个tuple:(saving, sotck_index),在计算selectStock时,我们先根据saving 和 i 在profit_map中查找,看看给定情况下是否已经有结果,如果有了结果里面返回,如果没有,那么计算当前条件下的最佳收益后,将对应结果存储到表里,于是代码实现如下:

start_time = time.time()
profit_map = {}
stock_selected = []def  selectStock(saving, index, currentValue, futureValue):if saving <= 0: #储蓄额为0,无法进行投资因此收益为0return 0if index >= len(currentValue): #所有股票都选取完毕,直接返回return 0#查询当前情况下是否已经处理过if (saving, index) in profit_map:return profit_map[(saving, index)]#分别计算购买或者放弃当前股票的最大收益profit_for_buying = 0if saving >= currentValue[i]:#计算购买当前股票后所可能获得的最大收益profit_for_buying = futureValue[index] - currentValue[index] + selectStock(saving - currentValue[index], index + 1, currentValue, futureValue)#计算放弃当前股票后所能获得最大收益profit_for_ignore = selectStock(saving, index + 1, currentValue, futureValue)#选择收益最大的一种可能,并标记当前股票是否被选择if  profit_for_buying > profit_for_ignore:profit_map[(saving, index)] = profit_for_buyingstock_selected[index] = 1else:profit_map[(saving, index+1)] = profit_for_ignorestock_selected[index] = 0return max(profit_for_buying, profit_for_ignore)current_value = []
future_value = []
saving = 0
#测试数据从文件读取,相关文件从给定github下载
with open("input009.txt") as f:saving = int(f.readline())element_count = int(f.readline())  #current_value数组元素个数for i in range(element_count):current_value.append(int(f.readline()))f.readline() #future_value数组元素个数,它与current_value一样,所以忽略掉for i in range(element_count):future_value.append(int(f.readline()))#则是简单测试用例,反注释后可以运行得到前面说明的结果
#saving = 250
#current_value = [175, 133, 109,  210, 97]
#future_value = [200, 125, 128, 228, 133]print("saving: ", saving)
print("current_value: ", current_value)
print("future_value: ", future_value)
print("len: ", len(current_value))for i in range(len(current_value)):stock_selected.append(0)max_profit = selectStock(saving, 0, current_value, future_value)
print("--- running time is %s seconds ---" % (time.time() - start_time))
print("max profit is: ",max_profit)total_profit = 0
for i in range(len(stock_selected)):if stock_selected[i] == 1:total_profit += future_value[i] - current_value[i]print("select stock: {}, with profit: {}".format(i, future_value[i] - current_value[i]))

上面代码运行后所得结果如下:

saving:  8099
current_value:  [199, 193, 201, 172, 159, 106, 42, 70, 118, 209, 202, 108, 189, 162, 283, 5, 123, 43, 127, 128, 105, 90, 91, 225, 192, 37, 251, 77, 195, 64, 7, 289, 24, 59, 84, 110, 48, 88, 248, 174, 131, 258, 244, 58, 50, 169, 217, 160, 41, 95, 283, 200, 149, 249, 106, 116, 174, 47, 159, 21, 119, 105, 42, 56]
future_value:  [231, 234, 7, 253, 35, 289, 27, 288, 278, 170, 238, 72, 146, 129, 254, 245, 16, 177, 272, 267, 12, 116, 289, 62, 25, 247, 138, 272, 28, 280, 297, 263, 51, 262, 38, 229, 146, 204, 285, 1, 269, 57, 290, 296, 293, 294, 297, 299, 264, 191, 266, 299, 277, 26, 269, 297, 94, 248, 46, 104, 288, 17, 33, 279]
len:  64
--- running time is 0.6094491481781006 seconds ---
max profit is:  5719
select stock: 0, with profit: 32
select stock: 1, with profit: 41
select stock: 3, with profit: 81
select stock: 5, with profit: 183
select stock: 7, with profit: 218
select stock: 8, with profit: 160
select stock: 10, with profit: 36
select stock: 15, with profit: 240
select stock: 17, with profit: 134
select stock: 18, with profit: 145
select stock: 19, with profit: 139
select stock: 21, with profit: 26
select stock: 22, with profit: 198
select stock: 25, with profit: 210
select stock: 27, with profit: 195
select stock: 29, with profit: 216
select stock: 30, with profit: 290
select stock: 32, with profit: 27
select stock: 33, with profit: 203
select stock: 35, with profit: 119
select stock: 36, with profit: 98
select stock: 37, with profit: 116
select stock: 38, with profit: 37
select stock: 40, with profit: 138
select stock: 42, with profit: 46
select stock: 43, with profit: 238
select stock: 44, with profit: 243
select stock: 45, with profit: 125
select stock: 46, with profit: 80
select stock: 47, with profit: 139
select stock: 48, with profit: 223
select stock: 49, with profit: 96
select stock: 51, with profit: 99
select stock: 52, with profit: 128
select stock: 54, with profit: 163
select stock: 55, with profit: 181
select stock: 57, with profit: 201
select stock: 59, with profit: 83
select stock: 60, with profit: 169
select stock: 63, with profit: 223

输入的数组有65个数据,运行时间在0.65秒左右,如果使用穷举法遍历所有可能情况的话,所需时间很可能要个把小时,HankRank上这道题的时间上线是10秒,如果不使用动态规划方法的话就很难达到要求。

我们分析一下算法复杂度,可以看到如果profit_map填入的key为(saving, index),其中index取值范围就是输入数组的个数,因此profit_map最会存储的个数为 saving* n,于是算法的时间复杂度为O(saving * n), 空间复杂度也是O(saving * n).

后续我会继续推出大厂算法面试中的动态规划问题,希望这个系列能给大家带来真正的实惠。代码和数据文件的下载地址在这里:
https://github.com/wycl16514/interview_coding_dynamic_pogramming_stock_investment.git

破解大厂最难算法面试题:动态规划之股票买卖收益最大化相关推荐

  1. 一周刷爆LeetCode,算法da神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解 笔记

    一周刷爆LeetCode,算法大神左神(左程云)耗时100天打造算法与数据结构基础到高级全家桶教程,直击BTAJ等一线大厂必问算法面试题真题详解 笔记 教程与代码地址 P1 出圈了!讲课之外我们来聊聊 ...

  2. cstring判断包含字符串_作为java程序员要知道的大厂常见的算法面试题:字符串的包含...

    1.2 字符串的包含 题目描述 给定一长字符串a和一短字符串b.请问,如何最快地判断出短字符串b中的所有字符是否都在长字符串a中?请编写函数bool StringContain(string & ...

  3. 每日算法面试题,大厂特训二十八天——第十四天(数组)

    本文来源于算法面试题特训专栏,这里有大量专业性的算法题比如(动态规划21天,大厂特训28天等等) 欢迎大家一起学习. 链接:传送门 目录标题 导读 算法特训二十八天 面试题 点击直接资料领取 导读 肥 ...

  4. 每日算法面试题,大厂特训二十八天——第十五天(字符串)

    本文来源于算法面试题特训专栏,这里有大量专业性的算法题比如(动态规划21天,大厂特训28天等等) 欢迎大家一起学习. 链接:传送门 目录标题 导读 算法特训二十八天 面试题 点击直接资料领取 导读 肥 ...

  5. prim算法_历时两月,终拿字节跳动offer,算法面试题分享「带答案」

    欢迎关注专栏<Java架构筑基>--专注于Java技术的研究与分享! Java架构筑基​zhuanlan.zhihu.com Java架构筑基--专注于Java技术的研究与分享! 后续文章 ...

  6. 2021最新汇总数据结构与算法面试题(准备面试的可以看看)

    闲来无事整理了一下数据结构与算法面试题,准备面试的可以看看. 这份面试题汇总除了Java语言的,我还整理了C++和Golang语言的,无论你是做什么开发这份数据结构与算法面试题汇总都可以看,如果你是做 ...

  7. java面笔试_java笔试手写算法面试题大全含答案

    java笔试手写算法面试题大全含答案 1.统计一篇英文文章单词个数. public class WordCounting { public static void main(String[] args ...

  8. 66 道前端算法面试题附思路分析助你查漏补缺

    大家好,我是漫步. 今天来分享一篇干货,前端关于算法的分析不多,下文列举了66道前端算法面试题,希望对你有所帮助. 作者:Eno_Yao https://segmentfault.com/a/1190 ...

  9. 微软的100道算法面试题(终结版)

    前言 数据结构与算法的重要性已不言而喻,最近,我整理出十大经典排序算法.五大常用算法总结,今天特意整理出微软面试的100题,若有不足之处,欢迎指正!由于篇幅过长,前30道题目写在上一篇,大家可以进我的 ...

最新文章

  1. 时隔6年,“三巨头”再度联手,发布万字长文:深度学习将通往何方?
  2. POJ 2566 Bound Found
  3. ubuntu部署postgreSQL数据库
  4. python消息推送_Python阿里云消息推送调用API
  5. Apollo应用接入
  6. perl统计http日志ip
  7. C语言怎么处理多位数,急~~~~~~C语言问题~~怎么把这个计算程序改成能计算多位整数? 爱问知识人...
  8. arraylist有顺序吗_美团Java研发岗二面:什么叫线程安全?servlet是线程安全吗?...
  9. 【考研词汇训练营】Day 6 —— eventually,state,create,productivity,stimulate
  10. 计算机c盘用户爆满,Win7系统C盘突然快满了怎么办?C盘空闲空间爆满
  11. 为什么计算机休眠风扇还转,Win10电脑睡眠但风扇还在转怎么办
  12. 22 个最常用的 Python 包
  13. 《Rework》读书笔记
  14. uc浏览器的navigator.userAgent
  15. Java写计算器自闭了
  16. Mybatis采用分页插件实现分页
  17. 苹果手机QQ扫正常二维码404
  18. Postgresql及mongodb下载
  19. kalibr使用笔记
  20. k44.第十七章 K8s运维篇-DevOps 持续集成 持续部署 (四)

热门文章

  1. linux如何查进程、杀进程,重启进程
  2. springboot+jsp高校科研项目管理系统java
  3. 深圳P2P:有了银行存管也不一定安全
  4. c语言访问dph 单片机,(c语言版)单片机复习题(15页)-原创力文档
  5. spark day06 + day07 + day08
  6. ps怎么制作流体_PS滤镜制作风化流体人物特效图片
  7. 对口高考C语言2012年真题,2012年湖南省对口高考C语言试题(含答案).doc
  8. 陌陌2018年第四季度营收同比增长50% 开盘大涨10.9%
  9. 江哥带你玩转C语言 | 04-C语言常量和变量
  10. 成长经历html代码,我的成长经历日记模板200字