最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 跌妈不认?一口气团灭6道股票算法打打气

    正文概述 掘金(童欧巴)   2021-02-27   404

    观感度:?????

    口味:金汤肥牛

    烹饪时间:10min

    跌妈不认?一口气团灭6道股票算法打打气

    2021 年的基金市场开年至今,暴涨又暴跌。刚迎完财神,期待牛气冲天的年轻人们,刚刚入场就狠狠的吃了资本市场的一记重锤。

    各种“人类迷惑行为大赏”轮番上演,让本就魔幻的世界变得更加魔幻。如果你最近也跌了,请点个赞,让我们互相抱团取暖。

    回到 LeetCode 这 6 道股票系列题,其实这 6 道题目可以归为一道题目来看:

    1. 买卖股票的最佳时机
    2. 买卖股票的最佳时机II
    3. 买卖股票的最佳时机III
    4. 买卖股票的最佳时机 IV
    5. 最佳买卖股票时机含冷冻期
    6. 买卖股票的最佳时机含手续费

    与现实略有不同,题目中添加了一些限制条件,读完题分析后不难发现。

    1. 第一题只交易一次,也就是 k = 1。
    2. 第二题不限制交易次数,也就是 k = +infinity。
    3. 第三题只交易两次,也就是 k = 2。
    4. 第四道限制最多交易次数 k 是任意数。
    5. 第五道和第六道不限次数,相当于在第二题的基础上分别添加了交易冷冻期手续费的额外条件。

    我们每天能做的操作无非是以下这三种:

    1. 买入
    2. 卖出
    3. 无操作

    不过要注意以下四点限制条件。

    1. 要先买入才能卖出
    2. 题目要求不能同时参与多笔交易,也就是说再次买入前需要卖出手中持有的股票
    3. 无操作基于两种状态,手中持有股票没有持有股票
    4. 交易次数 k 也有限制,只有 k >= 0 时才可以买入

    分析好了这些状态,接下来就是翻译成代码了。

    首先,我们可以建立一个三维数组来表示上面的这些状态,先来明确一些变量含义。

    • i: 天数 范围是 (0 <= i <= n - 1)
    • k: 最大交易次数
    • 0: 没有持有股票
    • 1: 持有股票
    • n: 股票数组长度
    dp[i][k][0]
    dp[i][k][1]
    
    // 举个?
    dp[2][2][1] // 今天是第 2 天,手中持有股票,最多还可以进行 2 次交易
    

    我们最终要求的可获得的最大收益就是 dp[n - 1][k][0],代表最后一天将股票卖出后的最大收益。(这里卖出一定比持有收益更大,所以是 [0],而不是 [1])

    接下来,我们尝试列出状态转移方程。

    // 今天手中没有持有股票,有两种可能:
    // 1. 昨天没有持有,今天选择不操作。 对应: dp[i - 1][k][0]
    // 2. 昨天持有,今天卖出了,所以今天没有股票了。对应: dp[i - 1][k][1] + prices[i]
    dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
    
    
    // 今天手中持有股票,有两种可能:
    // 1. 昨天手中持有股票,今天选择不操作。对应: dp[i - 1][k][1]
    // 2. 昨天没有持有股票,今天买入了。对应: dp[i - 1][k - 1][0] - prices[i]
    dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])
    

    很显然,卖出股票利润增加,买入股票利润减少。因为每次交易包含两次成对的操作,买入和卖出。

    所以只有买入的时候需要将 k - 1,那么最大利润就是上面这两种可能性中的最大值。

    第一题 k = 1

    将状态转移方程套入本题的条件,k = 1,列出状态转移方程。

    dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i])
    dp[i][1][1] = Math.max(dp[i - 1][1][1], dp[i - 1][0][0] - prices[i])
                = Math.max(dp[i - 1][1][1], -prices[i])
    // k = 0 时,dp[i - 1][0][0] = 0
    

    观察发现 k 既然都是 1 且不会改变,也就是说 k 对状态转移已经没有影响了,我们可以进一步化简方程。

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

    对于第 0 天,我们需要进行初始化:

    • 不持有:dp[0][0] = 0
    • 持有:dp[0][1] = -prices[0] (花了 prices[0] 的钱买入股票)
    const maxProfit = function(prices) {
        let n = prices.length
        let dp = Array.from(new Array(n), () => new Array(2))
        dp[0][0] = 0
        dp[0][1] = -prices[0]
        for (let i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
            dp[i][1] = Math.max(dp[i - 1][1], -prices[i])
        }
        return dp[n - 1][0]
    }
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

    我们发现在转移的时候,dp[i] 只会从 dp[i - 1] 转移得来,因此第一维可以去掉,空间复杂度优化到 O(1)。

    const maxProfit = function(prices) {
        let n = prices.length
        let dp = Array.from(new Array(n), () => new Array(2))
        dp[0] = 0
        dp[1] = -prices[0]
        for (let i = 1; i < n; i++) {
            dp[0] = Math.max(dp[0], dp[1] + prices[i])
            dp[1] = Math.max(dp[1], -prices[i])
        }
        return dp[0]
    }
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

    我们也可以将变量名变得更加友好一些。

    • profit_out:卖出时的利润
    • profit_in:买入时的利润
    const maxProfit = function(prices) {
        let n = prices.length
        let profit_out = 0
        let profit_in = -prices[0]
        for (let i = 1; i < n; i++) {
            profit_out = Math.max(profit_out, profit_in + prices[i])
            profit_in = Math.max(profit_in,  -prices[i])
        }
        return profit_out
    }
    
    • 时间复杂度:O(n)
    • 空间复杂度:O(1)

    第二题 k = +infinity

    将状态转移方程套入本题的条件,k = +infinity。

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

    我们发现数组中的 k 同样已经不会改变了,也就是说 k 对状态转移已经没有影响了,可以进一步化简方程。

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

    对于第 0 天,我们要给出初始值:

    • 不持有:dp[0][0] = 0
    • 持有:dp[0][1] = -prices[0] (花了 prices[0] 的钱买入股票)
    const maxProfit = function(prices) {
        let n = prices.length
        let dp = Array.from(new Array(n), () => new Array(2))
        dp[0][0] = 0 
        dp[0][1] = -prices[0]
        for (let i = 1; i < n; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i])
        }
        return dp[n - 1][0]
    }
    

    同样在转移的时候,dp[i] 只会从 dp[i - 1] 转移得来,因此第一维可以去掉,空间复杂度优化到 O(1)。

    const maxProfit = function(prices) {
        let n = prices.length
        let dp = Array.from(new Array(n), () => new Array(2))
        dp[0] = 0
        dp[1] = -prices[0]
        for (let i = 1; i < n; i++) {
            let tmp = dp[0] // 中间变量可省略,因为当天买入卖出不影响结果
            dp[0] = Math.max(dp[0], dp[1] + prices[i])
            dp[1] = Math.max(dp[1], tmp - prices[i])
        }
        return dp[0]
    }
    

    同上题一样,我们可以将变量名变得更加友好一些。

    const maxProfit = function(prices) {
        let n = prices.length
        let profit_out = 0
        let profit_in = -prices[0]
        for (let i = 1; i < n; i++) {
            profit_out = Math.max(profit_out, profit_in + prices[i])
            profit_in = Math.max(profit_in, profit_out - prices[i])
        }
        return profit_out
    }
    

    第三题 k = 2

    前面两种情况,无论是 k = 1,还是 k = +infinity 的情况下,k 对状态转移方程是没有影响的。

    不过当 k = 2 时,k 就对状态转移方程有影响了。列出状态转移方程:

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

    这个时候 k 无法化简,我们需要使用两次循环对 k 进行穷举。

    for (let i = 0; i < n; i++) {
        for (let k = maxK; k >= 1; k--) {
            dp[i][k][0] = Math.max(dp[i - 1][k][0], dp[i - 1][k][1] + prices[i])
            dp[i][k][1] = Math.max(dp[i - 1][k][1], dp[i - 1][k - 1][0] - prices[i])
        }
    }
    

    不过因为 k 的取值范围比较小,我们也可以直接将它们全部列举出来。

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

    有了上面两道题的铺垫,我们后面几道题就直接写出降维后的解法。

    const maxProfit = function(prices) {
        let n = prices.length
        let dp_i10 = 0
        let dp_i11 = -prices[0]
        let dp_i20 = 0
        let dp_i21 = -prices[0]
        for (let i = 1; i < n; i++) {
            dp_i20 = Math.max(dp_i20, dp_i21 + prices[i])
            dp_i21 = Math.max(dp_i21, dp_i10 - prices[i])
            dp_i10 = Math.max(dp_i10, dp_i11 + prices[i])
            dp_i11 = Math.max(dp_i11, -prices[i])
        }
        return dp_i20
    }
    

    同上面一样,我们可以将变量名变得更加友好一些。

    const maxProfit = function(prices) {
        let profit_1_in = -prices[0], profit_1_out = 0
        let profit_2_in = -prices[0], profit_2_out = 0
        let n = prices.length
        for (let i = 1; i < n; i++) {
            profit_2_out = Math.max(profit_2_out, profit_2_in + prices[i])
            profit_2_in = Math.max(profit_2_in, profit_1_out - prices[i])
            profit_1_out = Math.max(profit_1_out, profit_1_in + prices[i])
            profit_1_in = Math.max(profit_1_in, -prices[i])
        }
        return profit_2_out
    }
    

    第四题 k 是任意数

    一个有收益的交易至少需要两天(在前一天买入,在后一天卖出,前提是买入价格低于卖出价格)。

    如果股票价格数组的长度为 n,则有收益的交易的数量最多为 n / 2(整数除法)。因此 k 的临界值是 n / 2。

    如果给定的 k 不小于临界值,即 k >= n / 2,则可以将 k 扩展为正无穷,也就是第二题的情况,如下函数 maxProfit2。

    const maxProfit = function(k, prices) {
        let n = prices.length
        const maxProfit2 = function(prices) {
            let profit_out = 0
            let profit_in = -prices[0]
            for (let i = 1; i < n; i++) {
                profit_out = Math.max(profit_out, profit_in + prices[i])
                profit_in = Math.max(profit_in, profit_out - prices[i])
            }
            return profit_out
        }
        if (k > n / 2) {
            return maxProfit2(prices)
        }
        let profit = new Array(k)
        // 初始化买入卖出时的利润,将每次交易买入、卖出时的利润放在一个对象中,实现降维
        for (let j = 0; j <= k; j++) {
            profit[j] = {
                profit_in: -prices[0],
                profit_out: 0
            }
        }
        for (let i = 0; i < n; i++) {
            for (let j = 1; j <= k; j++) {
                profit[j] = {
                    profit_out: Math.max(profit[j].profit_out, profit[j].profit_in + prices[i]), 
                    profit_in: Math.max(profit[j].profit_in, profit[j-1].profit_out - prices[i])
                }
            }
        }
        return profit[k].profit_out
    }
    

    第五题 k 为正无穷但有冷却时间

    每次卖出之后都要等一天才能继续交易,也就是第 i 天选择买的时候,要从 i - 2 状态转移。

    列出状态转移方程。

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

    k 同样对状态转移已经没有影响了,可以进一步化简方程。

    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i])
    
    const maxProfit = function(prices) {
        let n = prices.length
        let dp_i0 = 0
        let dp_i1 = -prices[0];
        let dp_pre = 0 // 代表 dp[i-2][0]
        for (let i = 0; i < n; i++) {
            let tmp = dp_i0
            dp_i0 = Math.max(dp_i0, dp_i1 + prices[i])
            dp_i1 = Math.max(dp_i1, dp_pre - prices[i])
            dp_pre = tmp
        }
        return dp_i0
    }
    

    同上面一样,我们可以将变量名变得更加友好一些。

    const maxProfit = function(prices) {
        let n = prices.length
        let profit_in = -prices[0]
        let profit_out = 0
        let profit_freeze = 0
        for (let i = 1; i < n; i++) {
            let temp = profit_out
            profit_out = Math.max(profit_out, profit_in + prices[i])
            profit_in = Math.max(profit_in, profit_freeze - prices[i])
            profit_freeze = temp
        }
        return profit_out
    }
    

    第六题 k 为正无穷但有手续费

    在第二题的基础上,添加了手续费。

    每次交易要支付手续费,只要把手续费从利润中减去即可,可以列出如下两种方程。

    第一种方程:在每次买入股票时扣除手续费

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

    第二种方程:在每次卖出股票时扣除手续费

    dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee)
    dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
    
    const maxProfit = function(prices, fee) {
        let n = prices.length
        let dp = Array.from(new Array(n), () => new Array(2))
        dp[0] = 0
        dp[1] = -prices[0]
        for (let i = 1; i < n; i++) {
            let tmp = dp[0]
            dp[0] = Math.max(dp[0], dp[1] + prices[i] - fee)
            dp[1] = Math.max(dp[1], tmp - prices[i])
        }
        return dp[0]
    }
    

    同上面一样,我们可以将变量名变得更加友好一些。

    const maxProfit = function(prices, fee) {
        let profit_out = 0
        let profit_in = -prices[0]
        for (let i = 1; i < prices.length; i++) {
            profit_out = Math.max(profit_out, profit_in + prices[i] - fee)
            profit_in = Math.max(profit_in, profit_out - prices[i])
        }
        return profit_out
    }
    

    团灭完股票系列算法再来个首尾呼应,讲一讲所谓的投资时钟。

    经济分为两个大周期:经济复苏期经济衰退期。结合通胀和流动性的组合,可以分为四个小周期,衰退前期、衰退后期、复苏前期以及复苏后期

    搞清楚了当下位于哪个周期,调整资产进行合理的配置,才能不做韭菜。

    站在巨人的肩膀上

    • 股票问题系列通解(转载翻译)
    • 简单dp,秒懂股票买卖
    • 买卖股票最佳时机6道题解

    2021 组团刷题计划

    • 前端食堂的 LeetCode 题解仓库

    年初立了一个 flag,上面这个仓库在 2021 年写满 100 道前端面试高频题解,目前进度已经完成了 50%

    如果你也准备刷或者正在刷 LeetCode,不妨加入前端食堂,一起并肩作战,刷个痛快。

    ❤️爱心三连击

    1.如果你觉得食堂酒菜还合胃口,就点个赞支持下吧,你的是我最大的动力。

    2.关注公众号前端食堂,吃好每一顿饭!

    3.点赞、评论、转发 === 催更!

    跌妈不认?一口气团灭6道股票算法打打气


    起源地下载网 » 跌妈不认?一口气团灭6道股票算法打打气

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元