动态规划总结

动态规划题目特点:

  1. 计数类
  2. 求最大最小值类【最优策略】
  3. 求存在性类

1. 确定状态

确定问题的最后一步【确定最优解的最后一步】
找到最后一步前所有可能:{A, B, C, …},并用其表示出最优解。— 寻找前一步与最后一步关系

2. 转移方程

寻找到的关系

3. 初始条件和边界情况

(1) 初始条件【什么时候停下来?最开始可以人为确定的值F(0)/F(1)–最小问题的最优解】
(2) 边界情况【实现时不要让数组容器越界】

4. 计算顺序

二维的:从左到右,从上到下
从小到大【确定当前问题必须确定子问题的情况】
从大到小【确定当前问题必须确定父问题的情况】

另外一种思路:

  1. 实现暴力递归方法。
  2. 在暴力搜索方法的函数中看看哪些参数可以代表递归过程。
    [本质是利用申请的空间来记录每一个暴力搜索的计算结果,下次要用结果的时候直接使用,而不在进行重复计算的递归过程]
    [动态规划规定每一种递归状态的计算顺序,依次进行计算]
  3. 找到代表递归过程的参数之后,记忆化搜索的方法非常容易实现。
  4. 通过分析记忆化搜索的依赖路径,进而实现动态规划。
    [整理各个状态的依赖关系,简单的容易得到的先计算。例如:二维的第一行第一列,复杂的利用简单的去计算]
  5. 根据记忆化搜索方法改出动态规划方法,进而看看是否能化简,如果能化简,还能实现时间复杂度更低的动态规划方法。

一、最值型

package 动态规划;

/**
 * 〈最值型〉<br>
 *  有n种硬币,分别是2元,5元和7元,每种硬币都有足够多
 *  如果用最少的硬币组合正好付清m元,不需要对方找钱
 *  [其中最少最大可以用动态规划]
 *
 * 1. 最后一步:最后一枚的硬币->ak, 除掉这枚硬币,前面硬币的面值加起来是27-ak
 * 得到子问题,状态:f[i] 就是拼出ai所需的最少硬币
 *  子问题:最后那枚硬币只可能是2,5,7
 *      如果ak是2,f(27)应该是f(27-2) + 1 (加上最后这一枚硬币2)
 *      如果ak是5,f(27)应该是f(27-5) + 1 (加上最后这一枚硬币5)
 *      如果ak是7,f(27)应该是f(27-7) + 1 (加上最后这一枚硬币7)
 *  除此之外没有其他可能了
 *  需要求最少的硬币数,所以:f(27) = min(f(27 - 2) + 1, f(27-5) + 1, f(27-7) + 1)
 *  状态f(x) = 最少用多少枚硬币拼出x
 *
 * 2.转移方程
 *  对于任意x:
 *      f(27) = min(f(27 - 2) + 1, f(27-5) + 1, f(27-7) + 1)
 *
 * 3.初始化条件
 *      f[0] = 0
 *
 *  边界条件
 *
 *
 * 4.计算顺序
 *      计算f[1],f[2],...,f[27],由于f[x]需要用到f[x-2], f[x-5], f[x-7],所以是从前向后
 *
 */
public class Solution1 {
    // {2, 5, 7}  total = 27
    // A是零钱的种类  M是硬币个数
    public int coinChange(int[] A, int M){
        // java中的数组都是从0开始的
        // 0……n:开的数组是[n+1]
        // 0……n-1:开的数组是[n]
        int[] f = new int[M + 1];
        // 零钱的种类
        int n = A.length;

        // 初始化
        f[0] = 0;

        int i, j;
        // f[1], f[2],...,f[27]
        for(i = 1; i <= M; ++i){
            f[i] = Integer.MAX_VALUE;
            // 最后一个硬币A[j]
            // f[i] = min{f[i-A[0]] + 1,..., f[i-A[n-1]] +1}
            for (j = 0; j < n; ++j){
                // 边界情况
                if (i >= A[j] && f[i - A[j]] != Integer.MAX_VALUE)
                    // 转移方程
                    f[i] = Math.min(f[i - A[j]] + 1, f[i]);
            }
        }
        if (f[M] == Integer.MAX_VALUE){
            f[M] = -1;
        }
        return f[M];
    }
}

二、计数型

package 动态规划;

/**
 * <计数型 br>
 *题意:
 * 给定m行n列的网格,有一个机器人从左上角(0,0)出发,每一次可以向右或者向下走一步
 * 问有多少种不同的方式走到右下角?
 *
 * 1.最后一步:无论机器人用何种方式到达右下角,总有最后挪动的一步:向右或者向下
 * 右下角坐标设为(m-1, n-1)
 * 那么前一步机器人一定实在(m-2, n-1)或者(m-1, n - 2)
 * 加入机器人有X种方式从左上角走到(m-2, n-1),有Y中方式从左上角走到(m-1, n - 2),则机器人
 * 有X + Y种方式走到(m-1, n-1) [加法原理-- a.无重复  b.无遗漏]
 *
 * 子问题:
 * 那么此时问题就变成了有多少种方式从左上角走到(m-2, n-1) 和 (m-1, n - 2)【这里有两个变量所以需要开一个二维数组】
 *
 * 状态:设f[i][j] 为机器人有多少种方式从左上角走到(i, j)--所开数组表示的意义
 *
 * 2.转移方程
 *  f[i][j] = f[i - 1][j] + f[i][j - 1]
 *
 * 初始条件
 * f[0][0] = 1,机器人只有一种方式到左上角,就是不动。
 *
 * 边界情况
 * i = 0 或j = 0, 则前一步只能有一个方向过来f[i][j] = 1:只有一种方式
 *
 * 3.计算顺序->由于刚刚好满足下一步计算的要求上边和左边的都计算过
 * f[0][0] = 1
 * 计算第0行: f[0][0], f[0][1], ..., f[0][n-1]
 * 计算第1行: f[1][0], f[1][1], ..., f[1][n-1]
 * ...
 * 计算第m-1行: f[m-1][0], f[m-1][1], ...,f[m-1][n-1]
 *
 */
public class Solution2 {
    public static int uniquePaths(int m, int n){
        // 1. 定义数组
        // 由于只是从0 -> m-1, 0-> n - 1
        int[][] f = new int[m][n] ;

        // 2. 初始化
        f[0][0] = 1;
        // 第0行都只有一种方式
        for (int i = 0; i < n; i ++){
            f[0][i] = 1;
        }
        // 第0列也只有一种方式
        for (int i = 0; i < m; i ++){
            f[i][0] = 1;
        }

        // 3. 定义转移方程--计算顺序按照行算
        for (int i = 0; i < m; ++i){ // 行
            for (int j = 0; j < n; ++j){// 列
                if (i != 0 && j != 0){
                    f[i][j] = f[i-1][j] + f[i][j-1];
                }
            }
        }
        return f[m-1][n-1];
    }
    public static void main(String[] args) {
        System.out.println(uniquePaths(2, 4));
    }
}

三、可行性型

package 动态规划;

/**
 * 〈一句话功能简述〉<br> 
 * 〈可行性型〉
 * 有n块石头分别在x轴的0,1,...,n-1位置
 * 一只青蛙在石头0,想跳到石头n-1
 * 如果青蛙在第i块石头上,它最多可以向右跳距离ai
 * 问青蛙能否跳到石头n-1
 * example:
 *      a=[2, 3, 1, 1, 4]
 *      True
 *      a=[3, 2, 1, 0, 4]
 *      False
 * 1. 最后一步:如果青蛙能跳到最后一块石头n-1, 我们考虑它跳的最后一步
 *      这一步是从石头i跳过来,i < n -1,这需要满足两个条件:
 *          -青蛙可以跳到石头i
 *          -最后一步不超过跳跃的最大距离:n-1-i <= ai [比较好看出来]
 *
 *  子问题:我们需要知道青蛙能不能跳到石头i(i < n - 1),而我们原来求青
 *  蛙能不能跳到n-1
 *  状态:设f[j]表示青蛙能不能跳到石头j---所开数组表示的意义
 *
 *  2.转移方程
 *  设f[j]表示青蛙能不能跳到石头j, OR[遍历的时候只要有一个满足条件](0<=i<j):枚举上一跳到的石头i
 *  f[i] 青蛙能不能跳到石头i, i + a[i] > = j:最后一步不能超过ai
 *  f[j] = OR(0<=i<j)(f[i] AND i + a[i] > = j)
 *
 *  3.初始条件和边界情况
 *  初始条件:f[0] = True,因为青蛙一开始就在石头0
 *  由于遍历时限定了边界所以不会越界
 *
 *  4.计算顺序
 *  f[1], f[2],...,f[n-1]:由于计算f[n]需要用到f[0], f[1], ..., f[n-1],
 *  所以是从左向右计算
 *  答案是f[n-1]
 */
public class solution3 {
    public static boolean exitPath(int[] n){
        // 1.定义数组 -- 状态:f[j]表示能不能跳到石头j
        boolean[] f = new boolean[n.length];

        // 2.初始化条件
        f[0] = true;
        // 转移方程
        for (int i = 1; i < n.length; ++i){
            f[i] = false; // 默认条件
            for (int j = 0; j < i; ++j){
                // 能跳到石头j,并且能够从石头j跳到i
                if (f[j] && n[j] + j >= i){
                    f[i] = true;
                    break;
                }
            }
        }
        return f[n.length-1];
    }

    public static void main(String[] args) {
        int[] a = new int[]{3, 2, 1, 0, 4};

        System.out.println(exitPath(a));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值