0
点赞
收藏
分享

微信扫一扫

第五版算法设计与分析 动态规划(含书上与leetcode习题)

沐之轻语 2022-03-11 阅读 22

目录

动态规划关键要点

(1) 基本要素 + V.S. 递归

动态规划和递归类似,基本思想也是:先分解再求解子问题。但动态规划中的子问题往往不独立,如果使用递归,则将出现许多重复运行,时间复杂度往往指数级。动态规划法使用一个表来记录子问题的解,后续使用时查询即可。

因此,一个动态规划问题包含两个基本要素:

  • 最优子结构:问题的解可由他的子问题的解来确定,要想达到当前状态的最优解,则子问题的解也应该是最优的,即全局最优解包含局部最优解。
  • 重叠子问题:每次产生的子问题并不总是新问题。

(2)备忘录方法

备忘录方法是动态规划的变形。但与动态规划不同的是,备忘录是自顶向上的(即动态规划先求子问题,再求大问题,备忘录方法与之相反)。与动态规划相同的是,他也为每个子问题建立记录项。

二者方法优劣在于子问题重叠情况,若重叠多,则动态规划更佳,否则备忘录更佳。这是由于后者仅计算需要求解的子问题。

(3) 求解步骤

寻找最优子结构的结构特征 → 表示成子问题的最优解 → 自底向上计算出最优值 → 构造最优解

  1. 寻找最优子结构的性质、结构特征;
  2. 表示成子问题的最优解,并递归定义;
  3. 自底向上计算出最优值;
  4. 构造最优解。

另:通常可以通过观察特殊结构等,来减少使用空间。一般难点在于第一点,需要多实践摸索经验。

习题

(1)书:最长公共子序列

给定两个字符串,确定他们最长公共子序列(子序列为原序列中部分下标严格递增的序列)。

1.1 最优子结构 特征性质

我觉得对于初学者而言,这一题的难点在于如何去定义最优解,定义最优解的子问题变化。一个直接的认知是,最优子结构是根据两个字符串的长度变化而改变的,我们就可以设定两个字符串的求解位置作为两个变量。

接着,我们可以得到子结构的变化特征:

X = { x 1 , x 2 , . . . , x m } , Y = { y 1 , y 2 , . . . , y n } X = \{x_1,x_2, ..., x_m \}, Y=\{ y_1, y_2, ..., y_n\} X={x1,x2,...,xm},Y={y1,y2,...,yn},最长公共子序列 Z = { z 1 , . . . , z k } , 则 有 : Z=\{z_1, ..., z_k\},则有: Z={z1,...,zk}

  1. x m = y n x_m=y_n xm=yn,则 z k = x m = y n z_k =x_m=y_n zk=xm=yn,且 Z k − 1 Z_{k-1} Zk1 X m − 1 X_{m-1} Xm1 Y n − 1 Y_{n-1} Yn1的最长公共子序列;
  2. x m ≠ y n x_m≠y_n xm=yn z k ≠ x m z_k≠x_m zk=xm,且 Z Z Z X m − 1 X_{m-1} Xm1 Y Y Y的最长公共子序列;
  3. x m ≠ y n x_m≠y_n xm=yn z k ≠ y n z_k≠y_n zk=yn,且 Z Z Z X X X Y n Y_n Yn的最长公共子序列。

因此,我们可以设定解为c[i][j]:a[1:i]与b[1:j]的最长公共子序列长度,从而有递归的两种情况:
(1)两个序列最末元相等,即为最长公共子序列的最末元,因此 c[i][j] = c[i-1][j-1]+1
(2)最末元不等,则为任意去掉某一序列最末元所得最长长度,即 c[i][j] = max{c[i][j-1], c[i][j-1]}
以及递归终止情况:j=0或i=0时 c[i][j] = 0;

1.2 算法的改进

这是一个常见改进,即:求解所需子问题存在规律,可以不必存储全部子问题解答。

原本的算法使用了数组b来记录最长子序列的选择情况,从而完成求解之后可以得知解的来源;计算顺序为从上至下从左至右(或者先从左至右再从上至下)。

而在本题中,c[i][j]的值实际上只受c[i-1][j-1]、c[i-1][j]、c[i][j-1]三者影响。可以直接通过c的记录情况来完成比较进行回溯,并且不必花费二维数组 mnint字节 的空间开销,只需要 2*min{m,n}*int字节 的开销。

(2)书:最大子段和

本题具有很强的推广变化,十分经典。

给定序列 { a 1 , a 2 , . . . , a n } \{ a_1, a_2, ..., a_n\} {a1,a2,...,an},求 ∑ k = i j a k \sum_{k=i}^j a_k k=ijak 的最大值。

2.1 最优子结构 特征性质

其实与前题思路类似,本质也是控制求解对象长度,并根据推导递归变化。

需要注意的是,这两题尽管都存在两端的变化,但由于题中隐含i到j必然连续,因此我们都仅设置一端的长度作为变量,即一般考虑从0至i,或从i至n-1。仅特殊情况下需要采用第二维,如背包问题,需要使用第二维代表背包容量限制。

所以,我们定义b[j]:以 j 为结束点,可得最大的子段和。显然我们可得递归关系:b[j] = max{ b[j-1] + a[j], a[j] }。(即考虑是否选取当前位置元素)

时间空间复杂度均为O(n)。

2.2 其他解法

  • 普通算法:考虑对每一个开始位置找到当前位置的最大和,复杂度O(n^2)
  • 递归:最大子段和要么来自左半边或右半边,要么跨越两段的一段子段;前二者由递归可得,后者由于必须包括两段最邻近的两点,因此适用普通算法,计算从该二者开始可得最大子段和。因此复杂度有:T(n) = 2 T(n/2) + O(n) ⇒ T(n) = O(nlogn)

2.3 推广问题

2.3.1 最大子矩阵和(找一个矩阵子集,和最大):

t ( i 1 , i 2 ) = m a x 1 < = j 1 < = j 2 < = n ∑ j = j 1 j 2 b [ j ] t(i_1,i_2)=max_{1<=j_1<=j_2<=n}\sum_{j =j1}^{j_2}b[j] t(i1,i2)=max1<=j1<=j2<=nj=j1j2b[j]

b[j] 与之前类似:以 j 为列位置标记,第 i 1 : i 2 i_1:i_2 i1i2 行的元素之和

求 t(i1,i2) 的时间为O(n),因此最终的时间复杂度为 O(m^2*n)

2.3.2 最大m子段和问题(确定m个不相交子段的最大和):

设定子结构为 b[i][j]:前 j 个元素中,选择 i 个子段获得的最大和,子段需要选中第 j 个元素。
可得递归结构:
b [ i ] [ j ] = m a x { b [ i ] [ j − 1 ] + a [ j ] , m a x k { b [ i − 1 ] [ k ] + a [ j ] } } b[i][j] = max\{ b[i][j-1] +a[j], max_k\{b[i-1][k] + a[j] \} \} b[i][j]=max{b[i][j1]+a[j],maxk{b[i1][k]+a[j]}}
最后需要对b[m]进行遍历,找到最大值。

2.3.3 leetcode153题:最优除法

给定一组正整数,相邻的整数之间将会进行浮点除法操作,通过在任意位置添加任意数目的括号,改变算数的优先级,使最终结果最大。

同样,这一题是根据添加括号的位置作为变量。需要注意的是,前后的i、j都作为了变量 。并且通过运算我们知道,这一题不仅涉及“最大”这一最优概念,实则也需要“最小"这一最优概念。

因此最终推导的递归结构如下。


解析详情

举报

相关推荐

0 条评论