动态规划(Dynamic Programming),何掌划算简称DP,握动这个名字给人的态规套路感觉是一种非常高大上非常复杂的算法,很多同学看到这个名字可能就会望而却步,何掌划算在面试的握动时候也非常害怕被问到动态规划的题目。实际上,态规套路它并不是何掌划算不是一种确定的算法,它是握动一种最优化的方法求解问题的思想或方法。它是态规套路由美国数学家贝尔曼(Bellman)在研究多阶段决策过程的优化问题时提出。不过,何掌划算与之对应的握动还有一些与时间无关的静态规划,如:线性规划、态规套路非线性规划等。何掌划算在运筹学中,握动动态规划是态规套路的非常重要的内容,在各个行业领域都有着广泛的源码库应用。我们如何理解动态规划? 如果一个问题的最优解可以通过其子问题的最优解经过推导得到,那么,我们就可以先求出其子问题的最优解,根据子问题的解得出原问题的最优解。如果子问题有较多的重复出现,为了减少重复计算,降低时间复杂度,则可以自底向上从最终子问题向原问题逐步求解并先将子问题存储起来,在求解大的子问题时可以直接从表中查询子问题的解,这就是动态规划的基本思想。 简单来来理解就是将一个大问题简化成若干子问题,并存入一个表中,再根据数据表中子问题的解求出大问题的解。这种算法看上去是不是很熟悉?其实,动态规划和分治算法类似,我们也常常将其和分治算法进行比较。它们都需要将其分解成若干子问题并求解子问题。亿华云计算不同的是分治算法是自顶向下求解各子问题,然后合并子问题的解从而得到原问题的解;而动态规划是将子问题拆解之后,自底向上求解子问题的解并将存储结果存储起来,在求解大的子问题时直接查询子问题的解,算法效率也将大大的提高。 理论描述太过生硬和枯燥,我们直接来看一个例子。 斐波那契数列 斐波那契数列 斐波那契数列是一个非常神奇的数列,它由意大利数学家莱昂纳-斐波那契提出,其特征是数列某一项的值是前两项的和,也可以称作黄金分割数列。 莱昂纳多·斐波那契 我们可以用下面的通项公式来表示斐波那契数列。 从斐波那契数列的公式中可知,数列的第n(n>2)项的服务器租用值f(n)等于f(n)+f(n-1),如果要求得f(n)值就需要先求得f(n-1)和f(n-2)的值,为了便于分析,我们当假设n=6,我们可以按照下图进行分解,一步步分解成小的值。 斐波那契 看了上面的图,想必大家脑海中一种想到了程序的实现,我们可以直接通过递归的方法就可以求出n项的值,程序很容易,如下所示。 但是,很明显这种算法是指数时间复杂度O(2^n),其复杂度会随着n的增加成指数增长,当n取到一定大时,将需要很长的时间,显然这不是一种最优的算法。不过,仔细观察上图的各个分解项,我们会发现图中有很多重复的子项,这就是上面这种递归算法复杂度较高的原因。那么,还能不能进行优化呢?答案是肯定的。 我们可以通过动态规划的思想来优化上面这个算法,为了避免大量的重复计算,我们可以从最底层的子问题开始计算,并通过一个表来存储这些子问题的值,当再次遇到这个值就不需要再重新计算。 如下面的程序,我们从最小的子问题n=1,2开始向上计算,并且定义了一个vector容器用来存储被计算过的子问题的值,下次再计算大问题时直接调用容器里的值即可。 很明显上面的这种算法,大大降低了算法的时间复杂度,现在的时间复杂度就是O(n)了。不过,虽然时间复杂度降低了,这却是牺牲了空间换取过来的。实际上我们还可以进一步去优化,从公式上我们分析可以看出,要求出某一项的值我们需要先求出其前两项子问题的值,当我们自下而上求解子问题的过程中,我们直接保存连续两项子问题的值即可。 最长上升子序列 严格意义上来说,上面的斐波那契数列也不完全算是动态规划问题。因为从动态规划的定义上来看,动态规划问题一般满足三个性质: 根据动态规划问题的这三个性质我们再看另外一个例子,最长上升子序列(Longest Increasing Subsequence)问题,简称LIS,这是一个非常经典的动态规划问题。 有一个长度为n的数列a0, a1, ..., a(n-1),求出这个序列中最长的上升子序列的长度。所谓上升子序列指的是对于任意的i 我们先将原问题进行分解,依次拆解成子问题,如下表: 子序列 我们的代码可以按照下面来实现,其中,程序里我们用dp数组保存各个子序列以nums[i]结尾的最长子序列长度,max存储最长子序列的长度。 通过上面的两个例子,大家都学废了吗?常见的还有很多问题可以使用动态规划的方法解决,比如,背包问题,硬币找零,最短路径等。动态规划不是一种固定的算法,对应的问题也是多种多样,但大家只要掌握了其基本的思想,就可以轻松的解出相应的问题,大家赶快去尝试一下吧! 本文转载自微信公众号「Will的大食堂」,可以通过以下二维码关注。转载本文请联系Will的大食堂公众号。