什么情况用动态规划
- 发现有重叠子问题的
动态规划阶梯套路
- 写出动态状态转移方程
题目:求一组数字不相邻的数字和最大值
分析
求解动态规划的套路是把每一种情况都分为两种选择,要么选,要么不选。例如在 8 个数中求不相邻和的最大值,例如你选了序号8,就不能选7了。每一位就有两种情况,要么选,要么不选。拿序号8来说:
如果选:也就是如果选 8,那么最优解可能是 8 本身的值 6,加上左边 6 个数中的其中一个,7 不能选了。所以得出等式为:opt(8) = opt(6) + 6。
如果不选:不选的话就是从左边的7个中选两个,换句话说就是从 7 个数中求两和的最大值。所以可以得出等式为:opt(8) = opt(7)。
这里的 opt 是单词 optimal(最优)的缩写。
对于上面一位数字是这么分析的,其它的 7 位也是这样分析,每一位有两种情况,我们只要比较出这两种情况中最大的那个值就可以了。
(+表示选,-表示不选)
所以得出动态转移方程为:
选:opt(n) = opt(n-2) + arr[n-1]
不选:opt(n) = opt(n)
递归的出口:
当 n = 1 那么只能是 n 本身了;if (n == 1) return arr[n-1];
当 n = 2 那么可能是 1,或者是 2;if(n == 2) retur max(arr[n-1],arr[n-2])
递归求解
先尝试用递归的方式写:
/**
* @description:
* 在一组数中,求不相邻之间的数最大和是多少
* 例如:
* 3 3 4 1 9 3 6 6
* 最大值是 22
* @date: 2020-06-16 02:32
* @author: 十一
*/
public class Sum {
@Test
public void testRecOpt() {
int[] arr = {3,3,4,1,9,3,6,6};
int i = arr.length;
Sum sum = new Sum();
int r = sum.recOpt(i, arr);
System.out.println(r);
Assert.assertEquals(22,r);
int[] arr2 = {1,2,4,1,7,8,3};
int i2 = arr2.length;
r = sum.recOpt(i2, arr2);
System.out.println(r);
Assert.assertEquals(15,r);
}
/**
* 递归求最优解
* @param i 序号 - 1 = 数组下标
* @param arr 数组
* @return
*/
public int recOpt(int i,int[] arr) {
// 1. 递归先写退出条件
if (i == 1) {
return arr[i - 1];
}
if (i == 2) {
return max(arr[i - 1], arr[i - 2]);
}
// 2. 选,就是左边隔一个的最优解 + 自己
int a = recOpt(i - 2,arr) + arr[i - 1];
// 3. 不选,就是左边的最优解
int b = recOpt(i-1,arr);
return max(a,b);
}
public int max(int a,int b) {
return a > b ? a : b;
}
}
动态规划求解
动态规划求解不会有很多重复,是因为用一个内存空间来保存了每个节点的值,也是空间换时间。
@Test
public void testDpOpt() {
int[] arr = {3,3,4,1,9,3,6,6};
Sum sum = new Sum();
int r = sum.dpOpt(arr);
System.out.println(r);
Assert.assertEquals(22,r);
int[] arr2 = {1,2,4,1,7,8,3};
r = sum.dpOpt(arr2);
System.out.println(r);
Assert.assertEquals(15,r);
}
public int dpOpt(int[] arr) {
// 保存每种情况的值
int opt[] = new int[arr.length];
// 这两种情况是已知的
opt[0] = arr[0];
opt[1] = max(arr[0],arr[1]);
for (int i = 2; i < arr.length; i++) {
int a = opt[i - 2] + arr[i];
int b = opt[i - 1];
opt[i] = max(a,b);
}
return opt[arr.length -1];
}
public int max(int a,int b) {
return a > b ? a : b;
}
题目:机器人走方格
有一个 m * n 的网格,机器人一次只能走一个格点,且只能向右或向下走,要从左上角走到右下角。请设计一个算法,计算机器人有多少种走法。
分析
机器人要么往下走,要么往右走,所以在坐标轴中可以看做,起点是(m,n)终点是(0,0),那么当 m or n = 1 时,这时只能往一个方向走,当 m or n > 1 时,这时对于每一个格子来说都有两种走法,往下或往右。
递归出口为:if(m == 1 || n == 1) return 1;
状态转移方程:f(m,n)=(m-1,n)+(m,n-1);
递归求解
public int f1(int m, int n) {
if (m == 1 || n == 1) {
return 1;
}
return f1(m-1,n) + f1(m,n-1);
}
动态规划
动态规划和递归类似,这里用数组来保存状态
public int f2(int m, int n) {
int[][] r = new int[m+1][n+1];
r[0][0] = 0;
for (int i = 1; i <= m; i++) {
for (int i1 = 1; i1 <= n; i1++) {
if (i1 - 1 == 0 || i - 1 == 0) {
r[i][i1] = 1;
continue;
}
r[i][i1] = r[i-1][i1] + r[i][i1-1];
}
}
return r[m][n];
}