1. 买卖股票的最佳时机1
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
原则就是, 记录当前最小的值,逐一比较
(1)双指针:
- 记录数组中访问过的最小值
- 记录最大值,默认为0
- 循环数组,计算当前值与最小值的差距
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0)
return 0;
int max = 0, min = prices[0]; //数组中访问过的最小值
for (int i = 0; i < prices.length; i++) {
min = Math.min(min, prices[i]);
max = Math.max(max, prices[i] - min);
}
return max;
}
}
(2)动态规划:
- 确定状态
- 找到转移公式
- 确定初始条件以及边界条件
- 计算结果
定义一个二维数组 dp[length][2],其中 dp[i][0] 表示第 i+1 天( i是从0开始的)结束的时候没持有股票的最大利润,dp[i][1] 表示第 i+1 天结束的时候持有股票的最大利润。
如果我们要求第 i+1 天结束的时候没持有股票的最大利润dp[i][0],那么会有两种情况。
- 第一种情况就是第 i+1 天我们即没买也没卖,那么最大利润就是第i天没持有股票的最大利润 dp[i-1][0]。
- 第二种情况就是第 i+1 天我们卖了一支股票,那么最大利润就是第i天持有股票的最大利润(这个是负的,并且也不一定是第i天开始持有的,有可能在第i天之前就已经持有了)加上第i+1天卖出股票的最大利润,dp[i-1][1]+prices[i]
很明显我们可以得出
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i]);
同理我们可以得出第i+1天结束的时候我们持有股票的最大利润
dp[i][1] = max(dp[i-1][1], -prices[i]);
边界条件就是第1天的时候,如果我们不持有股票,那么
dp[0][0] = 0;
如果持有股票,那么
dp[0][1] = -prices[0];
有了边界条件和递推公式,代码就很容易写出来了,来看下代码
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0)
return 0;
int length = prices.length;
int[][] dp = new int[length][2];
//边界条件
dp[0][0]= 0;
dp[0][1] = -prices[0];
for (int i = 1; i < 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], -prices[i]);
}
//毋庸置疑,最后肯定是手里没持有股票利润才会最大,也就是卖出去了
return dp[length - 1][0];
}
}
优化代码:
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0)
return 0;
int length = prices.length;
int hold = -prices[0];//持有股票
int noHold = 0;//不持有股票
for (int i = 1; i < length; i++) {
//递推公式
noHold = Math.max(noHold, hold + prices[i]);
hold = Math.max(hold, -prices[i]);
}
//毋庸置疑,最后肯定是手里没持有股票利润才会最大,
//也就是卖出去了
return noHold;
}
}
(3)单调栈:其实跟双指针思想一样,保存最小值
- 始终保持栈顶元素是所访问过的元素中最小的,如果当前元素小于栈顶元素,就让栈顶元素出栈,让当前元素入栈。
- 如果访问的元素大于栈顶元素,就要计算他和栈顶元素的差值,我们记录最大的即可
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0)
return 0;
Stack<Integer> stack = new Stack<>();
stack.push(prices[0]);
int max = 0;
for (int i = 1; i < prices.length; i++) {
//如果栈顶元素大于prices[i],那么栈顶元素出栈,
//把prices[i]压栈,要始终保证栈顶元素是最小的
if (stack.peek() > prices[i]) {
stack.pop();
stack.push(prices[i]);
} else {
//否则如果栈顶元素不大于prices[i],就要计算
//prices[i]和栈顶元素的差值
max = Math.max(max, prices[i] - stack.peek());
}
}
return max;
}
}
2. 买卖股票的最佳时机 II
给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
(1) 贪心法
随便画了一个股票的曲线图,可以看到如果股票一直上涨,只需要找到股票上涨的最大值和股票开始上涨的最小值,计算他们的差就是这段时间内股票的最大利润。如果股票下跌就不用计算,最终只需要把所有股票上涨的时间段内的利润累加就是我们所要求的结果
class Solution {
public int maxProfit(int[] prices) {
int index = 0, total =0, len = prices.length;
if (len < 2 || prices == null)
return 0;
while (index < len) {
//持续循环,找到股票价格低点
while (index < len-1 && prices[index] >= prices[index + 1]) {
index++;
}
int min = prices[index];
//持续循环,找到股票价格最高点
while (index < len -1 && prices[index] <= prices[index+1]) {
index++;
}
//计算这一段中能够获利的差值
int Dvalue = prices[index] - min;
total = total + Dvalue;
//计算利润总值并继续循环
index++;
}
return total;
}
}
(2) 动态规划
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2)
return 0;
int length = prices.length;
int[][] dp = new int[length][2];
//初始条件
dp[0][1] = -prices[0];
dp[0][0] = 0;
for (int i = 1; i < 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]);
}
//最后一天肯定是手里没有股票的时候,利润才会最大,
//只需要返回dp[length - 1][0]即可
return dp[length - 1][0];
}
}
- 看到当天的利润只和前一天有关,没必要使用一个二维数组
- 只需要使用两个变量,一个记录当天交易完之后手里持有股票的最大利润,一个记录当天交易完之后手里没有股票的最大利润
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2)
return 0;
int length = prices.length;
//初始条件
int hold = -prices[0];//持有股票
int noHold = 0;//没持有股票
for (int i = 1; i < length; i++) {
//递推公式转化的
noHold = Math.max(noHold, hold + prices[i]);
hold = Math.max(hold, noHold - prices[i]);
}
//最后一天肯定是手里没有股票的时候利润才会最大,
//所以这里返回的是noHold
return noHold;
}
}