算法之股票交易问题
股票购买
交易一次
解法1:贪心算法
public int maxProfit(int prices[]) {
int minprice = Integer.MAX_VALUE;
int maxprofit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] < minprice) {
minprice = prices[i];
} else if (prices[i] - minprice > maxprofit) {
maxprofit = prices[i] - minprice;
}
}
return maxprofit;
} 解法2:动态规划
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
int len = prices.length;
//dp[i][0] -> 今日不持股的最大利润。 dp[i][1]今日持股的最大利润
//dp[i][0] 对于有两种情况。 1、昨日不持股,则利润为昨日不持股。 2、昨日持股,则今日卖掉了 dp[i][0] = dp[i - 1][0] + prices[i];
int[] dp = new int[2];
//初始化。最开始持股,则利润为 --prices[i]
dp[0] = 0;
dp[1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[0] = Math.max(dp[0], dp[1] + prices[i]);
dp[1] = Math.max(dp[1], -prices[i]);//注意dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);代表能进行多次的买卖。
}
return dp[0];//最终不持股。
} 不限交易次数
解法1: 贪心算法
//贪心算法。 以买入情况下,后一位元素小于当前元素/无后一位元素 卖出。
//以卖出情况下,后一位元素大于当前元素 买入。
public int maxProfit(int[] prices) {
int len = prices.length;
// 特殊判断
if (len < 2) {
return 0;
}
boolean state = false;//代表卖出状态 true 代表买入状态。
int profit = 0;
for (int i = 0; i < len; i++) {
if (!state && i + 1 < len && prices[i + 1] > prices[i]) {
profit -= prices[i];
state = true;
} else if (state && (i + 1 >= len || prices[i + 1] < prices[i])) {
profit += prices[i];
state = false;
}
}
return profit;
}
public int maxProfit(int[] prices) {
int len = prices.length;
// 特殊判断
if (len < 2) {
return 0;
}
int buy = prices[0];//最低购入价
int profit = 0;
for (int i = 1; i < len; i++) {
if (prices[i] < buy) {
buy = prices[i];
} else if (prices[i] > buy){
profit += prices[i] - buy;
buy = prices[i];//更新购入最低价。
}
}
return profit;
} 解法2:动态规划
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
int len = prices.length;
//dp[i][0] -> 今日不持股的最大利润。 dp[i][1]今日持股的最大利润
//dp[i][0] 对于有两种情况。 1、昨日不持股,则利润为昨日不持股。 2、昨日持股,则今日卖掉了 dp[i][0] = dp[i - 1][0] + prices[i];
int[] dp = new int[2];
//初始化。最开始持股,则利润为 --prices[i]
dp[0] = 0;
dp[1] = -prices[0];
for (int i = 1; i < len; i++) {
dp[0] = Math.max(dp[0], dp[1] + prices[i]);
dp[1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
}
return dp[0];//最终不持股。
} 交易存在冻结期(1天)- 309
冻结期为k天怎么办?
动态规划
sell[i]表示截至第i天,最后一个操作是卖时的最大收益;
buy[i]表示截至第i天,最后一个操作是买时的最大收益;
cool[i]表示截至第i天,最后一个操作是冷冻期时的最大收益;
递推公式:
sell[i] = max(buy[i-1]+prices[i], sell[i-1]) (第一项表示第i天卖出,第二项表示第i天冷冻)
buy[i] = max(cool[i-1]-prices[i], buy[i-1]) (第一项表示第i天买进,第二项表示第i天冷冻)
cool[i] = max(sell[i-1], cool[i-1])
public int maxProfit(int[] prices) {
int len = prices.length;
//特判
if (len < 2) {
return 0;
}
//buy[i]、sell[i]、cooled[i]表示第 i 天 最后一个操作 是买、卖、冻结能获得的最大利润
int[] buy = new int[len];
int[] sell = new int[len];
int[] cooled = new int[len];
buy[0] = -prices[0];
for (int i = 1; i < len; i++) {
cooled[i] = Math.max(cooled[i - 1], sell[i - 1]);//保持状态(无操作)cooled[i - 1] or 进入冻结;
sell[i] = Math.max(sell[i - 1], buy[i - 1] + prices[i]);//无操作,或昨日购入今日售出。
buy[i] = Math.max(buy[i - 1], cooled[i - 1] - prices[i]);//无操作 或 进入买入
}
return Math.max(cooled[len - 1], sell[len - 1]);
} 改进写法
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
//由于可以无限次交易,所以只定义两个维度,第一个维度是天数,第二个维度表示是否持有股票,0表示不持有,1表示持有,2表示过渡期
int[][] dp = new int[prices.length][3];
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][2] = 0;
for (int i = 1; i < prices.length; i++) {
//第i天不持有股票的情况有两种
//a.第i-1天也不持有股票
//b.第i-1天是过渡期
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][2]);
//第i天持有股票有两种情况
//a.第i-1天也持有股票,第i天不操作,
//b.第i-1天不持有股票,在第i天买入
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
//第i天是冷冻期只有一种情况,第i-1天持有股票且卖出
dp[i][2] = dp[i-1][1] + prices[i];
}
//最后最大利润为最后一天,不持有股票或者进入冷冻期的情况
return Math.max(dp[prices.length-1][0], dp[prices.length-1][2]);
} 交易存在手续费
解法1:贪心算法,购入时付手续费。
当前价格小于购入价格,则更新购入价格
当前价格大于购入价格,则利润更新, 更新购入价格(例如此时是i天卖出,则购入价格最低为 i天价格),注意更新的价格,防止递增数列。;
public int maxProfit(int[] prices, int fee) {
int len = prices.length;
// 特殊判断
if (len < 2) {
return 0;
}
int buy = prices[0] + fee;
int profit = 0;
for (int i = 1; i < len; i++) {
if (prices[i] + fee < buy) {//存在更小的购入价格。
buy = prices[i] + fee;
} else if (prices[i] > buy) {//有利润
profit += prices[i] - buy;
buy = prices[i];//更新购入价格,当前能购入的最低价格。 注意此处是 prices[i] 而不是prices[i] + fee, 因此若是递增,则会付多次手续费用。
}
}
return profit;
} 解法2:动态规划
public int maxProfit(int[] prices, int fee) {
int len = prices.length;
// 特殊判断
if (len < 2) {
return 0;
}
int[][][] dp = new int[len][2][2];
dp[0][0][0] = -prices[0];
dp[1][1][]
} 最多完成2次交易
动态规划:
注意初始条件:
其实第一天的dp[2]、dp[3]和dp[4]是无意义的,也不需要初始化。
真正需要初始化的是:
第二天的dp[2]:第二天及之后才能第一次卖
第三天的dp[3]:第三天及之后才能第二次买
第四天的dp[4]:第四天及之后才能第二次卖
此外在循环过程中需要进行天数的判断
dp[i][0] -> 无买入卖出
dp[i][1] -> 第一次买入
dp[i][2] -> 第一次买入
dp[i][3] -> 第二次买入
dp[i][4] -> 第二次买入 public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
int n = prices.length;
int[][] dp = new int[n][5];
dp[0][0] = 0;//第一天不动
// 第一天 第一次买
dp[0][1] = -prices[0];
// 第二天 第一次卖
dp[1][2] = dp[0][1] + prices[1];
// 第三天 第二次买
if (n > 2) {
dp[2][3] = dp[1][2] - prices[2];
}
// 第四天 第二次卖卖
if (n > 3) {
dp[3][4] = dp[2][3] + prices[3];
}
for (int i = 1; i < n; i++) {
// 初始状态其实都是0,写出来只是为了便于理解状态转换
dp[i][0] = dp[i - 1][0];
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
// 第二天及之后才能第一次卖,第二天作为初始情况已在循环外初始化,因此此处处理第二天之后
if (i > 1) {
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
}
dp[i][2] = i == 1 ? dp[0][1] + prices[i] : Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
// 第三天及之后才能第二次买,第三天作为初始情况已在循环外初始化,因此此处处理第三天之后
if (i > 2) {
dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
}
// 第四天及之后才能第二次卖,第四天作为初始情况已在循环外初始化,因此此处处理第四天之后
if (i > 3) {
dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
}
// 持有股票状态必不可能获得最大收益(只买不卖),所以不用考虑持有状态
return Math.max(Math.max(dp[n - 1][0], dp[n - 1][2]), dp[n - 1][4]);
} - 递归 + 记忆体
//state = 1代表买入状态。
//state = 0代表卖出状态。
Map<Key,Integer> map;
public int maxProfit(int[] prices) {
if(prices==null || prices.length==0) {
return 0;
}
int n = prices.length;
//用一个哈希表缓存重复的调用
map = new HashMap<Key,Integer>();
return dfs1(prices,0,0,0);
}
private int dfs1(int[] prices,int index,int status,int k) {
Key key = new Key(index,status,k);
//已存在,之间调用
if(map.containsKey(key)) {
return map.get(key);
}
//终止条件。
if(index==prices.length || k==2) {
return 0;
}
int still=0,sell=0,buy=0;
still = dfs1(prices,index+1,status,k);//无操作。
if(status==1) {
//卖出
sell = dfs1(prices,index+1,0,k+1)+prices[index];
}
else {
//买入
buy= dfs1(prices,index+1,1,k)-prices[index];
}
//获得三种操作的最大收益。
map.put(key, Math.max(Math.max(a,b),c) );
return map.get(key);
}
//Key对象封装了index、status、交易次数,作为map的key 或者将其序列化
private class Key {
final int index;
final int status;
final int k;
Key(int index,int status,int k) {
this.index = index;
this.status = status;
this.k = k;
}
//这里需要实现自定义的equals和hashCode函数
public int hashCode() {
return this.index * 3 + this.status * 5 + this.k * 7;
}
public boolean equals(Object obj) {
Key other = (Key)obj;
if(index==other.index && status==other.status && k==other.k) {
return true;
}
return false;
}
} 最多k次交易
- 当len / 2小于k,等同于无限次操作。
- 否则代表有限次操作 -> 递归 + 记忆体。

