给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。来自leetcode121,链接
思路1:暴力法需要两轮遍历,外层遍历买入,内层遍历卖出,因此时间复杂度O(N^2)。思路2,只需要一层遍历,同时更新最小值以及利润最大值。因此需要两个变量保存最小买入价格和最大利润。时间复杂度降为O(N);
public int maxProfit1(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int minBuy=Integer.MAX_VALUE;
int maxSell=0;
for(int i=0;i<prices.length;i++){
if(prices[i]<minBuy){//如果当前价格小于最小价格
minBuy=prices[i];
}
else{//当前价格大于最小价格
int get=prices[i]-minBuy;
maxSell=Math.max(maxSell,get);
}
}
return maxSell;
}
思路2、动态规划,如果说买卖股票这一系列题目有一个通用解法的话,那就是动态规划。
dp[i][j]表示第i天之后的最大收益(如果买入还没有卖出,那么收益就是负数),如果j=0,那么表示i天之后是持有股票的,如果j=1,那么表示i天之后是持有现金的(不持有股票的)。因此,可以总结出状态转移方程如下:
dp[i][0]=dp[i-1][0]+0-price[i]
dp[i][1]=dp[i-1][1]+dp[i-1][0]+price[i]
注意:这里注意第一行的0-price[i],因为只能买卖一次,如果第i天是持有股票的,那么要么dp[i-1][0]即前一天就持有股票,要么之前没有买卖过,今天第一次买入股票。只有这两种情况。
因此,代码如下:
public int maxProfit(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int[][] dp=new int[prices.length][2];
dp[0][0]=-prices[0];//初始化
dp[0][1]=0;
for(int i=1;i<prices.length;i++){
dp[i][0]=Math.max(dp[i-1][0],-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
}
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。leetcode122,链接
思路1:相比前面一道题,这里的变化就是可以买卖多次股票,求最大利润是多少。这里使用一种“峰谷”的思想(也不容易想,但是想到了就挺简单)。
这里peak就是峰,valley就是谷,如上图。也就是说最大利润等于所有相邻的“峰”-相邻的“谷”的累加。如果我们试图跳过其中一个峰值来获取更多利润,那么我们最终将失去其中一笔交易中获得的利润,从而导致总利润的降低。
例如,在上述情况下,如果我们跳过 peaki 和 valleyj 试图通过考虑差异较大的点以获取更多的利润,获得的净利润总是会小与包含它们而获得的净利润,因为 C 总是小于 A+B。因此我们的任务就是找到所有相邻的“峰谷”。
public int maxProfit1(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int peak=0,valley=0;
int i=0;
int res=0;
while(i<prices.length-1){
//首先找到谷
while(i<prices.length-1&&prices[i]>=prices[i+1])
i++;
valley=prices[i];
//然后找到峰
while(i<prices.length-1&&prices[i]<=prices[i+1])
i++;
peak=prices[i];
res+=peak-valley;
}
return res;
}
时间复杂度O(N).
思路2:就是刚才前面讲的通用解法,dp[i][j]表示第i天之后的最大收益(如果买入还没有卖出,那么收益就是负数),如果j=0,那么表示i天之后是持有股票的,如果j=1,那么表示i天之后是持有现金的(不持有股票的)。因此,可以总结出状态转移方程如下:
dp[i][0]=Math.max( dp[i-1][0],dp[i-1][1]-price[i] )
即:前一天要么已经持有股票了,今天依然持有,或者前一天卖出股票,今天买入股票,两者之间收益较大值
dp[i][1]=Math.max( dp[i-1][1],dp[i-1][0]+price[i] )
即:前一天和今天都不持有股票,或者前一天持有今天卖出的最大收益。
public int maxProfit2(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int[][] dp=new int[prices.length][2];
//初始化
dp[0][0]=-prices[0];
dp[0][1]= 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][1],dp[i-1][0]+prices[i]);
}
return dp[prices.length-1][1];
}
时间复杂度:O(N),这里 N 表示股价数组的长度。空间复杂度O(N).
空间优化,因为这个表实际上是n行2列的,每一行只和上一行有关,因此只需要一维dp,或者说2个变量即可。因此将空间复杂度降为o(1)。
public int maxProfit(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int have=-prices[0];//一开始持有股票、相当于上面dp的第一列
int no=0;//一开始不持有股票,相当于上面dp的第二列
int preHave=have;//用于保存
int preNo=no;
for(int i=1;i<prices.length;i++){
have=Math.max(preHave,preNo-prices[i]);
no=Math.max(preNo,preHave+prices[i]);
preHave=have;
preNo=no;
}
return no;
}
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
思路:这道题目和前面的区别就是:限制只能完成2笔交易。因此将原来的二维dp变成三维dp,里面增加一个k表示交易的次数
dp[i][k][0]=Math.max(dp[i-1][k-1][1]-price[i],dp[i-1][k][0])
dp[i][k][1]=Math.max( dp[i-1][k][1],dp[i-1][k][0]+price[i] )
还是和前面的一样,第三位的1表示这一天之后是持有股票。
如果第三位是0,表示这一天之后不持有股票。
然后因为多了一维,所以初始化的代码也多了一些:
public int maxProfit(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int[][][] dp=new int[prices.length][3][2];//第一个2表示两次交易,第二个2表示两种状态,持有股票或者不持有股票
dp[0][0][1]=0;//初始化
dp[0][0][0]=Integer.MIN_VALUE;//这种情况不可能,没交易但是持有股票
dp[0][1][0]=-prices[0];//交易一次,持有股票
dp[0][1][1]=0;//交易一次,不持有股票
dp[0][2][0]=-prices[0];//交易两次,持有股票
dp[0][2][1]=0;//交易两次,不持有股票
for(int i=1;i<prices.length;i++){
// dp[i][0][1]=0;//按道理应该写,但是写和没写答案是一样的
// dp[i][0][0]=Integer.MIN_VALUE;
for(int j=1;j<=2;j++){
dp[i][j][0]=Math.max( dp[i-1][j-1][1]-prices[i],dp[i-1][j][0]);
dp[i][j][1]=Math.max( dp[i-1][j][1],dp[i-1][j][0]+prices[i] );
}
}
return dp[prices.length-1][2][1];
时间复杂度O(N*M),N表示天数,M表示限制的交易次数,这里为2。
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。leetcode188,链接
思路:这道题目的要求更进一步了,题目的变化是,买卖的次数是作为参数指定的k,而不是之前确定的。但是使用动态规划依然可以非常套路的解出来。
依然像上面一样初始化第一天的所有情况,然后双重循环更新所有dp元素。但是需要注意的是,如果k>price.length/2,其实就是随便交易几次的情况(因为price.length/2是最多的交易次数),因此可以重用前面不限制交易次数(也就是前面的系列题目2)的代码。
public int maxProfit(int k, int[] prices) {
if(prices==null||prices.length<1)
return 0;
int[][][] dp=new int[prices.length][k+1][2];//第一个2表示两次交易,第二个2表示两种状态,持有股票或者不持有股票
if(k>prices.length/2){
return maxProfit1(prices);//不限制交易次数的代码
}
for(int i=0;i<=k;i++){//初始化第一天的数据
dp[0][i][0]=-prices[0];
dp[0][i][1]=0;
}
for(int i=1;i<prices.length;i++){
// dp[i][0][1]=0;
// dp[i][0][0]=Integer.MIN_VALUE;
for(int j=1;j<=k;j++){
dp[i][j][0]=Math.max( dp[i-1][j-1][1]-prices[i],dp[i-1][j][0]);
dp[i][j][1]=Math.max( dp[i-1][j][1],dp[i-1][j][0]+prices[i] );
}
}
return dp[prices.length-1][k][1];
}
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
思路:这里的关键点就是冷冻期如何理解,其实很简单,冷冻期就是不能卖出的第二天立即买入股票,因此如果说买入是第i天,那么第i-2天必须得是不持有股票的。即状态传递方程式下面这样:
dp[i][0] = max(dp[i-1][0], dp[i-2][1] - prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
但是这里注意i-2一开始是越界的,i=1是1-2=-1,是越界的。所以使用一个变量保存i-2的值。
public int maxProfit(int[] prices) {
if(prices==null||prices.length<1)
return 0;
int[][] dp=new int[prices.length][2];
dp[0][0]=-prices[0];//初始化
dp[0][1]=0;
int dpPre=0;
for(int i=1;i<prices.length;i++){
int tmp=dp[i-1][1];
dp[i][0]=Math.max(dp[i-1][0],dpPre-prices[i]);
dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
dpPre=tmp;
}
return dp[prices.length-1][1];
}
时间复杂度O(N)。
关于股票这一系列的题目做下来感觉还是挺难的,但是一旦掌握了套路之后至少能确保做出来,虽然说计算效率其实不是很高,但是对于面试来说完全够用了,毕竟关键是能不能做出来,效率再高,现场想不起来,做不出来都是“扯淡”。
今天就这样吧。再会。