「动态规划后篇」:适用场景

1 你会学到什么?

前三天的推送都是关于动态规划算法的,先通过一个《装水最多的容器》初步感受了动态规划是怎么一回事,相比于直观的枚举算法,它能使求解更快地收敛;之后,推送了求解有效括号对的最大数,在求解过程中,根据两种情况分别建立了递推公式;接着解决了动态规划常常需要一个O(n)或更大的空间以及这样做得到个回报,即效率上的提升,并通过一个典型的爬楼梯的实际例子进一步验证了空间确实能够换取时间。

经过以上三天对动态规划的初步研究后,今天我们将要解决的问题只有一个,也是最为核心的问题,那就是什么样的问题适合用动态规划?

我想这也是大家一定想知道的。

2 核心问题

动态规划是好,但是我怎么就能知道我这个问题就能用动态规划来求解呢?

我们还得从面对的这个实际问题具有的特征入手吧,如果它符合动态规划的主要条件,那么就可以尝试用动态规划。

一般地,动态规划需要求解的问题具有2个核心特征:
optimal-substructure property,最优化的子结构属性
overlapping subproblems,重叠的子问题集

下面,解释什么是最优化的子结构属性。如果一个问题的最优解包含在一系列的子问题集的最优解,那么它就称为具有最优化的子结构属性。

还是拿爬楼梯的例子,如果想求解爬到第 i 层楼梯的所有方法,不就是相当于以下这个子问题 (subproblem) 吗:
求爬到第 i-1层的所有方法,和爬到第 i-2层的所有方法,两者的累计和,然后爬到第 i-1层的所有方法。

那么爬到第 i-1 层的所有方法,不又是一个子问题吗,相对于爬到第 i 层的方法不就叫 subsubproblem 吗!

这个爬楼梯问题是非常明显地具有最优化的子结构属性。

再来看下重叠的子问题是怎么定义的,下面是算法导论上解释

When a recursive algorithm revisits the same problem repeatedly, we
say that the optimization problem has overlapping subproblems.

意思是说,当一个递归算法再次访问同一个问题时,这种事会出现一遍又一遍的,我们说这个最优化问题有重叠的子问题集。

这个有点抽象,我试着解释下,还是爬楼梯,如果我们分别求出了爬到第 i-1、第 i-2 层的所有方法,那么在求解爬到第 i 层的所有方法时,我们还得去解决爬到第 i-1、第 i-2 层的所有方法,这就是重叠的子问题。

当然,动态规划算法此时不会如此的傻,它会开辟一段内存,记录下爬到第 i-1、第 i-2 层的所有方法,然后在用到的时候直接 look up 了,这也是昨天提到的用空间换取时间的方法。

好了,以上便是两个动态规划问题常常具备的特征,一般地,我们待解决的问题满足了这两个特征,就值得试一试动态规划。

下面再看一个应用动态规划求解longest common subsequence (LCS) 的例子。

3 LCS

LCS的概念
给定两个字符串 x 和 y, 它们的公共最长子序列具有的长度。 这个概念容易让人误解,以为最长的子字符串必须是相邻的。其实是不必的! 例如 X = “ABCBDAB” , Y = “BDCABA”,则 最长子序列长度为4,为 B C A B

3-1 例子分析

为什么可以用动态规划来求解? 看看满足那两个特征吗。第一,是最优化的子问题吗? 是!

以下分析是LCS最重要的部分

如果字符串 Xm 和 字符串 Yn 的 X.charAt(m) 和 Y.charAt(n) 相等,那么最长子字符串一定等于字符串 Xm-1 和 字符串 Yn-1 的最长子字符串再加上这个相等字符。

如果字符串 Xm 和 字符串 Yn 的X.charAt(m) 和 Y.charAt(n)不相等,那么最长子字符串一定等于以下两者的较大者:

  • 字符串 Xm-1 和 字符串 Yn 的最长子字符串。
  • 字符串 Xm 和 字符串 Yn-1 的最长子字符串。

根据上面的分析,子问题具有最优化的子结构属性,并且容易看出也具备重叠的子问题集,因为求Xm和Yn的最长子字符串要用到Xm-1或 Yn-1的中的至少一个。

上面的分析,实际上得出了一个递推公式,具体的体现在下面的代码中。

4 LCS 代码实现

//返回x和y的最长子串
public static int lcs(String x, String y){
    return lcf(x,y,x.length()-1,y.length()-1);
}
//i : x索引
private static int lcs(String x, String y,
         int i, int j){
    //递归返回的条件
     if(i==-1 || j==-1) 
         return 0;
    //如果相等
     if(x.charAt(i)==y.charAt(j)){
        return lcs(x,y,i-1,j-1)+1;
     }    
    //如果不等,返回较大者
     return Math.max(lcs(x,y,i-1,j),
                      lcs(x,y,i,j-1));
}

算法的时间复杂度为 O(x.length()+y.length()),空间复杂度为函数的入栈空间。

5 总结

回答了适合动态规划求解的问题主要考量的2个指标,满足了这两个指标后,便能找出递推公式,最后不要忘了递归返回的条件。

请记住:每天一小步,日积月累一大步!

欢迎关注《算法channel》公众号

主要推送关于对算法的思考以及应用的消息。培养思维能力,注重过程,挖掘背后的原理,刨根问底。本着严谨和准确的态度,目标是撰写实用和启发性的文章,欢迎您的关注。

这里写图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力回答你的问题。 曲线积分和曲面积分都是数学中的重要概念,常用于物理、工程等领域的计算和分析。 首先,让我们来了解一下曲线积分。 曲线积分是沿着一条曲线对某个向量场进行积分的过程。具体来说,设曲线C为参数方程r(t)=(x(t), y(t), z(t)), t∈[a,b],向量场F为F=(P, Q, R),则曲线积分的形式为: ∫C F·dr = ∫ab F(r(t))·r'(t) dt 其中,·表示向量的点积,r'(t)表示曲线在t时刻的切向量。 接下来,让我们了解一下曲面积分。 曲面积分是沿着一个曲面对某个向量场进行积分的过程。具体来说,设曲面S为参数方程r(u,v)=(x(u,v), y(u,v), z(u,v)),向量场F为F=(P, Q, R),则曲面积分的形式为: ∫S F·dS = ∫∫S F(r(u,v))·n(u,v) dS 其中,·表示向量的点积,n(u,v)表示曲面在(u,v)处的法向量,dS表示曲面上面积元素。 在曲面积分中,常常会用到高斯公式和斯托克斯公式。 高斯公式是指对于任何一个有向光滑闭合曲面S和向量场F=(P,Q,R),都有: ∫S F·dS = ∫∫∫V div F dV 其中,div F表示向量场F的散度,V表示曲面S所围成的区域。 斯托克斯公式是指对于任何一个有向光滑曲面S和向量场F=(P,Q,R),都有: ∫S F·dr = ∫∫C curl F·n ds 其中,curl F表示向量场F的旋度,C表示曲面S的边界,n表示C的法向量,ds表示C上的弧长元素。 希望这些内容能够对你有所帮助。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值