区间DP是指以区间左右边界 f[i][j] 作为动态规划变量的问题
一般求解步骤:
1.状态定义:f[i][j] 为求解分区间 [i,j] 重复子问题的开销,其中 i<=j
2.状态转移:求f[i][j]通常要考虑将 [i, j] 区间分为两个重复子问题
这个根据具体的问题而定,有的可能要根据s[i]与s[j]是否相等做出不同的转移,如 664. 奇怪的打印机
有的可能是选择直接枚举分割点k,然后建立 [i, j] 区间与 左右子区间的转移,如 375. 猜数字大小 II
有的还可能不是分割区间,而是利用缩小区间 [i+1,j] 的子问题与 [i, j] 区间建立连接,如 877. 石子游戏
3.初始化:通常来说要初始化长度为1的边界状态 f[i][i] 的值,为接下来的遍历做好初值准备
4.遍历顺序:这里有两种遍历的方法,推荐以遍历长度len的方法
4.1 长度len为基础进行遍历
1.遍历长度len(正序2~n);
2.遍历左边界i(正序0~i+len-1==n-1);
3.遍历分割点k(正反序无所谓i~j-1)
 4.2 i与j为基础进行遍历
1.枚举左端点i(倒序n-2~0)
2.枚举右端点j(正序i+1~n-1)
3.枚举分割点k(正反序无所谓i~j-1)
此时以k分割后的左右子区间分别为:[i,k] 与 [k+1,j]
示意图如下:可知分割区间依赖于正下方和正左方的状态有效值->遍历长度是以“滑梯”的方式下去的;遍历i与j是从底下上来的

5.返回形式:一般来说返回 f[0][n-1] 就是关于整个区间对应的子问题答案
复杂度分析:一般地,时间复杂度:O(N^3);空间复杂度:O(N^2)
375. 猜数字大小 II
| 12
 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
 
 | class Solution {static int[][] f = new int[205][205];
 static final int INF = 0x3f3f3f3f;
 
 public int getMoneyAmount(int n) {
 
 
 
 
 
 
 
 
 
 
 
 for (int i = n; i >= 1; i--) {
 
 for (int j = i + 1; j <= n; j++) {
 int min = INF;
 
 for (int k = i; k <= j; k++) {
 int cur = k + Math.max(f[i][k - 1], f[k + 1][j]);
 min = Math.min(min, cur);
 }
 f[i][j] = min;
 }
 }
 return f[1][n];
 }
 }
 
 | 
664. 奇怪的打印机
| 12
 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
 
 | class Solution {public int strangePrinter(String s) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 int n = s.length(), INF = 101;
 int[][] f = new int[n][n];
 
 for (int i = 0; i < n; i++) {
 f[i][i] = 1;
 }
 
 for (int len = 2; len <= n; len++) {
 
 for (int i = 0; i + len - 1 < n; i++) {
 
 int j = i + len - 1;
 
 if (s.charAt(i) == s.charAt(j)) {
 f[i][j] = f[i][j - 1];
 } else {
 int min = INF;
 
 for (int k = i; k < j; k++) {
 
 min = Math.min(min, f[i][k] + f[k + 1][j]);
 }
 f[i][j] = min;
 }
 }
 }
 return f[0][n - 1];
 }
 }
 
 | 
记忆化DFS解法:
| 12
 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
 
 | class Solution {static int[][] memo;
 static final int INF = 101;
 char[] chs;
 
 public int strangePrinter(String s) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 chs = s.toCharArray();
 int n = chs.length;
 memo = new int[n][n];
 return dfs(0, n - 1);
 }
 
 
 private int dfs(int i, int j) {
 
 if (memo[i][j] != 0) return memo[i][j];
 
 if (i == j) return 1;
 
 if (chs[i] == chs[j]) return dfs(i, j - 1);
 int min = INF;
 for (int k = i; k < j; k++) {
 min = Math.min(min, dfs(i, k) + dfs(k + 1, j));
 }
 memo[i][j] = min;
 return min;
 }
 }
 
 | 
其实能用动态规划解决的问题都可以用记忆化DFS解决
记忆化DFS的优点就是不用考虑具体的状态遍历顺序是怎样的,只要能够判断出memo里面的状态被有效值覆盖过就可以直接拿来用,因此可以将 memo[i][j] 的值初始化为一个不可能到达的值INF
模板中一般包含一下几个关键点:
1.每次递归返回之前记录该次最优值进入memo memo[i][j] = min;
2.若dfs(i,j)中的memo[i][j]的有效值已经出现过可以直接取 if (memo[i][j] != 0) return memo[i][j];
3.base case 一般来说是 if (i == j) return ?;
4.遍历与dfs(i,j)有关的状态(结果),选择最优的(经过计算)得到的结果作为dfs(i,j)的结果 视具体问题而定