什么是 LCIS
LCIS 是建立在 LIS 与 LCS 的基础之上的问题。需要我们求解不同序列的公共子序列中的最长上升子序列等问题。
例子
假设我们拥有以下两个序列:
a:2 2 1 3b:2 1 2 3
那么这两个序列的最长公共上升子序列就是 1 3,长度为 2
解决方法
状态设计
习惯性写下了状态的表示:
dpi,j 表示 a1∼i 与 b1∼j 的 LCIS 的长度
初始化
由于当子序列长度为 1 时并不存在 LCIS,所以进行如下初始化:
dp1,1=∞
转移
- 当 ai=bj 时,由于我们外层循环为 i,所以此时 dpi,j=dpi−1,j
- 当 ai=bj 时,我们需要选出一个 1≤k<j 且 bk<ai 使得 dpi,k 最大,则此时这个最大值为
dpi,j=2≤k<j∧bk<aimax(dpi−1,k)+1
综上,转移如下:
dpi,j=⎩⎪⎨⎪⎧dpi−1,j,2≤k<j∧bk<aimax(dpi−1,k)+1,ai=biotherwise
此时就可以使用三重循环来计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| for (int i = 2; i <= n; ++i) { for (int j = 2; j <= n; ++j) { if (a[i] != b[j]) { dp[i][j] = dp[i - 1][j]; } else { int maxVal = 0; for (int k = 2; k < j; ++k) { if (b[k] < a[i]) { maxVal = max(maxVal, dp[i - 1][k]); } } dp[i][j] = maxVal + 1; } } }
|
但是这样的时间复杂度为 O(n3),能不能对其进行优化呢?
我们发现当计算到 ai=bj 时需要使用第三层循环来寻找 k,那么有没有一种方法能够快速寻找 2≤k<j∧bk<aimax(dpi−1,k)。
基于以上想法,我们拥有两种常见思路:
- 构建 ST 表来做 RMQ,但对于每一个 i 都需要重新构建一次,复杂度为 O(nlog2n),比原先的暴力循环更慢
- 滑动窗口(单调队列)求最大值,同样对于每一个 i 都需要重新计算区间最值,同时区间长度不定,与暴力循环无区别
以上常见的方法都不行,那我们从转移本身进行思考,对于每一对 i,j,当内层对 j 循环时外层的 i 保持不变,则我们在循环 i 时记录一个变量 maxVal
表示 2≤k<j∧bk<aimax(dpi−1,k),当 j 增加 1 时对新的 bj 进行判断是否需要更新 maxVal
即可。时间复杂度为 O(1)。
优化后的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| int maxVal = 0; for (int i = 2; i <= n; ++i) { maxVal = 0; if (a[i] > b[1]) { maxVal = dp[i - 1][1]; } for (int j = 2; j <= n; ++j) { if (a[i] == b[j]) { dp[i][j] = maxVal + 1; } else { dp[i][j] = dp[i - 1][j]; } if (a[i] > b[j]) { maxVal = max(maxVal, dp[i - 1][j]); } } }
|
时间复杂度
综上,整体时间复杂度为 O(n2),1s 内 n 的最大值约为 104
类似练习