买卖股票的最佳时机I

121. 买卖股票的最佳时机I-LeetCode题目地址

这道题题目要求我们在某一天买入股票,在未来的某一天卖出股票,也就是只能买卖一次股票,我们可以从左到右遍历整个数组,维护一个最小值,最小值代表的是第i天之前股票的最小值,不断计算今天股票的价格和之前天数当中股票最小值的差值,找出最大值返回结果,Java版代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int maxProfit(int[] prices) {
int min = Integer.MAX_VALUE;
int maxProfit = 0;
for (int i = 0; i < prices.length; i++) {
if (prices[i] > min) {
maxProfit = Math.max(maxProfit, prices[i] - min);
}
min = Math.min(min, prices[i]);
}
return maxProfit;
}
}

买卖股票的最佳时机II

122. 买卖股票的最佳时机 II-LeetCode题目地址

记忆化搜索

这道题和上到题有所不同,我们可以多次买卖股票,因此就需要进行动态规划了,具体情况分为今天是否有股票,今天没有股票(买还是不买),今天有股票(卖还是不卖),并且如果最后一天持有股票一定要卖。

由于递归时间复杂度很高,因此我们引入记忆化搜索减少重复的递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int maxProfit(int[] prices) {
int[][] memo = new int[prices.length][2];
for (int[] ints : memo) {
Arrays.fill(ints, -1);
}
return dfs(prices,0,0,memo);
}

public int dfs(int[] prices,int i,int hold,int[][] memo) {
if(i == prices.length) return 0;
if(memo[i][hold] != -1) return memo[i][hold];
// 当前未持有股票 买或者不卖
if(hold == 0){
return memo[i][hold] = Math.max(dfs(prices,i+1,0,memo),dfs(prices,i+1,1,memo) - prices[i]);
}
// 当前持有股票 卖
// 如果是最后一天只能卖了
if (i == prices.length-1) return memo[i][hold] = dfs(prices,i+1,0,memo) + prices[i];
return memo[i][hold] = Math.max(dfs(prices,i+1,1,memo),dfs(prices,i+1,0,memo) + prices[i]);
}
}

上面这种是正着进行递归,我们还可以倒着进行递归,当天持有股票可能是上一天持有股票没有卖或者今天刚刚买了股票,当天没有股票可能是上一天没有股票或者今天刚刚卖了股票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
private int[] prices;
private int[][] memo;

public int maxProfit(int[] prices) {
this.prices = prices;
int n = prices.length;
memo = new int[n][2];
for (int[] row : memo) {
Arrays.fill(row, -1); // -1 表示还没有计算过
}
return dfs(n - 1, 0);
}

private int dfs(int i, int hold) {
if (i < 0) {
return hold == 1 ? Integer.MIN_VALUE : 0;
}
if (memo[i][hold] != -1) {
return memo[i][hold]; // 之前计算过
}
if (hold == 1) {
return memo[i][hold] = Math.max(dfs(i - 1, 1), dfs(i - 1, 0) - prices[i]);
}
return memo[i][hold] = Math.max(dfs(i - 1, 0), dfs(i - 1, 1) + prices[i]);
}
}

1:1翻译为递推

我们根据这两个式子memo[i][hold] = Math.max(dfs(i - 1, 1), dfs(i - 1, 0) - prices[i]);memo[i][hold] = Math.max(dfs(i - 1, 0), dfs(i - 1, 1) + prices[i]);一比一翻译为递推,Java版代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length+1][2];
// 第一天有股票必须是当天买
dp[1][1] = -prices[0];
for (int i = 2; i <= prices.length; i++) {
// 当天没有股票 可能是前一天没有 或者是今天卖了
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
// 当天有股票 可能是前一天有 或者是今天刚买
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i-1]);
}
// 最后一天没有持有股票的利润肯定比最后一天持有股票的利润高
return dp[prices.length][0];
}
}

空间复杂度优化

上面我们用了一个数组来记录一天内是否持有股票的利润最大值,但是在递推的过程中我们只用到了前一天的状态,因此可以优化空间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public int maxProfit(int[] prices) {
// 我们每次只需要用到前一天的数据
int f0 = 0;
int f1 = -prices[0];
for (int i = 2; i <= prices.length; i++) {
int f = f0;
f0 = Math.max(f0, f1 + prices[i-1]);
f1 = Math.max(f1, f - prices[i-1]);
}
return f0;
}
}

买卖股票的最佳时机含冷冻期

309. 买卖股票的最佳时机含冷冻期-LeetCode题目地址

记忆化搜索

这道题在112的基础上加上了一个冷静期,在冷静期中我们什么也干不了,因此正着递归只需要加上冷静期的判断即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
public int maxProfit1(int[] prices) {
int[][] memo = new int[prices.length][2];
for (int[] ints : memo) {
Arrays.fill(ints, -1);
}
return dfs(prices,0,0,memo,false);
}

public int dfs(int[] prices,int i,int hold,int[][] memo,boolean isCool) {
if(i == prices.length) return 0;
// 冷静期什么也干不了
if(isCool){
return dfs(prices,i+1,hold,memo,false);
}
if(memo[i][hold] != -1) return memo[i][hold];
// 当前未持有股票 买或者不卖
if(hold == 0){
return memo[i][hold] = Math.max(dfs(prices,i+1,0,memo,false),dfs(prices,i+1,1,memo,false) - prices[i]);
}
// 当前持有股票 卖
// 如果是最后一天只能卖了
if (i == prices.length-1) return memo[i][hold] = dfs(prices,i+1,0,memo,true) + prices[i];
return memo[i][hold] = Math.max(dfs(prices,i+1,1,memo,false),dfs(prices,i+1,0,memo,true) + prices[i]);
}
}

倒着递归,当天持有股票可能是昨天有,或者今天刚买(如果是今天刚买,那么上一天既不能买,也不能卖,那么只能从i-2天没有持有股票的状态转移过来),当天没有股票可能是上一天没有股票或者今天刚刚卖了股票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Solution {
private int[] prices;
private int[][] memo;

public int maxProfit(int[] prices) {
this.prices = prices;
int n = prices.length;
memo = new int[n][2];
for (int[] row : memo) {
Arrays.fill(row, -1); // -1 表示还没有计算过
}
return dfs(n - 1, 0);
}

private int dfs(int i, int hold) {
if (i < 0) {
return hold == 1 ? Integer.MIN_VALUE : 0;
}
if (memo[i][hold] != -1) { // 之前计算过
return memo[i][hold];
}
if (hold == 1) {
return memo[i][hold] = Math.max(dfs(i - 1, 1), dfs(i - 2, 0) - prices[i]);
}
return memo[i][hold] = Math.max(dfs(i - 1, 0), dfs(i - 1, 1) + prices[i]);
}
}

1:1翻译为递推

根据memo[i][hold] = Math.max(dfs(i - 1, 1), dfs(i - 2, 0) - prices[i]);memo[i][hold] = Math.max(dfs(i - 1, 0), dfs(i - 1, 1) + prices[i]);这两个式子一比一翻译为递推

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length+1][2];
// 第一天有股票必须是当天买
dp[1][1] = -prices[0];
for (int i = 2; i <= prices.length; i++) {
// 当天没有股票 可能是前一天没有 或者是今天卖了
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
// 当天有股票 可能是前一天有 或者是今天刚买(今天刚买 那么前一天不能卖也不能买 只能从第 i-2 天的状态转移过来)
dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0] - prices[i-1]);
}
// 最后一天没有持有股票的利润肯定比最后一天持有股票的利润高
return dp[prices.length][0];
}
}

空间复杂度优化

我们计算当天的最优解时候需要知道前一天的值和i-2天没有持有股票的值,因此优化如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int maxProfit(int[] prices) {
int f0 = 0;
int ff0 = 0;
int ff1 = -prices[0];
for (int i = 2; i <= prices.length; i++) {
int f = ff0;
ff0 = Math.max(ff0, ff1 + prices[i-1]);
ff1 = Math.max(ff1, f0 - prices[i-1]);
f0 = f;
}
return ff0;
}
}