打家劫舍
解法一:线性 DP
分析
状态表示
q(i)
表示第i
家的现金f(i)
表示抢劫到第i
家时的最大现金
状态转移
- 限制条件:不能联系抢劫两家相邻的
- 抢劫第
i - 2
家时,能抢第i
家 - 抢劫第
i - 1
家时,不能抢第i
家
状态计算:最大值 max
代码
class Solution {
public int rob(int[] q) {
int n = q.length;
int[] f = new int[n];
f[0] = q[0];
if (n == 1) return f[0];
f[1] = Math.max(f[0], q[1]);
if (n == 2) return f[1];
for (int i = 2; i < n; i++) {
f[i] = Math.max(f[i - 2] + q[i], f[i - 1]);
}
return f[n - 1];
}
}
解法二:状态 DP
分析
状态表示
q(i)
表示第i
家的现金f(i, 0)
表示到第i
家时,没抢
第i
家 时的最大现金f(i, 1)
表示到第i
家时,抢了
第i
家时的最大现金
状态机
状态转移
没抢
第i
家,可以从抢了
第i - 1
家转移,也可以从没抢
第i - 1
家转移抢了
第i
家,则只能从没抢
第i - 1
家转移
状态计算:最大值 max
代码
class Solution {
public int rob(int[] q) {
int n = q.length;
int[][] f = new int[n][2];
f[0][1] = q[0];
for (int i = 1; i < n; i++) {
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + q[i];
}
return Math.max(f[n - 1][0], f[n - 1][1]);
}
}
优化
可见,状态 DP 的状态划分以及转移更加清晰明了,且状态 DP 也更容易优化。
如此题,线性 DP 依赖 i - 2
和 i - 1
状态,而状态 DP 只依赖 i - 1
状态,所以使用滚动变量优化时,状态 DP 更简单
解法一:线性 DP 的滚动变量优化
class Solution {
public int rob(int[] q) {
int n = q.length;
int a = 0, b = q[0], c = Math.max(q[0], q[1]);
for (int i = 2; i < n; i++) {
int t = Math.max(b + q[i], c);
a = b;
b = c;
c = t;
}
return c;
}
}
解法二:状态 DP 的滚动变量优化
class Solution {
public int rob(int[] q) {
int n = q.length;
int a = 0, b = q[0];
for (int i = 1; i < n; i++) {
int x = a, y = b;
a = Math.max(x, y);
b = x + q[i];
}
return Math.max(a, b);
}
}
打家劫舍 II
分析
这个和打家劫舍差不多,不同的点是偷了首家就不能偷最后一家,偷了最后一家就不能偷首家。
所以只需要做两次打家劫舍,第一次从第 0
到 n - 2
家,第二次从第 1
到 n - 1
家。
最后在求最大值就是答案。
代码
class Solution {
public int rob(int[] q) {
int n = q.length;
if (n == 1) return q[0];
int[][] f = new int[n][2];
f[0][1] = q[0];
for (int i = 1; i < n - 1; i++) {
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]);
f[i][1] = f[i - 1][0] + q[i];
}
int[][] g = new int[n][2];
g[1][1] = q[1];
for (int i = 2; i < n; i++) {
g[i][0] = Math.max(g[i - 1][0], g[i - 1][1]);
g[i][1] = g[i - 1][0] + q[i];
}
int a = Math.max(f[n - 2][0], f[n - 2][1]);
int b = Math.max(g[n - 1][0], g[n - 1][1]);
return Math.max(a, b);
}
}
买卖股票的最佳时机含手续费
分析
状态表示
q(i)
表示股票在第i
天的价格f(i, 0)
表示在第i
天, 手中持有
股票时的最大金额f(i, 1)
表示在第i
天, 手中没有
股票时的最大金额
状态机
状态转移
- 第
i
天,没有
股票。即f(i, 0)
- 在第
i - 1
天,没有
股票,什么也不做。即f(i - 1, 0)
转移 - 在第
i - 1
天,持有
股票,但卖出股票。即f(i - 1, 1)
转移
- 在第
- 第
i
天,持有
股票。即f(i, 1)
- 在第
i - 1
天,持有
股票,什么也不做。即f(i - 1, 1)
转移 - 在第
i - 1
天,没有
股票,但买入股票。即f(i - 1, 0)
转移
- 在第
状态计算:最大值 max
,手中不持有股票自然比手中持有股票的金额多
代码
class Solution {
public int maxProfit(int[] q, int fee) {
int n = q.length;
int[][] f = new int[n][2];
f[0][1] = -q[0];
for (int i = 1; i < n; i++) {
f[i][0] = Math.max(f[i - 1][0], f[i - 1][1] + q[i] - fee);
f[i][1] = Math.max(f[i - 1][1], f[i - 1][0] - q[i]);
}
return f[n - 1][0];
}
}
滚动变量优化后
class Solution {
public int maxProfit(int[] q, int fee) {
int n = q.length;
int x = 0, y = -q[0];
for (int i = 1; i < n; i++) {
int a = x, b = y;
x = Math.max(a, b + q[i] - fee);
y = Math.max(b, a - q[i]);
}
return x;
}
}
最佳买卖股票时机含冷冻期
分析
状态表示
q(i)
表示股票在第i
天的价格f(i, 0)
表示在第i
天, 手中没有股票且在冷冻期
时的最大金额f(i, 1)
表示在第i
天, 手中没有股票且非冷冻期
时的最大金额f(i, 2)
表示在第i
天,手中持有
股票时的最大金额
状态机
状态转移
- 在第
i
天,手中持有
股票时。即f(i, 2)
- 在第
i - 1
天,手中持有
股票。即f(i - 1, 2)
转移 - 在第
i - 1
天,手中没有股票且不非冷冻期
,购入股票。即f(i - 1, 1)
转移
- 在第
- 在
i
天,手中没有股票且在冷冻期
时。即f(i, 0)
- 在第
i - 1
天,手中持有
股票,卖出股票进入冷冻期。即f(i - 1, 2)
转移
- 在第
- 在
i
天,手中没有股票且非冷冻期
时。即f(i, 1)
- 在第
i - 1
天,手中没有股票且在冷冻期
,摆脱冷冻期。即f(i - 1, 0)
转移 - 在第
i - 1
天,手中没有股票且非冷冻期
,什么都不做。即f(i - 1, 1)
转移
- 在第
状态计算:最大值 max
,手中不持有股票自然比手中持有股票的金额多(不持有的两种状态都不做限制)
代码
class Solution {
public int maxProfit(int[] q) {
int n = q.length;
int[][] f = new int[n][3];
f[0][2] = -q[0];
for (int i = 1; i < n; i++) {
f[i][2] = Math.max(f[i - 1][2], f[i - 1][1] - q[i]);
f[i][0] = f[i - 1][2] + q[i];
f[i][1] = Math.max(f[i - 1][0], f[i - 1][1]);
}
return Math.max(f[n - 1][0], f[n - 1][1]);
}
}
滚动变量优化后
class Solution {
public int maxProfit(int[] q) {
int n = q.length;
int a = 0, b = 0, c = -q[0];
for (int i = 1; i < n; i++) {
int x = a, y = b, z = c;
c = Math.max(z, b - q[i]);
a = z + q[i];
b = Math.max(x, y);
}
return Math.max(a, b);
}
}
买卖股票的最佳时机 IV
买卖股票的最佳时机 III 同此题
分析
状态表示
q(i)
表示股票在第i
天的价格f(i, j, 0)
表示在第i
天,正进行第j
次交易,手中没有
股票时的最大金额f(i, j, 1)
表示在第i
天,正进行第j
次交易,手中持有
股票时的最大金额
状态机
状态转移
- 在第
i
天,正进行第j
次交易,手中没有
股票。即f(i, j, 0)
- 在第
i - 1
天没有
股票,第i
天也没买入,此时没开始新一轮的交易。从f(i - 1, j, 0)
转移 - 在第
i - 1
天持有
股票,但第i
天卖出,此时没开始新一轮的交易。从f(i - 1, j, 1)
转移
- 在第
- 在第
i
天,正进行第j
次交易,手中持有
股票。即f(i, j, 1)
- 在第
i - 1
天持有
股票,第i
天没卖出,此时没开始新一轮的交易。从f(i - 1, j, 1)
转移 - 在第
i - 1
天没有
股票,但第i
天买入,此时开始做新一轮的交易。从f(i - 1, j - 1, 0)
转移
- 在第
状态计算:最大值 max
,手中不持有股票自然比手中持有股票的金额多
代码
class Solution {
public int maxProfit(int k, int[] q) {
int n = q.length;
if (n == 0 || k == 0) return 0;
int[][][] f = new int[n][k + 1][2];
for (int i = 1; i <= k; i++) {
f[0][i][0] = f[0][i][1] = -0x3f3f3f;
}
f[0][0][0] = 0;
f[0][1][1] = -q[0];
for (int i = 1; i < n; i++) {
f[i][0][1] = Math.max(f[i - 1][0][1], f[i - 1][0][0] - q[i]);
for (int j = 1; j <= k; j++) {
f[i][j][0] = Math.max(f[i - 1][j][0], f[i - 1][j][1] + q[i]);
f[i][j][1] = Math.max(f[i - 1][j][1], f[i - 1][j - 1][0] - q[i]);
}
}
int res = 0;
for (int i = 0; i <= k; i++) {
res = Math.max(res, f[n - 1][i][0]);
}
return res;
}
}
滚动变量优化后
class Solution {
public int maxProfit(int k, int[] q) {
int n = q.length;
if (n == 0 || k == 0) return 0;
int[][] f = new int[k + 1][2];
for (int i = 1; i <= k; i++) {
f[i][0] = f[i][1] = -0x3f3f3f;
}
f[0][0] = 0;
f[1][1] = -q[0];
for (int i = 1; i < n; i++) {
int[][] g = new int[k + 1][2];
for (int j = 0; j <= k; j++) {
g[j][0] = f[j][0];
g[j][1] = f[j][1];
}
f[0][1] = Math.max(g[0][1], g[0][0] - q[i]);
for (int j = 1; j <= k; j++) {
f[j][0] = Math.max(g[j][0], g[j][1] + q[i]);
f[j][1] = Math.max(g[j][1], g[j - 1][0] - q[i]);
}
}
int res = 0;
for (int i = 0; i <= k; i++) {
res = Math.max(res, f[i][0]);
}
return res;
}
}