文章目录
leetcode121 买卖股票的最佳日期① 动态规划
特征方程:dp[i]=Math.max(dp[i-1],prices[i]-minPrice);
初始化 dp[0]=0 dp[1]=0 dp[2]开始执行特征方程
题目描述
题目描述:给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 10^5
0 <= prices[i] <= 10^4
解法:动态规划 dp[i]=max(dp[i−1],prices[i]−minprice)
动态规划一般分为一维、二维、多维(使用状态压缩),对应形式为 dp(i)dp(i)、dp(i)(j)dp(i)(j)、二进制dp(i)(j)二进制dp(i)(j)。
- 明确 dp(i)dp(i) 应该表示什么(二维情况:dp(i)(j)dp(i)(j));
- 根据 dp(i)dp(i) 和 dp(i-1)dp(i−1) 的关系得出状态转移方程;
- 确定初始条件,如 dp(0)dp(0)。
dp[i]=max(dp[i−1],prices[i]−minprice)
彻底搞懂这个dp特征方程:
- dp[i] 表示第i天最大利润;
- dp[i-1] 表示和前一天的状态一样,当前未持有股票,所以前一天也未持有股票,所以和前一天一样。注意:第i天未持有股票可能有两种可能:尚未开始买入股票继续等待、已经卖出了买入的股票。
- prices[i]−minprice 表示当前持有股票,可以以今天的价格卖出,曾经以最低价格买入的。
两种情况中,取最大值。
只能买一次的话,第i天的状态只能有两种,第i天未持有股票,则i-1天也一定未持有股票,利润和第 i-1 天一样(可能完成了买入卖出操作,可能尚未开始买入卖出操作);第i天持有股票,则可以以今天的价格卖出,曾经以最低价格买入的。
在这个动态规划方程中,把两种情况都考虑到了,所以没问题。
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==0) return 0;
int minPrice=prices[0];
int dp[]=new int[prices.length];
for(int i=1;i<prices.length;i++){
minPrice=Math.min(minPrice,prices[i]);
dp[i]=Math.max(dp[i-1],prices[i]-minPrice);
}
return dp[prices.length-1];
}
}
因为dp[i]仅仅和dp[i-1]相关,直接简化,用变量maxProfit代替dp数组,如下:
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==0) return 0;
int minPrice=prices[0];
int maxProfit=0;
for(int i=1;i<prices.length;i++){
minPrice=Math.min(minPrice,prices[i]);
maxProfit=Math.max(maxProfit,prices[i]-minPrice);
}
return maxProfit;
}
}
其实,对于这个动态规划方程,如果想看得更清楚一点,可以写成二维的,加上当前是否持有股票的判断,如下:
leetcode122 买卖股票的最佳日期② 贪心+动态规划
题目描述
题目描述:
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
解法1:贪心,吃掉所有上涨,一定是最大的,连涨的情况也被贪心包含了
具体代码为:
class Solution {
public int maxProfit(int[] prices) {
if(prices.length<2)
return 0;
int value=0;
for(int i=1;i<prices.length;i++)
{
// 就要吃掉波动市场上的所有上涨,避开所有下跌
if(prices[i]>prices[i-1])
value+=prices[i]-prices[i-1];
}
return value;
}
}
注意:连涨几天也没关系,虽然不可以“第一天买第二天卖,然后第二天买第三天卖”,但是可以第一天买第三天卖,一直累加到最后涨的那一天,刚好跌的前一天就好了。
解法2:动态规划
考虑到「不能同时参与多笔交易」,因此第i天交易结束后只可能存在两种:
- 手里有一支股票 dp[i][1]
- 没有股票的状态 dp[i][0]
不可能持有两支股票。
定义状态 dp[i][0] 表示第 i 天交易完后手里没有股票的最大利润,dp[i][1] 表示第 i 天交易完后手里持有一支股票的最大利润(i 从 0 开始)。
考虑 dp[i][0] 的转移方程,表示这一天交易完后手里没有股票:
- 可能前一天也没有股票,即 dp[i−1][0],表示继续等待;
- 前一天结束的时候手里持有一支股票,即 dp[i−1][1],表示卖出股票,并获得 prices[i] 的收益。
因此为了收益最大化,我们列出如下的转移方程:
dp[i][0]=max{dp[i−1][0],dp[i−1][1]+prices[i]}
这个动态规划方程,表示当前没有股票的前一天的两种情况的利润最大值。
再来考虑 dp[i][1],按照同样的方式考虑转移状态,那么可能的转移状态为
- 前一天已经持有一支股票,即 dp[i−1][1],表示继续持有;
- 前一天结束时还没有股票,即 dp[i−1][0],表示买入股票,并减少 prices[i] 的本金。
可以列出如下的转移方程:
dp[i][1]=max{dp[i−1][1],dp[i−1][0]−prices[i]}
这个动态规划方程,表示当前持有股票的前一天的两种情况的利润最大值。
股票在i天卖出就是+prices[i],股票在i天买入就是-prices[i];
继续持有或者继续未持有股票就是dp[i-1][0或1]。
对于初始状态,根据状态定义我们可以知道第 0 天交易结束的时候 dp[0][0] = 0,dp[0][1] = −prices[0]。
因此,我们只要从前往后依次计算状态即可。由于全部交易结束后,持有股票的收益一定低于不持有股票的收益(因为股票卖出了才赚钱,你也可以最后比较一下,取dp[n−1][0]和dp[n−1][1]中较为大的那个),最后的答案即为dp[n−1][0]。
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int[][] dp = new int[n][2];
dp[0][0] = 0; // 初始化:第0天没有股票,收益为0
dp[0][1] = -prices[0]; // 初始化:第0天买入股票,收益为-prices[0],或者手里本金为-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];
}
}
注意到上面的状态转移方程中,每一天的状态只与前一天的状态有关,而与更早的状态都无关,因此我们不必存储这些无关的状态,只需要将 dp[i−1][0] 和 dp[i−1][1] 存放在两个变量中,通过它们计算出 dp[i][0] 和 dp[i][1] 并存回对应的变量,以便于第 i+1 天的状态转移即可。
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int dp0 = 0, dp1 = -prices[0];
for (int i = 1; i < n; ++i) {
int newDp0 = Math.max(dp0, dp1 + prices[i]);
int newDp1 = Math.max(dp1, dp0 - prices[i]);
dp0 = newDp0;
dp1 = newDp1;
}
return dp0;
}
}
leetcode123 买卖股票的最佳时机③ 动态规划
题目描述
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 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 <= prices.length <= 10^5
0 <= prices[i] <= 10^5
解法:动态规划
二维dp数组
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<vector<int>> dp(prices.size(), vector<int>(5, 0));
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
dp[i][0] = dp[i - 1][0];
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
dp[i][2] = max(dp[i - 1][2], dp[i - 1][1] + prices[i]);
dp[i][3] = max(dp[i - 1][3], dp[i - 1][2] - prices[i]);
dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
}
return dp[prices.size() - 1][4];
}
};
一维dp数组
class Solution {
public:
int maxProfit(vector<int>& prices) {
if (prices.size() == 0) return 0;
vector<int> dp(5, 0);
dp[0] = 0;
dp[1] = -prices[0];
dp[3] = -prices[0];
for (int i = 1; i < prices.size(); i++) {
dp[1] = max(dp[1], dp[0] - prices[i]);
dp[2] = max(dp[2], dp[1] + prices[i]);
dp[3] = max(dp[3], dp[2] - prices[i]);
dp[4] = max(dp[4], dp[3] + prices[i]);
}
return dp[4];
}
};
四个变量,表示四种状态
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int buy1 = -prices[0], sell1 = 0;
int buy2 = -prices[0], sell2 = 0;
for (int i = 1; i < n; ++i) {
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
}