【算法】动态规划(第五章习题解答)

5 动态规划

【算法】动态规划(第五章习题解答)_第1张图片

5.1

图书馆大门前有 n n n 级台阶, 你每次跨上 1 1 1 级或者 2 2 2 级, 请问等上 n n n 级台阶总共有多少种不同的方法? 设计一个算法求解上述问题, 尝试写出公式, 说明算法设计思想和时间复杂度.

算法设计:核心思路是函数的递归调用,当处理 n n n级台阶时,如果跨上1级则还需要处理 n − 1 n-1 n1级台阶,如果跨上 2 2 2级则还需要处理 n − 2 n-2 n2级台阶,容易得到递推表达式 f ( n ) = f ( n − 1 ) + f ( n − 2 ) f(n)=f(n-1)+f(n-2) f(n)=f(n1)+f(n2),这里要注意递归终止条件的设定,为了避免多统计要在 n < 0 n<0 n<0时返回 0 0 0,而在 n = 0 n=0 n=0时返回 1 1 1,代表 1 1 1种走法。

数据输入:台阶级数 n n n

结果输出:走台阶的不同方法数 s o l v e N u m b e r solveNumber solveNumber

伪码描述:

f i n d s o l v e N u m b e r   ( n ) : findsolveNumber\ (n): findsolveNumber (n):

  1. i f    n < 0 if\ \ n<0 if  n<0
  2. $then \ return\ 0 $
  3. i f    n = 0 if\ \ n=0 if  n=0
  4. $then \ return\ 1 $
  5. r e t u r n   f i n d s o l v e N u m b e r ( n − 1 ) + f i n d s o l v e N u m b e r ( n − 2 ) return \ findsolveNumber(n-1)+findsolveNumber(n-2) return findsolveNumber(n1)+findsolveNumber(n2)

递归方程:
{ W ( n ) = W ( n − 1 ) + W ( n − 2 ) W ( 1 ) = 1 \left\{\begin{array}{l} W(n)=W\left(n-1\right)+W\left(n-2\right) \\ W(1)=1 \end{array}\right. {W(n)=W(n1)+W(n2)W(1)=1
复杂度分析:由递归方程可知,该算法的时间复杂度相当于斐波那契数列的时间复杂度
W ( n ) = 5 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] = O ( 2 n ) \begin{aligned} W(n)=\frac{\sqrt{5}}{5}\left[\left(\frac{1+\sqrt{5}}{2}\right)^{n}-\left(\frac{1-\sqrt{5}}{2}\right)^{n}\right]=O(2^n) \end{aligned} W(n)=55 [(21+5 )n(215 )n]=O(2n)
该算法除了输入和输出外不需要额外的存储空间,因此空间复杂度是
S ( n ) = O ( 1 ) \begin{aligned} S(n)=O(1 ) \end{aligned} S(n)=O(1)

5.2

n n n 种币值 v 1 , v 2 , . . . , v n v_1, v_2, . . . , v_n v1,v2,...,vn 和总钱数 M M M 都是正整数. 如果每种币值的钱币至多使用 1 1 1 次, 问: 对于 M M M 是否可以有一种找零钱的方法? 设计一个算法求解上述问题. 说明算法设计思想, 分析算法最坏情况下的时间复杂度.

算法设计:核心思路是采用动态规划,不断尝试并缩小问题的规模,同时考虑尽可能多的情况,可以设函数 F ( n , M ) F(n,M) F(n,M),即使用前 n n n种钱币是否可以凑出 M M M币值,其值域为 { 0 , 1 } \{0,1\} {0,1} 1 1 1代表存在至少一种找零钱的方法, 0 0 0代表不存在任何找零钱的方法。

递推方程:
F ( n , M ) = max ⁡ { F ( n − 1 , M ) , F ( n − 1 , ( M − v n ) } F ( 1 , M ) = { 1 M = v 1 0 M ≠ v 1 F ( n , M ) = 0 M < 0 \begin{array}{l} F(n,M)=\max \left\{F(n-1,M), F(n-1,\left(M-v_{n}\right)\right\}\\ F(1,M)=\left\{\begin{array}{ll} 1 & M=v_{1} \\ 0 & M≠v_{1} \end{array}\right. \\ F(n,M)=0 \quad M<0 \end{array} F(n,M)=max{F(n1,M),F(n1,(Mvn)}F(1,M)={10M=v1M=v1F(n,M)=0M<0
数据输入: n n n种币值 v [ 1.. n ] = v 1 , v 2 , . . . , v n v[1..n]=v_1, v_2, . . . , v_n v[1..n]=v1,v2,...,vn和总钱数 M M M

结果输出: 1 1 1 0 0 0,其中 1 1 1代表存在至少一种找零钱的方法, 0 0 0代表不存在任何找零钱的方法。

伪码描述:

f i n d C h a n g e   ( n , M , v ) : findChange\ (n,M,v): findChange (n,M,v):

  1. i f    y < 0 if\ \ y<0 if  y<0
  2. $then \ return\ 0 $
  3. i f    n = 1    a n d    y = v 1 if\ \ n=1\ \ and\ \ y=v_{1} if  n=1  and  y=v1
  4. $then \ return\ 1 $
  5. i f    n = 1    a n d    y ≠ v 1 if\ \ n=1\ \ and\ \ y≠v_{1} if  n=1  and  y=v1
  6. $then \ return\ 0 $
  7. / / 通过二进制或运算实现 M A X 函数 //通过二进制或运算实现MAX函数 //通过二进制或运算实现MAX函数
  8. r e t u r n   f i n d C h a n g e ( n − 1 , M , v ) ∣ f i n d C h a n g e ( n − 1 , M − v [ n ] , v ) return \ findChange(n-1,M,v)|findChange(n-1,M-v[n],v) return findChange(n1,M,v)findChange(n1,Mv[n],v)

复杂度分析:由递推方程可知,该算法的时间复杂度为
W ( n ) = O ( n M ) \begin{aligned} W(n)=O(nM) \end{aligned} W(n)=O(nM)
该算法由于通过二进制或运算实现了 M A X MAX MAX函数,因此除了输入和输出外不需要额外的存储空间,因此空间复杂度是
S ( n ) = O ( 1 ) \begin{aligned} S(n)=O(1 ) \end{aligned} S(n)=O(1)

5.3

P P P 是一台高性能服务器, T = 1 , 2 , . . . , n T = {1, 2, . . . , n} T=1,2,...,n n n n 个计算任务集合, a i a_i ai 表示任务 i i i 所申请的计算资源. 已知服务器的最大计算资源是正整数 K K K. 请确定 T T T 的一个子集 S S S, 使得 ∑ i ∈ S a i ≤ K ∑ _{i∈S} a_i ≤ K iSaiK, 且 K − ∑ i ∈ S a i K − ∑ _{i∈S} a_i KiSai 的值达到最小. 请设计一个算法求解 S S S, 并分析最坏情况下的时间复杂度.

算法设计:由题目所给的信息可以知道这实质上是一个 0 − 1 0-1 01背包问题,物品的价值和重量相等,由于这个背包问题并没有什么特殊已知条件,因此贪心算法并一定可以得到全局最优解,所以最好选用动态规划法来解决。

解决该问题的函数为 F ( n , K ) F(n,K) F(n,K),即只允许前 n n n个任务的申请,总计算资源不超过 K K K时能达到的最大计算资源,使 ∑ i ∈ S a i ∑ _{i∈S} a_i iSai的值达到最大。

递推方程:
F ( n , K ) = max ⁡ { F ( n − 1 , K ) , F ( n − 1 , K − a n ) + a n } F ( 0 , K ) = 0 F ( n , 0 ) = 0 F ( 1 , K ) = { a 1 , a 1 ≤ K 0 , a 1 > K \begin{array}{l} F(n,K)=\max \left\{F(n-1,K), F\left(n-1,K-a_{n}\right)+a_{n}\right\} \\ F(0,K)=0 \\ F(n,0)=0 \\ F(1,K)=\left\{\begin{array}{ll} a_{1}, & a_{1} \leq K \\ 0, & a_{1}>K \end{array}\right. \\ \end{array} F(n,K)=max{F(n1,K),F(n1,Kan)+an}F(0,K)=0F(n,0)=0F(1,K)={a1,0,a1Ka1>K
数据输入: n n n 个计算任务的集合 T [ 1.. n ] = { a 1 , a 2 , . . . , a n } T[1..n]=\{a_1,a_2,...,a_n\} T[1..n]={a1,a2,...,an}、服务器最大计算资源K

结果输出:布尔类型数组 S [ 1.. n ] S[1..n] S[1..n],值为 T r u e True True代表相应的任务位于 T T T的子集 S S S中,反之 F a l s e False False为该任务不在 T T T的子集 S S S中。

伪码描述:

f i n d S u b s e t   ( n , K ) : findSubset\ (n,K): findSubset (n,K):

  1. i f    n = 0   o r   K = 0 if\ \ n=0\ or\ K=0 if  n=0 or K=0
  2. $then \ return \ 0 $
  3. i f    n = 1   a n d   a 1 ≤ K if\ \ n=1\ and\ a_{1} \leq K if  n=1 and a1K
  4. $then \ return\ a_1 $
  5. i f    n = 1   a n d   a 1 > K if\ \ n=1\ and\ a_{1} > K if  n=1 and a1>K
  6. $then \ return \ 0 $
  7. x ← f i n d S u b s e t ( n − 1 , K ) x\leftarrow findSubset(n-1,K) xfindSubset(n1,K)
  8. y ← 0 y\leftarrow0 y0
  9. i f    y − a n ≥ 0 / / 防止发生 K < 0 的错误 if\ \ y-a_{n}≥0\quad//防止发生K<0的错误 if  yan0//防止发生K<0的错误
  10. t h e n   y ← f i n d S u b s e t ( n − 1 , K − a n ) + a n then \ y\leftarrow findSubset(n-1,K-a_{n})+a_{n} then yfindSubset(n1,Kan)+an
  11. i f    y ≥ x if\ \ y≥x if  yx
  12. $then \ S[n]\leftarrow True;\ return \ y $
  13. e l s e   S [ n ] ← F a l s e ;   r e t u r n   x else\ S[n]\leftarrow False;\ return\ x else S[n]False; return x

复杂度分析:由递推方程可知,该算法的时间复杂度为
W ( n ) = O ( n K ) \begin{aligned} W(n)=O(nK) \end{aligned} W(n)=O(nK)
算法需要一个布尔类型数组 S [ 1.. n ] S[1..n] S[1..n]来记录相应的任务是否位于 T T T的子集 S S S中,另外还有两个变量 x , y x,y x,y用于比较出最优情况,因此空间复杂度是
S ( n ) = O ( n + 2 ) = O ( n ) \begin{aligned} S(n)=O(n+2 )=O(n ) \end{aligned} S(n)=O(n+2)=O(n)

5.4

I I I 是一个 n n n 位十进制整数. 如果将 I I I 划分为 k k k 段, 则可得到 k k k 个整数. 这 k k k 个整数的乘积称为 I I I 的一个 k k k 乘积. 试设计一个算法, 对于给定的 I I I k k k, 求出 I I I 的最大 k k k 乘积. 尝试写出公式, 并说明算法设计思想和时间复杂度.

算法设计:核心思路依旧是采用动态规划,不断尝试并缩小问题的规模,同时考虑尽可能多的情况,设 n n n位十进制整数 I I I I 1 I 2 I 3 . . . I n I_1I_2I_3...I_n I1I2I3...In,函数 p a r t i t i o n ( I , i , j ) partition(I,i,j) partition(I,i,j)可以将 n n n位十进制整数 I I I中第 i i i位到第 j j j位拆分出来成为一个新的十进制整数。

设求解最大 k k k乘积的函数 F ( n , k ) F(n,k) F(n,k)将第 n n n位之前的部分划分为 k k k段得到的最大 k k k乘积,另外还需要找到这个函数的递推方程。

递推方程:
F ( n , k ) = { 0 n < k I 1 I 2 I 3 . . . I n k = 1 max ⁡ 1 ≤ i < n − 1 { F ( i , k − 1 ) ∗ I i + 1 I i + 2 . . . I n } k > 1   a n d   k ≤ n F(n, k)=\left\{\begin{array}{ll} 0 & n1 \ and \ k \leq n \end{array}\right. F(n,k)= 0I1I2I3...Inmax1i<n1{F(i,k1)Ii+1Ii+2...In}n<kk=1k>1 and kn
数据输入: n n n位十进制整数 I I I,划分段数 k k k

结果输出: I I I的最大 k k k乘积

伪码描述:

f i n d M a x P r o d u c t   ( n , k ) : findMaxProduct\ (n,k): findMaxProduct (n,k):

  1. i f    n < k if\ \ nif  n<k
  2. $then \ return \ 0 $
  3. i f    k = 1 if\ \ k=1 if  k=1
  4. $then \ return\ partition(I,1,n) $
  5. P r o d u c t [ 1.. n − 1 ] ← { 0 , 0...0 } Product[1..n-1]\leftarrow\{0,0...0\} Product[1..n1]{0,0...0}
  6. f o r    i ← 1    t o    n − 1    d o / / 尝试所有可能 for \ \ i \leftarrow 1 \ \ to \ \ n-1 \ \ do\quad//尝试所有可能 for  i1  to  n1  do//尝试所有可能
  7. P r o d u c t [ i ] ← f i n d M a x P r o d u c t ( i , k − 1 ) ∗ p a r t i t i o n ( I , i + 1 , n ) Product[i]\leftarrow findMaxProduct(i,k-1)*partition(I,i+1,n) Product[i]findMaxProduct(i,k1)partition(I,i+1,n)
  8. r e t u r n   M A X ( P r o d u c t ) / / 找到 P r o d u c t 数组中最大的值返回 return \ MAX(Product)\quad //找到Product数组中最大的值返回 return MAX(Product)//找到Product数组中最大的值返回

复杂度分析:由递推方程可知,每层递归的时间复杂度是 O ( 1 + 2 + . . . + n − 1 ) O(1+2+...+n-1) O(1+2+...+n1),递归深度为 O ( k − 1 ) O(k-1) O(k1),因此该算法的时间复杂度为
W ( n ) = O ( ( k − 1 ) ( 1 + 2 + . . . + n − 1 ) ) = O ( k n 2 ) \begin{aligned} W(n)=O((k-1)(1+2+...+n-1))=O(kn^2) \end{aligned} W(n)=O((k1)(1+2+...+n1))=O(kn2)
该算法每次递归都需要一个额外的 P r o d u c t Product Product数组来记录遍历过程中求出的子乘积,以便于最后使用 M A X MAX MAX函数求出最大 k k k乘积,因此空间复杂度是
S ( n ) = O ( n ) \begin{aligned} S(n)=O(n) \end{aligned} S(n)=O(n)

你可能感兴趣的:(算法设计与分析,算法,贪心算法,数据结构,链表,排序算法)