大家好!我是未来村村长,就是那个“请你跟我这样做,我就跟你这样做!”的村长!
未来村村长正推出一系列【Algorithm Day】文章,该系列文章重在提高本人的算法能力,希望能在刷题过程中总结一般方法,提高个人的逻辑思维能力和解题能力。该系列文章以天数为轴,从一个个算法中逐步强化算法相关知识点。
”算法之路,任重而道远。“|day 8|
[美的笔试第一题]
假设你有一个数组prices,长度为n,其中prices[i]是股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益,具体要求如下:
1.你可以买入一次股票和卖出一次股票,并非每天都可以买入或卖出一次,总共只能买入和卖出一次,且买入必须在卖出的前面的某一天
2.如果不能获取到任何利润,请返回0
3.假设买入卖出均无手续费
要求:空间复杂度 O(1),时间复杂度 O(n)
如上图所示:
我们这里定义三个变量,其中min,i为指针,maxPro为第i天的最大收益。
public class Solution {
public int maxProfit (int[] prices) {
int n = prices.length;
//状态确定
int dp[][] = new int[n][2];
//初始状态
dp[0][0] = 0;//不持股
dp[0][1] = -prices[0];//持股
//计算顺序
for(int 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];
}
}
public class Solution {
public int maxProfit (int[] prices) {
//错误判断
if(prices==null || prices.length==0) return 0;
//指针
int min = prices[0];//min最开始指向prices[0]
int i;//用于遍历prices[]
//最大收益
maxPro = 0;
//遍历
for(i = 1;i<prices.length;i++){
min = Math.min(min,prices[i]);
maxPro = Math.max(maxPro,prices[i]-min);
}
return maxPro;
}
}
最后我们可以结合计算过程就可以很好理解,我们以[8,9,2,5,4,7,1]为例:
dp[i][0]表示的是第i天不持股的最大收益,也就是maxPro,即决定了当天是否要卖股票。d[i][1]表示的是第i天持股的最大收益,其实对应双指针中的min,即决定了当天是否要买(这里当然是买小不买大)。
假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益,具体要求如下:
你可以多次买卖该只股票,但是再次购买前必须卖出之前的股票
如果不能获取收益,请返回0
假设买入卖出均无手续费
思路依旧是动态规划,和上一题的解题过程相比,只变动了状态dp[i][1]的转移方程。
我们来具体看看状态转移方程:
public class Solution {
public int maxProfit (int[] prices) {
int n = prices.length;
//确定状态
int dp[][] = new int[n][2];
//初始状态
dp[0][0] = 0;
dp[0][1] = -prices[0];
//计算顺序
for(int 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];
}
}
我们继续来看计算过程,还是以[8,9,2,5,4,7,1]为例:
dp[i][1]还是决定了什么时候买,dp[i][0]决定了什么时候卖,但这里的不同是比较过程中,dp[i][1]的状态转移关联了dp[i-1][0],dp[i][0]的状态转移关联了dp[i-1][1]。
假设你有一个数组prices,长度为n,其中prices[i]是某只股票在第i天的价格,请根据这个价格数组,返回买卖股票能获得的最大收益,具体要求如下:
如上图所示:
class Solution {
public static int maxProfit (int[] prices, int k) {
//错误判断
if(prices==null || prices.length ==0 || k==0) return 0;
//状态确认
int n = prices.length;
int dp[][][] = new int[n][k+1][2];
//初始状态
for(int j=0;j<=k;i++){
dp[0][j][0] = 0;
dp[0][j][1] = -prices[0];
}
for(int i=1;i<n;i++){
for(int j=1;j<=k;j++){
dp[i][j][0] = Math.max(dp[i-1][j][0],dp[i-1][j][1] + prices[i]);
dp[i][j][1] = Math.max(dp[i-1][j][1],dp[i-1][j-1][0] - peices[i]);
}
}
return dp[n-1][k][0];
}
}
我们结合计算过程来看,以[8,9,3,5,1,3]为例,操作次数k=2。我们可以看到,这里如果只交易一次的话,dp[n-1][1][0]=2就是最终答案,这里交易两次,这第二次交易计算dp[i][2][0]和dp[i][2][1]时,就需要用到第一次交易的数据。
计算过程是连续的、多次的、关联的,就可能用到动态规划,一般最优问题(最大值或最小值)可能用到动态规划,其次就是计数型和可行性。
确定状态和转移方程是解决问题的关键,我们可以通过分析计算过程的最后一步到得出结果这个过程来推导状态和状态的转移。根据条件,我们要判断有哪几种状态,然后状态是如何转移的。如股票问题,就有以下几种状态:
我们可以通过列举的方式,把几种状态列举出来,选择具有关联性并且对结果具有影响的状态指标作为状态。
状态如何转移,就是如何将原问题分解成子问题,最值的状态转移就是一个不断比较更新的过程,即是否进行值的更新,状态转移一定是一个子问题,代表我们不能受整体的影响,只需要关注前一步对当前状态的影响以及当前状态如何进行变化。