动态规划:用空间代替重复计算
任何动态规划问题都一定对应着一个有重复调用行为的递归
所以动态规划的题目都一定可以从递归入手,逐渐实现动态规划的方法。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package class066;

import java.util.Arrays;

// 最低票价
// 在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行
// 在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出
// 每一项是一个从 1 到 365 的整数
// 火车票有 三种不同的销售方式
// 一张 为期1天 的通行证售价为 costs[0] 美元
// 一张 为期7天 的通行证售价为 costs[1] 美元
// 一张 为期30天 的通行证售价为 costs[2] 美元
// 通行证允许数天无限制的旅行
// 例如,如果我们在第 2 天获得一张 为期 7 天 的通行证
// 那么我们可以连着旅行 7 天(第2~8天)
// 返回 你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费
// 测试链接 : https://leetcode.cn/problems/minimum-cost-for-tickets/
public class Code02_MinimumCostForTickets {

// 无论提交什么方法都带着这个数组 0 1 2
public static int[] durations = { 1, 7, 30 };

// 暴力尝试
public static int mincostTickets1(int[] days, int[] costs) {
return f1(days, costs, 0);
}

// days[i..... 最少花费是多少
public static int f1(int[] days, int[] costs, int i) {
if (i == days.length) {
// 后续已经无旅行了
return 0;
}
// i下标 : 第days[i]天,有一场旅行
// i.... 最少花费是多少
int ans = Integer.MAX_VALUE;
for (int k = 0, j = i; k < 3; k++) {
// k是方案编号 : 0 1 2
while (j < days.length && days[i] + durations[k] > days[j]) {
// 因为方案2持续的天数最多,30天
// 所以while循环最多执行30次
// 枚举行为可以认为是O(1)
j++;
}
ans = Math.min(ans, costs[k] + f1(days, costs, j));
}
return ans;
}

// 暴力尝试改记忆化搜索
// 从顶到底的动态规划
public static int mincostTickets2(int[] days, int[] costs) {
int[] dp = new int[days.length];
for (int i = 0; i < days.length; i++) {
dp[i] = Integer.MAX_VALUE;
}
return f2(days, costs, 0, dp);
}

public static int f2(int[] days, int[] costs, int i, int[] dp) {
if (i == days.length) {
return 0;
}
if (dp[i] != Integer.MAX_VALUE) {
return dp[i];
}
int ans = Integer.MAX_VALUE;
for (int k = 0, j = i; k < 3; k++) {
while (j < days.length && days[i] + durations[k] > days[j]) {
j++;
}
ans = Math.min(ans, costs[k] + f2(days, costs, j, dp));
}
dp[i] = ans;
return ans;
}

// 严格位置依赖的动态规划
// 从底到顶的动态规划
public static int MAXN = 366;

public static int[] dp = new int[MAXN];

public static int mincostTickets3(int[] days, int[] costs) {
int n = days.length;
Arrays.fill(dp, 0, n + 1, Integer.MAX_VALUE);
dp[n] = 0;
for (int i = n - 1; i >= 0; i--) {
for (int k = 0, j = i; k < 3; k++) {
while (j < days.length && days[i] + durations[k] > days[j]) {
j++;
}
dp[i] = Math.min(dp[i], costs[k] + dp[j]);
}
}
return dp[0];
}

}

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
28
29
30
31
32
33
34
35
36
37
38
39
40
package class066;

import java.util.Arrays;

// 解码方法
// 一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
// 'A' -> "1"
// 'B' -> "2"
// ...
// 'Z' -> "26"
// 要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)
// 例如,"11106" 可以映射为:"AAJF"、"KJF"
// 注意,消息不能分组为(1 11 06),因为 "06" 不能映射为 "F"
// 这是由于 "6" 和 "06" 在映射中并不等价
// 给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数
// 题目数据保证答案肯定是一个 32位 的整数
// 测试链接 : https://leetcode.cn/problems/decode-ways/
public class Code03_DecodeWays {


// 严格位置依赖的动态规划
public static int numDecodings3(String str) {
char[] s = str.toCharArray();
int n = s.length;
int[] dp = new int[n + 1];
dp[n] = 1;
for (int i = n - 1; i >= 0; i--) {
if (s[i] == '0') {
dp[i] = 0;
} else {
dp[i] = dp[i + 1];
if (i + 1 < s.length && ((s[i] - '0') * 10 + s[i + 1] - '0') <= 26) {
dp[i] += dp[i + 2];
}
}
}
return dp[0];
}


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
28
29
30
31
32
33
34
package class066;

// 丑数 II
// 给你一个整数 n ,请你找出并返回第 n 个 丑数
// 丑数 就是只包含质因数 2、3 或 5 的正整数
// 测试链接 : https://leetcode.cn/problems/ugly-number-ii/
public class Code05_UglyNumberII {

// 时间复杂度O(n),n代表第n个丑数
public static int nthUglyNumber(int n) {
// dp 0 1 2 ... n
// 1 2 ... ?
int[] dp = new int[n + 1];
dp[1] = 1;
for (int i = 2, i2 = 1, i3 = 1, i5 = 1, a, b, c, cur; i <= n; i++) {
a = dp[i2] * 2;
b = dp[i3] * 3;
c = dp[i5] * 5;
cur = Math.min(Math.min(a, b), c);
if (cur == a) {
i2++;
}
if (cur == b) {
i3++;
}
if (cur == c) {
i5++;
}
dp[i] = cur;
}
return dp[n];
}

}