【算法导论】No.16 动态规划

一、动态规划:通过组合子问题的解来解决问题。

      动态规划对于每个子问题只求解一次,将结果保存在一张表里。

      通常用于求解最优化问题

二、动态规划算法设计步骤:

  1. 描述最优解结构
  2. 递归定义最优解的值
  3. 按自底向上的方式计算最优解
  4. 由计算出的结果构造一个最优解

转自:https://www.cnblogs.com/tgycoder/p/5037559.html 侵删

免责声明:转发是因为我觉得好使,你若是觉得看着不顺眼,出门右滚不送。

 

 

动态规划(dynamic programing)和分治法类似,都是通过组合子问题的解来求解原问题的解。(在经典排序算法中的二路归并排序和快速排序都用到了分而治之的思想-分治法)。

分治法是将原问题划分为没有交集,相互独立的子问题,并分别求解后再进行合并,求出原问题的解。

动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题。分治法会做许多不必要的工作,它会反复地求解那些公共子问题。动态规划算法对每个子问题只求解一次,将其解保存在一个表格中,从而无需每次求解一个子子问题时都需要重新计算。

动态规划上用来求解最优化问题(optimization problem)。

可以按照下面四个步骤来设计一个动态规划算法:

1、刻画一个最优解的结构特征。

2、递归地定义最优解的值。

3、计算最优解的值,通常采用自底向上的方法。

4、利用计算出的信息构造一个最优解。

最优子结构

用动态规划求解最优化问题的第一步就是刻画一个最优解的结构特征。如果一个问题的最优解包含其子问题的最优解,我们称此问题具有最优子结构性质。因此,某个问题是否适合用动态规划,它是否具有最优子结构性质是一个好的标准。使用动态规划方法时,我们用子问题的最优解来构造原问题的最优解。

如何发掘最优子结构的性质?

1、证明问题最优解的第一个组成部分是做出一个选择,而做出这个选择将会产生一个或多个待解的子问题。

2、对一个给定问题,在其可能的第一步选择中,假定已经知道哪种选择才会得到最优解。而我们并不关心这种选择具体是如何得到的,只是假定已经知道了这种选择。

3、给定获取的最优解选择后,确定这次选择会产生哪些子问题,以及如何最好地刻画子问题空间。

4、利用“剪切-粘贴(cut and paste)”技术证明作为构成原问题最优解组成部分,每个子问题的解就是它本身的最优解。

反证法:假定子问题的解不是自身的最优解,那么我们就可以从原问题中剪切掉这些非最优解,将最优解粘贴进去,从而得到原问题一个更优的解,这个解与最初的解的前提假设矛盾。

刻画子问题空间的经验

保持子问题空间尽量简单,只在必要时才扩展它。例如下一节的例子,求钢条切割的最大收益问题中,子问题空间包含的问题为:对每个i值,长度为i的钢条最优切割问题。

对于不同问题领域,最优子结构的不同体现在两个方面:

  1. 原问题的最优解中涉及到多个子问题。
  2. 在确定最优解使用哪些子问题时,需要考察多少种选择。

重叠子问题

适合用动态规划方法求解最优化问题的第二个性质是子问题的空间必须足够小,即问题的递归算法会反复地求解相同的子问题,而不是一直生成新的子问题。动态规划算法会对重叠的子问题只求解一次,并保存在一张表里,需要用的时候直接查表即可,每次查表的时间代价为常量O(1)。

写代码是一种艺术,甚于蒙娜丽莎的微笑。

 

 

62. Unique Paths

m行n列的数组,只能往下或者往右走,看走到最后一个元素有多少种走法

Example 1:

Input: m = 3, n = 2
Output: 3
Explanation:
From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
1. Right -> Right -> Down
2. Right -> Down -> Right
3. Down -> Right -> Right

Example 2:Input: m = 7, n = 3 Output: 28
/**
     * 最后一列初始化为1,然后dp[i][j]=dp[i][j+1] + dp[i+1][j]
     * @param m
     * @param n
     * @return
     */
    public int uniquePaths(int m, int n) {
        int[][] dp = new int[m][n];
        //初始化最后一列为1
        for(int i=0;i<m;i++){
            dp[i][n-1]=1;
        }

        //初始化最后一行为1
        for(int j=0;j<n;j++){
            dp[m-1][j]=1;
        }

        //从右向左 从下至上更新
        for(int j=n-2; j>=0;j--){
            for (int i=m-2;i>=0;i--){
                //等于同一行+同一列相邻的邻居走法之和
                dp[i][j] = dp[i][j+1] + dp[i+1][j];
            }
        }
        return dp[0][0];
    }

L120:Triangle

Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

For example, given the following triangle

[
     [2],
    [3,4],
   [6,5,7],
  [4,1,8,3]
]
The minimum path sum from top to bottom is 11   (i.e., 2 + 3 + 5 + 1 = 11).
public int minimumTotal(List<List<Integer>> triangle) {
        int rows = triangle.size(); //行数
        int cols = triangle.get(rows-1).size();//列数
        int[][] dp = new int[rows][cols];
        dp[0][0] = triangle.get(0).get(0);

        for(int row=1;row<rows; row++){
            List<Integer> curRow = triangle.get(row);//本行的所有shu
            int col =0;
//            dp[1][0]、dp[2][0]这种左边界值
            dp[row][col] = dp[row-1][col] + curRow.get(col);
            col=1;
            //遍历找最大的
            for(; col <curRow.size()-1;col++){
                dp[row][col] = Math.min(dp[row-1][col-1],dp[row-1][col])+curRow.get(col);
            }

            //右边界,上一行累计下来的最小值+右边界
            dp[row][col] = dp[row - 1][col - 1] + curRow.get(col);
        }

        //最后一行取值
        int min = Integer.MAX_VALUE;
        for(int col=0; col <cols; col++){
            min = Math.min(min, dp[rows-1][col]);
        }
        return min;
    }
L64

/**
     * 状态转移方程:dp[x][y] = Min(dp[x+1][y],dp[x][y+1]) + grid[x][y]
     *  从左边和上边选一个最小的,再加上当前点的值,就是路过该点的最小和
     *  1 4 5
     *  2 7 6
     *  6 8 7这种,最小7,返回7
     *
     * @param grid
     * @return
     */
    public int minPathSum(int[][] grid) {
        int m = grid.length; //行
        int n = grid[0].length;//列

        //因为第一行只依赖左边的值,更新第一行的权值
        for(int i=1; i < n;i++){
            grid[0][i] = grid[0][i-1]+grid[0][i];
        }

        //因为第一列只依赖上边的值,更新第一列的值
        for(int j=1; j<m;j++){
            grid[j][0] = grid[j][0] + grid[j-1][0];
        }

        //递推公式更新其他的状态值
        for(int i=1;i<m;i++){
            for (int j=1;j<n;j++){
                grid[i][j] = Math.min(grid[i-1][j], grid[i][j-1])+ grid[i][j];
            }
        }
        return grid[m-1][n-1];//这样算下来,最后一个元素就是最小的值
    }

 

    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值