动态规划是一种用来解决一类最优化问题的算法思想。
将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解。
动态规划会将每个求解过的子问题的解记录下来,这样当下一次碰到同样的子问题时,
就可以直接使用之前记录的结果,而不是重复计算。
如何记录子问题的解?
例:斐波那契数列的定义:F0=1,F1=1,Fn = Fn-1 + Fn-2 (n>=2)
public class Main {
static int[] dt;//用于保存已计算过的结果
static int F(int n) {
if (n == 0 || n == 1)// 递归边界
return 1;
if (dt[n] != -1)// 已经被计算过,直接返回结果,不再重复计算
return dt[n];
else {
dt[n] = F(n - 1) + F(n - 2);// 计算F(n),并保存至dt[n];
return dt[n];// 返回F(n)的结果
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
dt = new int[n * n];
Arrays.fill(dt, -1);
System.out.println(F(n));
}
}
如果一个问题可以被分解为若干个子问题,且这些子问题会重复出现,
那么就称这个问题拥有重叠子问题。
动态规划通过记录重叠子问题的解,来使下次碰到相同的子问题时直接使用之前记录的结果,
以此避免大量重复计算。
因此,一个问题必须拥有重叠子问题,才能使用动态规划去解决。
动态规划的递推写法
动态规划的递推写法总是从边界出发,通过状态转移方程扩散到整个dt数组。
例:数塔问题
将一些数字排成楼塔的形状,其中第一层有一个数字,第二层有两个数字......第n层有n个数字。
现在要从第一层走到第n层,每次只能走向下一层连接的两个数字中的一个。
问:最后将路径上所有数字相加后得到的和最大是多少?
输入:5
5
8 3
12 7 16
4 10 11 6
9 5 3 9 4
输出:44
解:
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[][] a = new int[n + 1][n + 1];
// 呈现塔型,下层总比它的上一层多一个
for (int i = 1; i <= n; i++)
for (int j = 1; j <= i; j++)
a[i][j] = in.nextInt();
int[][] dt = new int[n + 1][n + 1];// 用于保存已计算过的结果
for (int j = 1; j <= n; j++) {// 边界(最底层那一排)
dt[n][j] = a[n][j];// 因为是边界,所以最大值就是其本身
}
// 从第n-1层不断往上计算出dt[i][j]
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
// 动态转移方程(这个点的最大值等于它下面两个点中的较大值加上它本身的值)
dt[i][j] = Math.max(dt[i + 1][j], dt[i + 1][j + 1]) + a[i][j];
}
}
System.out.println(dt[1][1]);// 输出顶点的最大值
}
}
如果一个问题的最优解可以由其子问题的最优解有效地构造出来,
那么称这个问题拥有最优子结构。
最优子结构保证了动态规划中原问题的最优解可以由子问题的最优解推导而来。
所以,一个问题必须拥有重叠子问题和最优子结构,才能使用动态规划去解决。