算法导论14.4 Exercises 答案
14.4-1
实际上,一共有\(8\)个符合要求的子序列,这些子序列的长度为\(6\)。
\(\begin{aligned} \langle0,0,1,0,1,0\rangle \\ \langle0,0,1,0,1,1\rangle \\ \langle0,0,1,1,0,1\rangle \\ \langle0,1,0,1,0,1\rangle \\ \langle1,0,0,1,1,0\rangle \\ \langle1,0,1,0,1,0\rangle \\ \langle1,0,1,0,1,1\rangle \\ \langle1,0,1,1,0,1\rangle \\ \end{aligned}\)
14.4-2
1 | RECONSTRUCT-LCS(c, X, Y, m, n) |
14.4-3
1 | MEMOIZED-LCS-LENGTH(X, Y, i, j, b, c) |
14.4-4
为了尽量减少元素使用量,如果\(m< n\),那么交换字符串\(X,Y\)再运行程序。这并不影响它们的LCS长度。
可以发现,由于\(c[i,j]\)只从\(c[i-1,j],c[i-1,j-1],c[i,j-1]\)中的一个状态转移过来,这些状态只从在当前行或者上一行,因此我们只需要保存两行的\(c\)表即可。
计算\(c[i,j]\)时,如果只希望保存一行元素,那么针对上一行的元素两个元素\(c[i-1,j-1],c[i-1,j]\)分别进行如下处理:
用一个临时遍历临时存储\(c[i-1,j-1]\)。
\(c[i-1,j]\)的值仍然未被覆盖,可以直接使用。
这两个版本的伪代码如下:
1 | LCS-LENGTH1(X, Y, m, n) |
14.4-5
假设\(X[0]=-\infty\),以使得所有\(X[i]\)都可以接在它后面,实际上,\(X[0]\)并不存在。对于每一个数\(X[i]\),如果\(j< i\)并且\(X[j]\le X[i]\),那么如果一个序列以\(X[j]\)为结尾,添加上\(X[i]\)后,那么变成了以\(X[i]\)为结尾的序列,并且长度增加了\(1\)。令\(c[i]\)为以\(X[i]\)为结尾的子序列的最长长度,那么根据最优子结构,可以写出如下状态转移方程:
\(c[i]= \left \{\begin{aligned} &0 & & \text{if}\quad i=0 \\ &\max_{0\le j<i,X[j]\le X[i]}\{ f[j]+1\}& & \text{if}\quad i>0 \\ \end{aligned}\right.\)
按照这个状态转移方程可以写出子程序LIS-LENGTH
:
1 | LIS-LENGTH(X, m) |
\(\star\) 14.4-6
1 | LIS-LENGTH(X, m) |
基本思想:如果我们得到了一个不下降子序列,那么我们希望最后一个数尽可能的小,以使以后转移时当前状态有更大的机会。数组\(a[i]\)表示长度为\(i\)的最长上升子序列中,第\(i\)个数能达到的最小值。如第8行中的代码,如果找到了符合条件的\(j\),那么说明\(a[j-1]\le X[i]\),那么\(X[i]\)可以拼接在\(a[j-1]\)的后面,从而得到一个长度为\(j\)的不下降子序列。
迭代完成后,\(n\)就是能够得到最长的不下降子序列长度。由于数组\(a\)一直是单调递增的,因此第8行代码在寻找j的过程可以以\(\Theta(\lg n)\)的时间完成。
最终,整个算法的时间复杂度为\(O(n \lg n)\)。