动态规划就是1+1

岑眰
• 阅读 1609

动态规划就是1+1

1 - 什么是动态规划?

    首先引用一下Wikipedia的词条,我们来认识一下动态规划。

Dynamic programming is both a mathematical optimization method and a computer programming method. The method was developed by Richard Bellman in the 1950s and has found applications in numerous fields, from aerospace engineering to economics.

In both contexts it refers to simplifying a complicated problem by breaking it down into simpler sub-problems in a recursive manner. While some decision problems cannot be taken apart this way, decisions that span several points in time do often break apart recursively. Likewise, in computer science, if a problem can be solved optimally by breaking it into sub-problems and then recursively finding the optimal solutions to the sub-problems, then it is said to have optimal substructure.

If sub-problems can be nested recursively inside larger problems, so that dynamic programming methods are applicable, then there is a relation between the value of the larger problem and the values of the sub-problems.[1] In the optimization literature this relationship is called the Bellman equation.

    其中最重要的一句话是"it refers to simplifying a complicated problem by breaking it down into simpler sub-problems in a recursive manner."。意思是“动态规划使用递归的方式将一个复杂的问题分解为更简单的子问题解决问题。”。乍一听,感觉动态规划比较晦涩,像是个深奥的数学问题。其实,我们每天都在接触动态规划。给大家举个栗子,我们在计算1 + 1的时候,脑子条件反射般的算出答案,没错,等于2。但是当我们算1 + 1 + 1 + 1的时候,大家就会算得1 + 1 = 2,然后2 + 1 = 3, 最后3 + 1 = 4,这也正是是乘法的本质。动态规划的原理也不过如此,将复杂的问题分解为简单的子问题。

    我们通过这个例子,不难发现其中几个需要注意的点。第一,做这个"1+1"难题的时候,最重要的是什么?是我们知道后一项等于前一项加一,所以我们发现,动态规划不仅仅是将大问题化简为小问题,在这其中有一个必要条件,那就是大问题和子问题之间的关系,我们把它叫做“状态转移“。在此例中,状态转移的规律非常明显。a[n] = a[n-1] + 1我们把这种状态转移的公式化描述叫做状态转移方程。第二个需要注意的点也很容易找到,我们总是用前一项推导后一项的值,那么最前面的一项用什么来推导呢?答案是无法推到。那么我们指定一个初始值,方能使用状态转移方程推导复杂问题,我们管这个初始值叫做初始状态。这有点类似于虹吸原理。可能大家都有过这样的经历,水缸里有一缸水,我们要将其排出,那最简单的办法是什么呢?让其利用虹吸原理自动流出来,那最核心的步骤就是,我们首先需要在管子的一头用嘴巴吸出第一口水,给其一个初始动能。这就是初始状态的意义。

2 - 斐波那契数列和动态规划的渊源

    众所周知,斐波那契的定义是a[n] = a[n-1] + a[n-2],我们能很明显的发现其中的一项是由前两项的和组成的。那么这不久刚好匹配了动态规划的定义吗?我们来尝试使用动态规划的思想求解斐波那契数列,感受一下动态规划为什么高效。

public static int fib(int n) {
        // 边界检查
        if (n <= 0) throw new IllegalArgumentException("n 必须大于0");
        if (n <= 2) return n;

        // 确定状态 (开一个数组,明确数组的索引和值之间的对应关系  例如: 第i个元素表示fib(i))
        int[] dp = new int[n];

        // 初始状态
        dp[0] = 1;
        dp[1] = 1;

        // 状态转移
        for (int i = 2; i < n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n - 1];
    }

    在我们实践中发现,还有两个步骤是必不可少的,边界检查和确定状态,确定不了状态无法用程序表示出状态转移方程,没有边界检查,我们就无法保证程序正确运行。所以这四个步骤,就是动态规划的全部。用班主任的口吻来讲,动态规划是万变不离其宗!

3 - 实践出真知

    我们通过几个例子,来巩固一下动态规划的解题思路。

70. 爬楼梯

    印象非常深刻,这是美团曾经的一道笔试题,属于非常简单的入门题了。跟斐波那契数列一个接替思路,唯一要注意的是初始状态的界定。

    public int climbStairs(int n) {
        // 边界检查
        if(n <= 0) return 0;
        if(n > 0 && n <= 2) return n;

        // 确定状态
        int[] dp = new int[n];

        // 初始状态
        dp[0] = 1;
        dp[1] = 2; // FBI WARNING !!!

        for(int i = 2; i < n; i++){
            dp[i] = dp[i-1] + dp[i-2];
        }

        return dp[n - 1];
    }
300. 最长上升子序列

    这道题的难点在于如何找到状态转移方程,这也是绝大多数动态规划题目的难点,我们对此进行分析。首先要确定状态,我们的dp数组究竟表示什么?一般来讲,题目的结果是什么,dp数组的值就是什么,然后再去找索引对应的含义,这是个阅读理解题……。”dp[i]表示以nums[i]结尾的LIS的长度“,这就是我们这道题中状态的含义,我们待会以此推出状态转移方程。

public int longestCommonSubsequence(String text1, String text2) {

        int m = text1.length(), n = text2.length();
        // dp[i][j] 表示: 字符串 str1[0:i] 和字符串 str2[0:j] 的最大公共子序列
        int[][] dp = new int[m+1][n+1];
        // 进行状态转移
        for(int i = 1; i <= m; i++){         
            for(int j = 1; j <= n; j++){
                if(text1.charAt(i-1) == text2.charAt(j-1)){ // 若两个字符相等,必然可以构成子问题的最优解
                    // 这个字符存在于 lcs 之中
                    dp[i][j] = dp[i-1][j-1] + 1; 
                }else{
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);  
                }
            }
        }
        return dp[m][n];
    }

动态规划的题目还有很多,例如求最值,博弈等,最大的难点就是找状态转移方程,只有多刷题才能够完全掌握,学得基本原理之后,接下来就是联系啦!

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Kubrnete Kubrnete
4年前
基于01背包问题的动态规划算法
目录初步认识动态规划(初步认识动态规划)与动态规划有关的理论知识:(与动态规划有关的理论知识:)动态规划中的最优决策表(基于填表的动态规划算法)最终版动态规划(最终版动态规划)总结(总结:)初步认识动态规划动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推
Kubrnete Kubrnete
4年前
动态规划之马拉车算法
问题描述:给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。如"abc"有三个回文子串‘a','b','c'.示例1:输入:"abc"输出:3解释:三个回文子串:"a","b","c"示例2:输入:"aaa"输出
Wesley13 Wesley13
3年前
ACM金牌大神侯卫东老师的四步动规解题秘籍!请收下
近年来,国内外科技公司的算法面试中,动态规划几乎成了必考题型。动规题目类型众多,又没有固定的解题模板,初学者往往摸不着头脑,有时还会混淆动规和递归,所以动态规划又被称为“新人杀手”。不过动态规划的难,更多是因为初学者不知道怎么入门。学会正确的思考模式和解题流程,掌握动态规划其实并不难。九章侯卫东老师针对所有动态规划题型,总结了一套
Stella981 Stella981
3年前
A Mini Locomotive(动态规划 01)
 /\ 题意:选出3个连续的数的个数 为K的区间,使他们的和最大分析: dp\j\\i\max(dp\jk\\i1\value\j\,dp\j1\\i\);dp\j\\i\:从j个数种选出i个连续区间 数值的最大和value\j\:第j个区间内的数的和和背包有点像,但要活用\
Wesley13 Wesley13
3年前
01背包问题(动态规划求解)
这两天c的习题开始不考察c了,开始考察动态规划问题,唉,没学过动态规划算法来编这题目真是一把辛酸泪,下面给出题目(题目来源:郭玮老师的mooc)2:CharmBracelet查看提交统计提问总时间限制:1000ms内存限制:65536kB描述Bessiehasgonetothemall’s
菜园前端 菜园前端
2年前
什么是动态规划?
原文链接:什么是动态规划?动态规划也是算法设计的一种方法/思想。它将一个问题分解为相互重叠的子问题,通过反复求解子问题,来解决原来的问题。基础案例场景一斐波那契数列当前数等于前面两个数的和。定义子问题:f(n)f(n1)f(n2)
贾蔷 贾蔷
3个月前
力扣1137题 解题思路和步骤 C++代码实现,力扣一共多少题
一、题目分析力扣1137题要求我们找到第N个泰波那契数。泰波那契数的定义是:T00,T11,T21,且在n0的条件下Tn3TnTn1Tn2。,当n4时,T4T3T2T14。这道题主要考查我们对递归或动态规划的理解和运用。在思考解题方法时,我们
贾蔷 贾蔷
2个月前
2023年GESP六级题解:洛谷P10108闯关游戏动态规划解法详解
一、题目解读本文针对2023年GESP六级题目“闯关游戏”(洛谷P10108)进行详细解析。题目要求玩家通过不同关卡路径选择,计算从起点到终点的最大得分。关卡间存在跳跃规则,需结合动态规划思想设计高效算法,最终输出最优得分。二、解题思路采用动态规划(Dyn