We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
上面这 6 道题目可以归为一道题目来看,与现实略有不同,题目中添加了一些限制条件,读完题分析后不难发现。
冷冻期
手续费
我们每天能做的操作无非是以下这三种:
不过要注意以下四点限制条件。
要先买入才能卖出
再次买入前需要卖出手中持有的股票
手中持有股票
没有持有股票
只有 k >= 0 时才可以买入
分析好了这些状态,接下来就是翻译成代码了。
首先,我们可以建立一个三维数组来表示上面的这些状态,先来明确一些变量含义。
dp[i][k][0] dp[i][k][1] // 举个🌰 dp[2][2][1] // 今天是第 2 天,手中持有股票,最多还可以进行 2 次交易
我们最终要求的可获得的最大收益就是 dp[n - 1][k][0],代表最后一天将股票卖出后的最大收益。(这里卖出一定比持有收益更大,所以是 [0],而不是 [1])
dp[n - 1][k][0]
接下来,我们尝试列出状态转移方程。
// 今天手中没有持有股票,有两种可能: // 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)。
dp[i]
dp[i - 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 天,我们要给出初始值:
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 }
The text was updated successfully, but these errors were encountered:
No branches or pull requests
上面这 6 道题目可以归为一道题目来看,与现实略有不同,题目中添加了一些限制条件,读完题分析后不难发现。
冷冻期
和手续费
的额外条件。我们每天能做的操作无非是以下这三种:
不过要注意以下四点限制条件。
要先买入才能卖出
。再次买入前需要卖出手中持有的股票
。手中持有股票
和没有持有股票
。只有 k >= 0 时才可以买入
。分析好了这些状态,接下来就是翻译成代码了。
首先,我们可以建立一个三维数组来表示上面的这些状态,先来明确一些变量含义。
我们最终要求的可获得的最大收益就是
dp[n - 1][k][0]
,代表最后一天将股票卖出后的最大收益。(这里卖出一定比持有收益更大,所以是 [0],而不是 [1])接下来,我们尝试列出状态转移方程。
很显然,卖出股票利润增加,买入股票利润减少。因为每次交易包含两次成对的操作,买入和卖出。
所以只有买入的时候需要将 k - 1,那么最大利润就是上面这两种可能性中的最大值。
第一题 k = 1
将状态转移方程套入本题的条件,k = 1,列出状态转移方程。
观察发现 k 既然都是 1 且不会改变,也就是说 k 对状态转移已经没有影响了,我们可以进一步化简方程。
对于第 0 天,我们需要进行初始化:
dp[0][0] = 0
dp[0][1] = -prices[0] (花了 prices[0] 的钱买入股票)
我们发现在转移的时候,
dp[i]
只会从dp[i - 1]
转移得来,因此第一维可以去掉,空间复杂度优化到 O(1)。我们也可以将变量名变得更加友好一些。
第二题 k = +infinity
将状态转移方程套入本题的条件,k = +infinity。
我们发现数组中的 k 同样已经不会改变了,也就是说 k 对状态转移已经没有影响了,可以进一步化简方程。
对于第 0 天,我们要给出初始值:
dp[0][0] = 0
dp[0][1] = -prices[0] (花了 prices[0] 的钱买入股票)
同样在转移的时候,dp[i] 只会从 dp[i - 1] 转移得来,因此第一维可以去掉,空间复杂度优化到 O(1)。
同上题一样,我们可以将变量名变得更加友好一些。
第三题 k = 2
前面两种情况,无论是 k = 1,还是 k = +infinity 的情况下,k 对状态转移方程是没有影响的。
不过当 k = 2 时,k 就对状态转移方程有影响了。列出状态转移方程:
这个时候 k 无法化简,我们需要使用两次循环对 k 进行穷举。
不过因为 k 的取值范围比较小,我们也可以直接将它们全部列举出来。
有了上面两道题的铺垫,我们后面几道题就直接写出降维后的解法。
同上面一样,我们可以将变量名变得更加友好一些。
第四题 k 是任意数
一个有收益的交易至少需要两天(在前一天买入,在后一天卖出,前提是买入价格低于卖出价格)。
如果股票价格数组的长度为 n,则有收益的交易的数量最多为 n / 2(整数除法)。因此 k 的临界值是 n / 2。
如果给定的 k 不小于临界值,即 k >= n / 2,则可以将 k 扩展为正无穷,也就是第二题的情况,如下函数 maxProfit2。
第五题 k 为正无穷但有冷却时间
每次卖出之后都要等一天才能继续交易,也就是第 i 天选择买的时候,要从 i - 2 状态转移。
列出状态转移方程。
k 同样对状态转移已经没有影响了,可以进一步化简方程。
同上面一样,我们可以将变量名变得更加友好一些。
第六题 k 为正无穷但有手续费
在第二题的基础上,添加了手续费。
每次交易要支付手续费,只要把手续费从利润中减去即可,可以列出如下两种方程。
第一种方程:在每次买入股票时扣除手续费
第二种方程:在每次卖出股票时扣除手续费
同上面一样,我们可以将变量名变得更加友好一些。
站在巨人的肩膀上
The text was updated successfully, but these errors were encountered: