zoukankan      html  css  js  c++  java
  • 动态规划

    1 解题要点

    动态归划是用来优化递归重复计算缺点的,其中主要思想是用空间换时间,即手动设置一张表格,自底向上将表格填满,从而能够将递归O(2^n)时间复杂度优化到O(n^2)或者其它幂函数级别。

    1.1 何时知道该使用动态归划解决问题呢?

    1. 当题目中所求解决问题为以下三种类型时,常常可以用动规解决:
    1. 求最大值或者最小值;
    2. 统计方案个数;
    3. 判断是否可行;
    1. 当然,以上三种类型问题通常也可以通过递归解决,但是如果题目有严格的执行时间要求,却对空间没有任何限制,应当直接考虑动规;

    2.2 解决一道动规题目应该从哪几个方面分析问题?

    1. 状态函数
      状态函数是一道动规题目最核心的部分,想到正确的状态函数后应当立即写下,后续步聚随时用来理清思路。状态函数分析技巧:
    1. 首先判断状态的维度,一般题目输入为数组的个数有关系,当然背包类型问题会把容量作为一个新的维度;
    2. 因为前i个数据的状态包含前i-1个数据的状态, 这样规定有得于推导出状态方程;
    3. 状态函数的描述常常包含关键字:前i个数据,恰好,能否,的最大值(最小值);
    4. 状态函数的正确与否通常还需要结合状态方程才能判断;
    1. 状态方程
      用来联系状态之间的关系,一般通过第i个元素的值来判断,f[i]与之前状态的联系,通常是:条件判断 + min, max, 或 + const,来将当前状态与之间状态联系起来。当然,如果状态方程难以建立,那么也有可能是状态函数设定错误。

    2. 初始状态
      因为状态方程通常包含f[i-1]或其它之前的状态,这要求我们在填表之前就需要把表格的边界部分填充完毕,这样才能根据边缘部分值来推导出表格内部元素的值。

    1. 通常需要确定f[i][0]与f[0][j] (一维状态函数也是同理),这时候根据严格根据状态函数的定义来推断边缘状态的值即可;
    2. 为了让状态方程的形势统一,并且也为了使用初始化不那么麻烦,有时候会多加1行1列,并给定一些特殊值:0,false, INT_MAX, INT_MIN等,这种方法大多数情况都比较有效。但有时候增加虚拟行列反而会使用答案变得繁琐,并且出bug了不容易调,如min_path_sum。所有有时虚拟行列的状态不能十分确定其值,就不使用了。
    1. 答案所在位置
      通常情况下,最右下角(二维),或者最右端(一维),对应的元素为最终所求解的答案,但是也有一些特例需要再最后一行遍历次才能知道,如backpacklongest-common-substringlongest-increasing-subsequence

    2 四种经典动规题型

    1. 矩阵中路径规划 (10%)
    1. 此类型题目的状态方程比较容易写出,因为矩阵中的每个点只能由左边位置状态与上边位置状态转移过来;
    2. 这种类型题目如何不设置虚拟行列不会对初始化造成太大麻烦,就考虑不加虚拟行列,如unique-paths
        int uniquePaths(int m, int n) {
            int **f = new int*[m];
            for (int i = 0; i < m; i++) {
                f[i] = new int[n];
            }
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) {
                    if (i == 0 || j == 0) {
                        f[i][j] = 1;
                    }
                    else {
                        f[i][j] = f[i-1][j] + f[i][j-1];
                    }
                }
            }
            return f[m-1][n-1];
        }
    
    1. 单序列的动规 (40%)
    1. 状态函数常常描述为前i个数字/字符的 最优解?/能否达到最优解?
    2. 代表问题:
      jump-game
      palindrome-partitioning-ii
    1. 双序列的动规 (40%)
    1. 状态函数常常描述为A序列的前i个数字/字符 与 B序列的前j个数字/字符 所达到的最优解?/能否达到最优解?
    2. 通过讨论第i个字符与第j个字符的是否相等来将当前状态与之前状态联系起来;
    3. 代表问题:
      longest-common-subsequence
      edit-distance
    1. 背包类型动规 (10%)
    1. 背包问题的一个特征是,不仅题目给定的序列要作为DP表的一个维度,而且题目中给定的一些变量或者常量都有可能作为DP表的一个新维度,在建立状态方程时,这个新维度变化通常是离散调整的,如背包问题中的$ f[i-1][j-A[i-1]]$。
    2. k sum: 当题目中没有说明k的值,要想到使用动态规划解决。当k=2时,即为two sum问题,要想到来用两根指针的O(n)方法解决。
    3. 经典背包问题解析
      backpack,这题目是比较难想的,因此做为经典题应该反复理解。
      (1)状态函数:背包问题的给定数据有点类似于单序列问题,这让我会习惯性假设 “前i-1个元素的最优解,来推导前i个元素的最优解”,但是在推导状态方程时会发现,前i-1个元素如果达到最优了,那么背包余下容量可能不能装下一个更大的物品,使得前i个元素达到最优,这时候前i-1个元素与前i个元素的状态关系难以建立。如,m = 12,A = [2, 3, 5, 7]。而正确的状态函数是bool型的,描述为:前i个数能否挑一些恰好组成和为j,是为数不多的Yes/No型动规问题
      (2)状态方程:如果当前容量能够装下第i个物品A[i-1] (即j >= A[i-1] ?)并且 前i-1个物品跳出一些恰好能够装入剩余j-A[i-1]的容量中,那么:装入第i个物品,并且立flag说明:前i个物品挑出一些恰好能装入容量为j的背包中。否则:不能,不装第i个物品,并询问前i-1个物品挑出一些能否恰好装入容量为j的背包中,以此来决定f[i][j]为true or false。
      (3)初始化的过程:
      考虑f[i][0]:前i个数,一个都不取,和恰好为0,所有全部分初始化为true
      考虑f[0][j]:前0个数,无论怎么取,和不都可能达到j (j != 0),因此全部初始化为false
      (4)因为我们的状态函数表示恰好能组成和为j,那么f[nums][m]不一定为true,因为m可能比我们得到的最大物品重量和要多一点余量,所以要向前找第一个为true的j
      code
        int backPack(int m, vector<int> A) {
            int nums = A.size();
            vector<vector<bool>> f;
            for (int i = 0; i <= nums; i++) {
                f.push_back(vector<bool>(m + 1, false));
            }
            for (int i = 0; i <= nums; i++) {
                f[i][0] = true;
            }
            for (int i = 1; i <= nums; i++) {
                for (int j = 1; j <= m; j++) {
                    f[i][j] = f[i-1][j];
                    if (j >= A[i-1] && f[i-1][j-A[i-1]]) {
                        f[i][j] = true;
                    }
                }
            }
            for (int j = m; j >=0; j--) {
                if (f[nums][j]) {
                    return j;
                }
            }
            return m;
        }
    

    3 值得回味的题目

    min_path_sum
    palindrome-partition II:难点在于优化时间复杂度,并且用DP求回文串
    longest-increasing-subsequence:难点在于状态方程的含义如何定义?如何推出状态转移方程?
    backpack ii
    min-adjust-cost

  • 相关阅读:
    LeetCode算法题-Find Pivot Index(Java实现)
    LeetCode算法题-Longest Word in Dictionary(Java实现)
    LeetCode算法题-1-bit and 2-bit Characters(Java实现)
    2016-8-4学习正则表达式
    doT 这个模板 是怎么实现的?
    manually Invoking Model Binding / Model Binding /Pro asp.net mvc 5
    隐隐约约 听 RazorEngine 在 那里 据说 生成代码 很 美。
    web api 的 安全 认证问题 , 对外开放 的 时候 需要考虑到安全的问题
    鼠标滑过显示图片
    页面加载中效果实现
  • 原文地址:https://www.cnblogs.com/fariver/p/7156663.html
Copyright © 2011-2022 走看看