15.4-2
Q: 不使用数组b完成LCS
A: 直接通过数组c和序列即可。从数组c的最右下角开始,输出的字符按照从右到左的顺序填充公共子序列,即最先输出最后一个字符。步骤如下:
1. 如果当前两个字符不相等则转步骤2,否则输出该字符并且当前位置往左上方前进一位;
2. 查看当前位置的数组c,如果c[i-1][j]>=c[i][j-1]则往上方前进一位,否则往左方前进一位;
15.4-3
Q: 书写记忆化(memorized version)的LCS算法
A: 个人理解所谓记忆化,即是从上到下的思路,和人的思维很相似,记录下以前生成的结果,使用递归化的方法将高维问题分解成低维问题,举例来说如果X(m)和Y(n)两个序列最后一个字符相同,则有如下计算方法:
LCS(X(m), Y(n)) = LCS(X(m-1),Y(n-1)) + 1
按照如此想法可以非常简单的写出递归函数,生成数组c
15.4-4
Q: 仅需要计算LCS的长度,优化将数组c 的空间,使用2 * min(m,n)空间;可以更进一步的优化,使用min(m,n)空间
A: 2*min(m,n),由于数组c的当前位置取值,仅仅依赖于当前行和上一行,故保存这两行即可;换行时交换这两行的内容即可。
再进一步,当前位置取值,仅仅依赖于其左上位置开始到当前位置之前的所有数值。使用一个类似环形链表的数组存储这些数值,数组中的所有元素组成一个环形链表,数组下标运算不是简单的加法,同时还要取模运算。
15.4-5
Q: 给出O(n2)时间复杂度的算法,计算最长递增子序列
A: 两种思路:
1. 将当前序列排序,排序后的序列和原序列进行LCS计算,直接可得到最长递增子序列,排序时间复杂度O(nlogn),LCS时间复杂度O(n2),故此方法总复杂度为O(n2)
2. 直接动态规划,从左到右扫描序列X(n),考虑以某元素x(i)结尾的最长递增子序列长度,设其为L(i)可以等到如下公式:
L(i) = max(1,
max(L(j) |(j<i && x(j) < x(i))) + 1)
此公式看起来很复杂,实际上思路很简单,在i之前的某个j,以x(j)结尾的最长递增子序列再加上x(i),一定也是一个递增子序列,遍历所有的j,找到最长的递增子序列即可。
注意还需要使用前驱数组来获得整个子序列,即保存每个元素的前驱元素,这种方法和nlogn 的最长递增子序列方法相同。代码如下:
static int lis_n2(int *p, int len, int *inc_seq)
{
int max = 1, last = 0;
int i = 0, j = 0, index = -1;
int *L = NULL, *prev = NULL;
L = malloc(len * sizeof(int));
prev = malloc(len * sizeof(int));
L[0] = 1;
prev[0] = -1;
last = 0;
for (i = 1;i < len;i++) {
L[i] = 1;
index = -1;
for (j = 0;j < i;j++) {
if (p[j] <= p[i] && L[j] + 1 > L[i]) {
L[i] = L[j] + 1;
index = j;
}
}
prev[i] = index;
if (L[i] > max) {
max = L[i];
last = i;
}
}
for (i = max - 1;i >= 0;i--) {
inc_seq[i] = p[last];
last = prev[last];
}
free(L); free(prev);
return max;
}