好的,我将首先描述最简单的解决方案,即 O(N^2),其中 N 是集合的大小。还有一个 O(N log N) 的解决方案,我也会描述。在高效算法部分查看here。
我将假设数组的索引是从 0 到 N - 1。所以让我们将 DP[i] 定义为 LIS(最长递增子序列)的长度,它在索引为 i 的元素处结束。为了计算DP[i],我们查看所有索引j < i,并检查DP[j] + 1 > DP[i] 和array[j] < array[i](我们希望它增加)。如果这是真的,我们可以更新DP[i] 的当前最优值。要找到数组的全局最优值,您可以从 DP[0...N - 1] 中获取最大值。
int maxLength = 1, bestEnd = 0;
DP[0] = 1;
prev[0] = -1;
for (int i = 1; i < N; i++)
{
DP[i] = 1;
prev[i] = -1;
for (int j = i - 1; j >= 0; j--)
if (DP[j] + 1 > DP[i] && array[j] < array[i])
{
DP[i] = DP[j] + 1;
prev[i] = j;
}
if (DP[i] > maxLength)
{
bestEnd = i;
maxLength = DP[i];
}
}
我使用数组prev 以便以后能够找到实际序列,而不仅仅是它的长度。只需使用prev[bestEnd] 在循环中从bestEnd 递归返回即可。 -1 值是停止的标志。
好的,现在到更高效的O(N log N) 解决方案:
让S[pos] 定义为结束长度递增序列pos 的最小整数。现在遍历输入集的每个整数 X 并执行以下操作:
-
如果X > S 中的最后一个元素,则将X 附加到S 的末尾。这实质上意味着我们找到了一个新的最大的LIS。
-
否则找到S中最小的元素,即>=比X,改成X。
因为S是随时排序的,所以可以在log(N)中使用二分查找找到元素。
总运行时间 - N 整数和对它们中的每一个的二进制搜索 - N * log(N) = O(N log N)
现在让我们做一个真实的例子:
整数集合:
2 6 3 4 1 2 9 5 8
步骤:
0. S = {} - Initialize S to the empty set
1. S = {2} - New largest LIS
2. S = {2, 6} - New largest LIS
3. S = {2, 3} - Changed 6 to 3
4. S = {2, 3, 4} - New largest LIS
5. S = {1, 3, 4} - Changed 2 to 1
6. S = {1, 2, 4} - Changed 3 to 2
7. S = {1, 2, 4, 9} - New largest LIS
8. S = {1, 2, 4, 5} - Changed 9 to 5
9. S = {1, 2, 4, 5, 8} - New largest LIS
所以LIS的长度是5(S的大小)。
为了重构实际的LIS,我们将再次使用父数组。
让parent[i] 成为LIS 中索引为i 的元素的前身,该元素以索引为i 的元素结束。
为了简单起见,我们可以在数组S 中保存,而不是实际的整数,而是它们在集合中的索引(位置)。我们不保留{1, 2, 4, 5, 8},而是保留{4, 5, 3, 7, 8}。
即input[4] = 1, input[5] = 2, input[3] = 4, input[7 ] = 5,输入[8] = 8。
如果我们正确更新父数组,实际的 LIS 是:
input[S[lastElementOfS]],
input[parent[S[lastElementOfS]]],
input[parent[parent[S[lastElementOfS]]]],
........................................
现在重要的事情——我们如何更新父数组?有两种选择:
-
如果X > S 中的最后一个元素,那么parent[indexX] = indexLastElement。这意味着最新元素的父元素是最后一个元素。我们只是在S 的末尾添加X。
-
否则找到S中最小元素的索引,即>=而不是X,改成X。这里parent[indexX] = S[index - 1]。