Day 18 —— 买卖股票的最佳时机

640?wx_fmt=png

? 温故而知新



? 今日份挑战

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]输出: 5解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]输出: 0解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。


先思考一下,后面我会给出一个解题思路~?


640?wx_fmt=other


这道题我们明确知道交易只可以进行一次,所以,我们只要找到两个卖出和买入点的最大差值即可,这里有一个简单的公式:

最大利润=max{前一天最大利润, 今天的价格 - 之前最低价格}

所以,我们可以定义一个变量保存利润,每次迭代天数(从第一天至最后一天),如果遇到更加大的价格差,就更新一下,同时,还有一些边界问题需要额外处理。


Python实现1:
# 买卖股票的最佳时机
def maxProfit(prices):
   if len(prices)<=1: return 0
   price_min = prices[0]
   profit_max = 0
   for price in prices:
       price_min = min(price_min, price)
       profit_max = max(profit_max, price-price_min) # 最大利润=max{前一天最大利润, 今天的价格 - 之前最低价格}
   return profit_max


这道题还有一个思路,是来自于东哥(公众号:labuladong)的一篇文章。

可以戳:团灭 LeetCode 股票买卖问题

下面呢,我根据自己的学习,整理了一下解题思路。

这里主要是用到了动态规划的思想,首先,先了解一下动态规划:

动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。

动态规划一般可分为线性动规,区域动规,树形动规,背包动规四类。

举例:

线性动规:拦截导弹,合唱队形,挖地雷,建学校,剑客决斗等;

区域动规:石子合并, 加分二叉树,统计单词个数,炮兵布阵等;

树形动规:贪吃的九头龙,二分查找树,聚会的欢乐,数字三角形等;

背包问题:01背包问题,完全背包问题,分组背包问题,二维背包,装箱问题,挤牛奶(同济ACM第1132题)等;

而我们今天的解题思路只是其中一类,算是给还没有入门DP的同学一个案例。

这里的DP,你可以理解是一个框架,一个套路,如果遇到类似的问题,就可以套这个框架来解决问题,所以,我先介绍一下这个框架,然后再用今天的算法题来应用一下这个框架。


1、状态枚举

什么是状态?状态在这里的意思就是条件,比如这道股票交易的题目,条件就包括了:交易天数n、最大交易次数K、股票持有状态(0代表未持有,1代表持有)。每种“状态”,都有对应的“选择”,这道题的“选择”有:买入、卖出、无操作,我们用 buy, sell, rest 表示这三种选择。上面的东西,我们可以用一个三维列表来表示:

dp[i][k][0 or 1]

0 <= i <= n-1, 1 <= k <= K

n 为天数,大 K 为最多交易数此问题共 n × K × 2 种状态,全部穷举就能搞定。

for 0 <= i < n:    

  for 1 <= k <= K:

       for s in {0, 1}:

           dpi[s] = max(buy, sell, rest)

而且,上面抽象的切片,我们也可以用自然语言来表达,比如:

dp[3][2][1],代表今天是第三天,我现在手上持有着股票,至今最多进行 2 次交易。

dp[2][3][0],代表今天是第二天,我现在手上没有持有股票,至今最多进行 3 次交易。


2、状态转移

这道题可以画一下状态转移图:

640?wx_fmt=jpeg

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])

当前的股票持有状态为0,因此它的前一种状态可以是:

  • 股票持有状态为0,选择rest

  • 股票持有状态为1,选择sell,因为sell了,所以要加上利润prices[i]

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

当前的股票持有状态为1,因此它的前一种状态可以是:

  • 股票持有状态为1,本次选择rest

  • 股票持有状态为0,本次选择buy,因为buy了,所以要减去成本prices[i],同时,交易次数要减去1

当然,还有一些特殊情况需要单独处理:

  • dp[-1][k][0] = 0

    解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。

  • dp[-1][k][1] = -infinity

    解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。

  • dp[i][0][0] = 0

    解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0

  • dp[i][0][1] = -infinity

    解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。

总结一下,状态转移方程如下:

dp[-1][k][0] = dp[i][0][0] = 0

dp[-1][k][1] = dp[i][0][1] = -infinity

状态转移方程:

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])

dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])

细心的同学可能会发现上面的结构其实有点“越界”问题,比如i=0时候,i-1其实是非法的,因此,我们需要对一些边界情况做一下处理。


对于这道题,交易次数为1,因此K=1,代入状态转移方程:

dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])

dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i])

因为k = 0,所以 dp[i-1][0][0] = 0,因此,

dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])

dp[i][1][1] = max(dp[i-1][1][1], -prices[i])

现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了,可以进行进一步化简去掉所有 k:

dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])

dp[i][1] = max(dp[i-1][1], -prices[i])


Python实现 02:
# 买卖股票的最佳时机
def maxProfit(prices):
   n = len(prices)
   # 生成一个空状态机
   dp = [[0]*2]*n
   for i in range(n):
       if i-1<0:
           dp[i][0] = 0
           dp[i][1] = -prices[i]
           '''
          dp[i][0] = max(dp[-1][0], dp[-1][1] + prices[i])
                    = max(0, -infinity + prices[i]) = 0
           
          dp[i][1] = max(dp[-1][1], dp[-1][0] - prices[i])
                    = max(-infinity, 0 - prices[i])
                    = -prices[i]
          '''
       else:
           dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
           dp[i][1] = max(dp[i-1][1], -prices[i])
   return dp[n-1][0]



? 配图角色背景介绍

原本是一名优秀的神经外科医生,因车祸导致其双手无法使用手术刀正常工作,之后来到喜马拉雅山上拜魔法师古一为师,成为强大的魔法师,看家法器是一件有自我意识的悬浮斗篷
性格高傲的神经外科手术专家史蒂芬·斯特兰奇(本尼迪克特·康伯巴奇饰)事业有成,在遭遇一次车祸悲剧后,双手再也无法握住手术刀,不能继续他的医生职业,为了治疗他的伤,他远赴尼泊尔遇到了莫度男爵(切瓦特·埃加福特饰),在莫度男爵带动下他得到古一法师(蒂尔达·斯文顿饰)帮助。斯蒂芬-斯特兰奇把自己曾经的自负都抛在了一边,开始接触和学习鲜为人知的玄学,以及多维空间世界的学问。在纽约的格林威治村,变身奇异博士的斯特兰奇,现实世界和多维空间的中间人,他利用超自然能力和神器来保护着世界,更要与力量强大党羽众多的卡西利亚斯(麦斯·米科尔森饰)一决高下,来拯救即将崩塌的多维世界。


基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究(Matlab代码实现)内容概要:本文围绕“基于可靠性评估序贯蒙特卡洛模拟法的配电网可靠性评估研究”,介绍了利用Matlab代码实现配电网可靠性的仿真分析方法。重点采用序贯蒙特卡洛模拟法对配电网进行长时间段的状态抽样与统计,通过模拟系统元件的故障与修复过程,评估配电网的关键可靠性指标,如系统停电频率、停电持续时间、负荷点可靠性等。该方法能够有效处理复杂网络结构与设备时序特性,提升评估精度,适用于含分布式电源、电动汽车等新型负荷接入的现代配电网。文中提供了完整的Matlab实现代码与案例分析,便于复现和扩展应用。; 适合人群:具备电力系统基础知识和Matlab编程能力的高校研究生、科研人员及电力行业技术人员,尤其适合从事配电网规划、运行与可靠性分析相关工作的人员; 使用场景及目标:①掌握序贯蒙特卡洛模拟法在电力系统可靠性评估中的基本原理与实现流程;②学习如何通过Matlab构建配电网仿真模型并进行状态转移模拟;③应用于含新能源接入的复杂配电网可靠性定量评估与优化设计; 阅读建议:建议结合文中提供的Matlab代码逐段调试运行,理解状态抽样、故障判断、修复逻辑及指标统计的具体实现方式,同时可扩展至不同网络结构或加入更多不确定性因素进行深化研究。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值