预计周六笔者会整理一下01背包问题和完全背包问题的遍历顺序。后续还会有二叉树的遍历方式和回溯的遍历方式(其实二叉树和回溯的遍历都是一样的,回溯问题都可以变成一个二叉树的问题,来源于代码随想录)
如下的几个方法在代码随想录都会有更简洁的写法,这里主要考虑的是动态规划的思路。
买卖股票的最佳时机1
class Solution {
public int maxProfit(int[] prices) {
//dp[i][]数组表示第 i 天是否持有股票状态下,所拥有的最大现金数,
//dp[i][0]表示第 i 天持有股票时,所拥有的最大现金数
//dp[i][1]表示第 i 天不持有股票时,,所拥有的最大现金数
int[][] dp = new int[prices.length][2];
//初始化,第1天开始时现金数为0
//如果持有股票,那就是第一天(赊账)买的,所以现金为 0 - prices[0];
dp[0][0] = -prices[0];
//如果不买那就还是0
dp[0][1] = 0;
for(int i = 1; i < prices.length; i++){
//第i天持有股票有两种状态,可能是第 i 天买的股票,也可能是在前面就已经买了
//第i天买股票的话,之前的现金一直是0, 第i天(赊账)买,现金为 0 - prices[i]
//如果前面就已经买了,那就是跟上一天的状态一样,dp[i-1][0]
dp[i][0] = Math.max(-prices[i], dp[i-1][0]);
//第i天不持有股票也有两种状态,可能是第i天卖出去的,也可能是在前面就卖了
//第i天卖出时,前一天持有股票所拥有的现金数 + 第i天卖出去的股票现金数 就是所拥有的现金数
//前面就卖出去的话,那就是跟上一天的状态一样,dp[i-1][1]
dp[i][1] = Math.max(dp[i-1][0]+prices[i], dp[i-1][1]);
}
//最终返回第最后一天的状态,卖了还是没卖,取最大值即可
return Math.max(dp[prices.length-1][0], dp[prices.length-1][1]);
}
}
买卖股票的最佳时机2
class Solution {
public int maxProfit(int[] prices) {
//dp数组定义同上
int[][] dp = new int[prices.length][2];
//初始化第一天
dp[0][0] = -prices[0];
dp[0][1] = 0;
for(int i = 1; i < prices.length; i++){
//这里和买卖股票的最佳时机1不同的是,买卖多次
//那么当天买的状态就会有差别,因为买的时候,前一天不持有股票状态时肯定是有现金的,即dp[i-1][1]
//用第i-1天剩下的现金在第i天买价格为prices[i]的股票,现金剩余dp[i-1][1]-prices[i]
dp[i][0] = Math.max(dp[i-1][1]-prices[i], dp[i-1][0]);
dp[i][1] = Math.max(dp[i-1][0]+prices[i], dp[i-1][1]);
}
return Math.max(dp[prices.length-1][0], dp[prices.length-1][1]);
}
}
买卖股票的最佳时机3
代码随想录的代码会更简洁一些,这里只是笔者觉得较为清晰的思路。笔者这里是用数组的两维表示第几次买卖的状态,其实按照代码随想录,只用一维表示状态即可。(天数这个状态不可省略)
class Solution {
public int maxProfit(int[] prices) {
//这里稍微有点区别,dp[i][j][k]
//含义为,在第i天,当前交易为第k次交易时,j=0或者1表示是持有还是不持有
int[][][] dp = new int[prices.length][2][2];
//第一天初始化,你可以想象在第一天同时买卖两次
//那么第一次持有,买进就是0-prices[0]
//第一次卖出,就是当前剩余的钱0-prices[0] + 卖出的利润prices[0] = 0
//第二次同理
dp[0][0][0] = -prices[0];
dp[0][1][0] = 0;
dp[0][0][1] = -prices[0];
dp[0][1][1] = 0;
//第二天开始
for(int i = 1; i < prices.length; i++){
//第i天第一次买卖需要单独放出来,因为第i天第一次买的时候是赊账买的,剩余的钱为-prices[i],
//或者第i天前面已经第一次买入了,那就是dp[i-1][0][0]
dp[i][0][0] = Math.max(-prices[i], dp[i-1][0][0]);
//第i天不持有,那么就是两种情况,第i天卖出,或者是在第i天之前就卖出了
//第i天卖出,那么就是i-1天持有状态下(j=0)的金钱 + 第i天卖出的利润prices[i]
//第i天之前就卖出,那么第i天保持i-1天的不持有状态下(j=1)的金钱
dp[i][1][0] = Math.max(dp[i-1][0][0]+prices[i], dp[i-1][1][0]);
//注意!!!!!此处的dp[i-1][1][0]-prices[i]中的dp[i-1][1][0]
//表示的是前一天为状态[1][0]的现金,即第一次卖出,前一天不持有时的状态
//不要用前一天不持有的状态[1][1],因为有可能买入的前一天在第一次卖出的后面
//可以这么理解,第一次卖出了,第一次卖出的状态会持续到第二次买入,直接用第一次卖出剩余的钱减去第二次买入的钱即可
dp[i][0][1] = Math.max(dp[i-1][1][0]-prices[i], dp[i-1][0][1]);
dp[i][1][1] = Math.max(dp[i-1][0][1]+prices[i], dp[i-1][1][1]);
}
//最后最大利润有可能是第一次卖出,也有可能是第二次卖出,但是第二次卖出的利润包含了第一次卖出的利润
//因为你可以再最后一天完成第二次买入和第二次卖出(没有亏钱),相当于还是只做了第一次的买卖
// return Math.max(dp[prices.length-1][1][0], dp[prices.length-1][1][1]);
return dp[prices.length-1][1][1];
}
}
买卖股票的最佳时机4
class Solution {
public int maxProfit(int k, int[] prices) {
int[][][] dp = new int[prices.length][2][k];
//初始化见题目3
for(int j = 0; j < k; j++){
dp[0][0][j] = -prices[0];
}
for(int i = 1; i < prices.length; i++){
//题目3中说过了,第一次持有时,买入时剩余的金钱会跟后面不同,因为第一次买入前现金为0,后面是有现金的。因此需要分开
dp[i][0][0] = Math.max(-prices[i], dp[i-1][0][0]);
dp[i][1][0] = Math.max(dp[i-1][0][0]+prices[i], dp[i-1][1][0]);
//后面的k-1次买卖
for(int m = 1; m < k; m++){
dp[i][0][m] = Math.max(dp[i-1][1][m-1]-prices[i], dp[i-1][0][m]);
dp[i][1][m] = Math.max(dp[i-1][0][m]+prices[i], dp[i-1][1][m]);
}
}
//直接返回第i天,第k次买卖中,不持有股票(j = 1)状态下,的现金钱数
//至于为什么只返回第k次,而不是k次所有的现金钱数取最大值,题目3说过了。因为最后已经包含了前面的所有了
return dp[prices.length-1][1][k-1];
}
}