如何掌握动态规划算法的套路?

动态规划(Dynamic Programming),简称DP,这个名字给人的感觉是一种非常高大上非常复杂的算法,很多同学看到这个名字可能就会望而却步,在面试的时候也非常害怕被问到动态规划的题目。实际上,它并不是不是一种确定的算法,它是一种最优化的方法求解问题的思想或方法。它是由美国数学家贝尔曼(Bellman)在研究多阶段决策过程的优化问题时提出。不过,与之对应的还有一些与时间无关的静态规划,如:线性规划、非线性规划等。在运筹学中,动态规划是的非常重要的内容,在各个行业领域都有着广泛的应用。我们如何理解动态规划?

如果一个问题的最优解可以通过其子问题的最优解经过推导得到,那么,我们就可以先求出其子问题的最优解,根据子问题的解得出原问题的最优解。如果子问题有较多的重复出现,为了减少重复计算,降低时间复杂度,则可以自底向上从最终子问题向原问题逐步求解并先将子问题存储起来,在求解大的子问题时可以直接从表中查询子问题的解,这就是动态规划的基本思想。

简单来来理解就是将一个大问题简化成若干子问题,并存入一个表中,再根据数据表中子问题的解求出大问题的解。这种算法看上去是不是很熟悉?其实,动态规划和分治算法类似,我们也常常将其和分治算法进行比较。它们都需要将其分解成若干子问题并求解子问题。不同的是分治算法是自顶向下求解各子问题,然后合并子问题的解从而得到原问题的解;而动态规划是将子问题拆解之后,自底向上求解子问题的解并将存储结果存储起来,在求解大的子问题时直接查询子问题的解,算法效率也将大大的提高。

理论描述太过生硬和枯燥,我们直接来看一个例子。

斐波那契数列

斐波那契数列

斐波那契数列是一个非常神奇的数列,它由意大利数学家莱昂纳-斐波那契提出,其特征是数列某一项的值是前两项的和,也可以称作黄金分割数列

莱昂纳多·斐波那契

我们可以用下面的通项公式来表示斐波那契数列。

f(n)= \begin{cases} 1& \text{n=1,2}\\ f(n-1) + f(n-2)& \text{n>2} \end{cases}

从斐波那契数列的公式中可知,数列的第n(n>2)项的值f(n)等于f(n)+f(n-1),如果要求得f(n)值就需要先求得f(n-1)f(n-2)的值,为了便于分析,我们当假设n=6,我们可以按照下图进行分解,一步步分解成小的值。

斐波那契

看了上面的图,想必大家脑海中一种想到了程序的实现,我们可以直接通过递归的方法就可以求出n项的值,程序很容易,如下所示。


int fib(int n)
{
    if(n==1 || n==2) return 1;
    return fib(n-1) + fib(n-2);
}

但是,很明显这种算法是指数时间复杂度O(2^n),其复杂度会随着n的增加成指数增长,当n取到一定大时,将需要很长的时间,显然这不是一种最优的算法。不过,仔细观察上图的各个分解项,我们会发现图中有很多重复的子项,这就是上面这种递归算法复杂度较高的原因。那么,还能不能进行优化呢?答案是肯定的。

我们可以通过动态规划的思想来优化上面这个算法,为了避免大量的重复计算,我们可以从最底层的子问题开始计算,并通过一个表来存储这些子问题的值,当再次遇到这个值就不需要再重新计算。

如下面的程序,我们从最小的子问题n=1,2开始向上计算,并且定义了一个vector容器用来存储被计算过的子问题的值,下次再计算大问题时直接调用容器里的值即可。

int fib(int n)
{
    vector<int> dp(n, 0);

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

    return dp[n-1];
}

很明显上面的这种算法,大大降低了算法的时间复杂度,现在的时间复杂度就是O(n)了。不过,虽然时间复杂度降低了,这却是牺牲了空间换取过来的。实际上我们还可以进一步去优化,从公式上我们分析可以看出,要求出某一项的值我们需要先求出其前两项子问题的值,当我们自下而上求解子问题的过程中,我们直接保存连续两项子问题的值即可。

int fib(int n)
{
    int dp[2]={1,1};

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

    return dp[1];
}

最长上升子序列

严格意义上来说,上面的斐波那契数列也不完全算是动态规划问题。因为从动态规划的定义上来看,动态规划问题一般满足三个性质:

  • 最优化原理:如果原问题的最优解所分解出的子问题的解也是最优的,我们就称该问题具有最优子结构,原问题的最优解可以有子问题的最优解推导得出;
  • 无后效性:某阶段状态一旦确定,这个状态以后决策的影响,它只与当前状态有关;
  • 有重叠子问题:子问题可能会在下一阶段决策中被重复多次用到。

根据动态规划问题的这三个性质我们再看另外一个例子,最长上升子序列(Longest Increasing Subsequence)问题,简称LIS,这是一个非常经典的动态规划问题。

有一个长度为n的数列a0, a1, ..., a(n-1),求出这个序列中最长的上升子序列的长度。所谓上升子序列指的是对于任意的i<j都满足a(i)<a(j)的子序列。例如,一个序列为{6, 3, 5, 4, 7, 8, 1, 10},那么,它的最长上升子序列为{3, 5, 7, 8, 10}{3, 4, 7, 8, 10}

我们先将原问题进行分解,依次拆解成子问题,如下表:

子序列

我们的代码可以按照下面来实现,其中,程序里我们用dp数组保存各个子序列以nums[i]结尾的最长子序列长度,max存储最长子序列的长度。


int maxLIS(std::vector<int>& nums)
{
    int max = 1;
    std::vector<int> dp(nums.size(), 1);

    for(int i = 1;i< nums.size(); i++)
    {
        for(int j=0; j<i; j++)
        {
            if(nums[i]>nums[j])
            {
                dp[i] = dp[j] + 1;
            }
            max = std::max(dp[i], max);
        }
    }

    return max;
}

通过上面的两个例子,大家都学废了吗?常见的还有很多问题可以使用动态规划的方法解决,比如,背包问题,硬币找零,最短路径等。动态规划不是一种固定的算法,对应的问题也是多种多样,但大家只要掌握了其基本的思想,就可以轻松的解出相应的问题,大家赶快去尝试一下吧!

本文首发公众号:Will的大食堂,转载请联系微信:yuzaiduzhong。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容