NO.121 买卖股票的最佳时机 简单 、NO.122 买卖股票的最佳时机II 简单 、NO.123 买卖股票的最佳时机III 困难 、NO.188 买卖股票的最佳时机IV 困难
NO.121 买卖股票的最佳时机 简单
思路一:暴力法 没什么好说的,双重循环计算所有元素两两组合相减的结果,取最大。
public int maxProfit(int[] prices) {
int maxProfit=0;
for (int i = 0; i < prices.length - 1; i++) {
for (int j = i+1; j < prices.length; j++) {
maxProfit=Math.max(maxProfit,prices[j]-prices[i]);
}
}
return maxProfit;
}
时间复杂度:O(n^2)
思路二:优化暴力法到一次遍历 买卖股票从第二天开始我们每天都会"后悔":后悔没有在之前的最低点进行买入,只有这样我们的收益才会最大化。
由此可见,我们想要当天利益最大化,只需要在过去的某个最低点买入股票就好。所以我们只需要记录曾经出现过的最低点就好。
public int maxProfit(int[] prices) {
int minPoint=Integer.MAX_VALUE,maxProfit=0;
for (int i = 0; i < prices.length; i++) {
//记录曾出现过最低点
minPoint=Math.min(prices[i],minPoint);
//当日-曾经的最低
maxProfit=Math.max(maxProfit,prices[i]-minPoint);
}
return maxProfit;
}
时间复杂度:O(n)
从NO.121题不难看出:买股票的最佳时机是曾经!股市有风险,入股需谨慎!(狗头)
单纯的解答本题是比较简单的,但是买卖股票可以算作是一个系列的经典问题,在leetcode上就有本题一系列的变种问题:买卖股票的最佳时机、买卖股票的最佳时机II、买卖股票的最佳时机III、买卖股票的最佳时机IV、买卖股票的最佳时机含冷冻期、买卖股票的最佳时机含手续费。
虽然这些题有难有易,但是既然是一类问题,就有这一些通用的方法。
这六个问题都是由第四个问题简化变种而来的,第四题相较于本题多了一个参数k,限制只能进行k次交易;第一题也就是本题是只进行一次交易,相当于 k = 1;第二题是不限交易次数,相当于 k = +infinity(正无穷);第三题是只进行 2 次交易,相当于 k = 2;剩下两道也是不限次数,但是加了交易「冷冻期」和「手续费」的额外条件,其实就是第二题的变种。
NO.122 买卖股票的最佳时机II 简单
思路一:贪心算法 姊妹题NO.121,区别在于NO.121题只允许交易一次(一次买入、一次卖出),而本题没有交易次数的限制。
所以想到贪心算法,因为我们是"上帝视角"知道"未来股价",所以只要赚钱我们每天都进行交易。
所谓赚钱就是:“明天i+1股价”-“今天i股价”>0,这种情况今天就买入;否则就是赔钱或者不赚钱的情况,不进行交易。
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) return 0;
int Profit = 0;
for (int i = 0; i < prices.length - 1; i++) {
//今天i买,明天i+1赚钱。就今天买入,明天卖。
int sub = prices[i + 1] - prices[i];
if (sub > 0) {
Profit += sub;
}
}
return Profit;
}
时间复杂度:O(n)
思路二:动态规划 贪心算法已经很好地解决了这个问题,但是我们还是要带入一下DP解法,为后序的姊妹题NO.122买卖股票的最佳时机III、以及等等很多其他的XXX买卖股票问题都是基于本题进行变化的,无非是在本题的基础上增加更多的限制条件。
dp[i][j]的含义:i表示第i天,j表示持股状态(0表示不持股,1表示持股)。dp[i][j]整体就表示第i天持股/不持股时手里的钱。
初始化:dp[0][0]=0,刚开始不赔不赚;dp[0][1]= -prices[0],只支出了。
状态转移:dp[i][0]=Max(dp[i-1][0],dp[i-1][1]+prices[i])
,即第i天不持股时手里钱最多为MAX(i-1天不持股i天继续不持股,i-1天持股+i天以当日价格卖掉股票);
同理dp[i][1]=Max(dp[i-1][0]-prices[i],dp[i-1][1])
,即第i天持股时手里钱最多为MAX(i-1天不持股-i天以当日价格买入股票,i-1天持股i天继续持股)。
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
//初始化
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] -= prices[0];
//状态转移
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1]);
}
//最后一天一定是不持股的
return dp[prices.length - 1][0];
}
时间复杂度:O(n)
NO.123 买卖股票的最佳时机III 困难
思路一:动态规划 NO.121是只能买卖一次股票,NO.122是任意买卖股票,而本题是只能买卖股票两次。
相较于前两题的状态,本题又增加了一个交易次数的状态k。
dp[i][j][k]的含义:第i天的利润,j表示持股状态(0不持股,1持股),k交易次数(卖出次数0~2)。所以每天有六种状态,思路就是从每天的六种状态中找到利润最大的。
初始化:dp[0][0][0]=dp[0][0][1]=dp[0][0][2]=0
,啥也没干;dp[0][1][0]=dp[0][1][1]=dp[0][1][2]=-prices[0]
,第一天持股只有支出。
状态转移:dp[i][0][0]=dp[i-1][0][0]
,第i天不持股交易次数0,第i-1天一定未持股未交易,维持现状;
dp[i][0][1]=MAX(dp[i-1][0][1],dp[i-1][1][0]+prices[i])
,第i天不持股1次交易,维持第i-1天的状态,或者第i-1天持股未交易第i天卖出。
dp[i][0][2]=MAX(dp[i-1][0][2],dp[i-1][1][1]+prices[i])
,第i天不持股2次交易,维持第i-1天的状态,或者第i-1天持股1次交易第i天卖出。
dp[i][1][0]=MAX(dp[i-1][1][0],dp[i-1][0][0]-prices[i])
,第i天持股未交易,维持第i-1天的状态,或者第i-1天不持股未交易第i天买入。
dp[i][1][1]=MAX(dp[i-1][1][1],dp[i-1][0][1]-prices[i])
,第i天持股1次交易,维持第i-1天的状态,或者第i-1天不持股1次交易第i天买入。
dp[i][1][2]=dp[i][1][2]
,第i天持股2次交易,已经到达交易限制,但是手中还持股,不需要考虑一定不能利润最大化。
最终的结果一定是在不持股的三种状态中进行比较。
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) return 0;
//初始化
int[][][] dp = new int[prices.length][2][3];
dp[0][0][0] = dp[0][0][1] = dp[0][0][2] = 0;
dp[0][1][0] = dp[0][1][1] = dp[0][1][2] = -prices[0];
//状态转移
for (int i = 1; i < prices.length; i++) {
//六种状态
dp[i][0][0] = dp[i - 1][0][0];
dp[i][0][1] = Math.max(dp[i - 1][0][1], dp[i - 1][1][0] + prices[i]);
dp[i][0][2] = Math.max(dp[i - 1][0][2], dp[i - 1][1][1] + prices[i]);
dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][0][0] - prices[i]);
dp[i][1][1] = Math.max(dp[i - 1][1][1], dp[i - 1][0][1] - prices[i]);
dp[i][1][2] = dp[i - 1][1][2];
}
//最大利润一定是不持股的三种状态之一。
return Math.max(dp[prices.length - 1][0][2],
Math.max(dp[prices.length - 1][0][1], dp[prices.length - 1][0][0]));
}
时间复杂度:O(n) 空间O(n)虽然是三维数组,但2和3是常数。
NO.188 买卖股票的最佳时机IV 困难
思路一:动态规划 在上一题的基础上两次交易,变味了 k 次交易。但是需要考虑的状态,仍然是三个,第 i 天、j 持股状态、k 进行过的交易次数。
虽然 k 从上一题的常量 2 变为了本题的变量,但是 dp 数组的含义不变。但是本题如果使用三维数组会因为 k 值过大,导致内存超出。
所以采用两个一维数组 dpi0[k+1] 和 dpi1[k+1] 表示第 i 天持股(1)或不持股(0)时,不同交易次数下的利润。(上一题也可以采用这个方法,进行空间优化)
实现上,由于本题 k 不是上一题中的常量 2 ,无法手动写死每种情况,所以采用一个 for 循环即可。
public int maxProfit(int k, int[] prices) {
if (k<=0||prices.length<1)return 0;
//最多只能交易 prices.lenght/2 次
if (k>prices.length/2) k = prices.length/2;
//初始化
int[] dpi0 = new int[k+1];
int[] dpi1 = new int[k+1];
for (int j = 1; j < k + 1; j++) {
dpi1[j] = -prices[0];
}
//状态转移
for (int i = 1; i < prices.length; i++) {
for (int j = k; j >= 1; j--) {
//第i天不持股 = Max(第i-1天不持股,第i-1天持股+第i天出售)
dpi0[j] = Math.max(dpi0[j],dpi1[j]+prices[i]);
//第i天持股 = Max(第i-1天持股,第i-1天不持股-第i天买入)
dpi1[j] = Math.max(dpi1[j],dpi0[j-1]-prices[i]);
}
}
return dpi0[k];
}
时间复杂度:O(n*k) n 共n天
空间复杂度:O(k)
本人菜鸟,有错误请告知,感激不尽!