观感度:🌟🌟🌟🌟🌟javascript
口味:虎皮凤爪前端
烹饪时间:10minjava
我欲清仓归去,又恐迅速反弹,踏空不胜寒。与其储蓄负利,不如厮混其间,少追涨,勿杀跌,夜安眠,不该有恨,获利总在无心间。月有阴晴圆缺,股有横盘涨跌,此事股难全。 -- 著名古人白交易git
本文已收录在前端食堂同名仓库 Github github.com/Geekhyt,欢迎光临食堂,若是以为酒菜还算可口,赏个 Star 对食堂老板来讲是莫大的鼓励。github
2021 年的基金市场开年至今,暴涨又暴跌。刚迎完财神,期待牛气冲天的年轻人们,刚刚入场就狠狠的吃了资本市场的一记重锤。面试
各类“人类迷惑行为大赏”轮番上演,让本就魔幻的世界变得更加魔幻。若是你最近也跌了,请点个赞,让咱们互相抱团取暖。
算法
回到 LeetCode 这 6 道股票系列题,其实这 6 道题目能够归为一道题目来看:数组
与现实略有不一样,题目中添加了一些限制条件,读完题分析后不难发现。函数
冷冻期
和手续费
的额外条件。咱们天天能作的操做无非是如下这三种:优化
不过要注意如下四点限制条件。
分析好了这些状态,接下来就是翻译成代码了。
首先,咱们能够创建一个三维数组来表示上面的这些状态,先来明确一些变量含义。
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,列出状态转移方程。
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] }
咱们发如今转移的时候,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] }
咱们也能够将变量名变得更加友好一些。
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 }
将状态转移方程套入本题的条件,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 = 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 }
一个有收益的交易至少须要两天(在前一天买入,在后一天卖出,前提是买入价格低于卖出价格)。
若是股票价格数组的长度为 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 }
每次卖出以后都要等一天才能继续交易,也就是第 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 }
在第二题的基础上,添加了手续费。
每次交易要支付手续费,只要把手续费从利润中减去便可,能够列出以下两种方程。
第一种方程:在每次买入股票时扣除手续费
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 }
团灭完股票系列算法再来个首尾呼应,讲一讲所谓的投资时钟。
经济分为两个大周期:经济复苏期
和经济衰退期
。结合通胀和流动性的组合,能够分为四个小周期,衰退前期、衰退后期、复苏前期以及复苏后期
。
不一样的经济周期对应着不一样的资产和市场风格。任何的资产都有周期性,没有只涨不跌的资产,即使是茅台这样的核心消费资产在不合适的周期里也能平均回调 30% 以上,即便钢铁这种夕阳产业在合适的周期也能涨个 50%。
搞清楚了当下位于哪一个周期,调整资产进行合理的配置,才能不作韭菜。
年初立了一个 flag,上面这个仓库在 2021 年写满 100 道前端面试高频题解,目前进度已经完成了 50%。
若是你也准备刷或者正在刷 LeetCode,不妨加入前端食堂,一块儿并肩做战,刷个痛快。
1.若是你以为食堂酒菜还合胃口,就点个赞支持下吧,你的赞是我最大的动力。
2.关注公众号前端食堂,吃好每一顿饭!
3.点赞、评论、转发 === 催更!