最长上升子序列问题:给定一个数组A, 如A={-7, 10, 9, 2, 3, 8, 8, 1},找到这个序列的最长递增子序列(LIS)(子序列中的元素的index不一定是连续的), 这里的LIS便是{-7, 2, 3, 8}
-
方法一:动态规划(O(n^2))
用L(i)表示A中以下标i结尾的子序列中最长的长度,那么有转移方程:
- L(1) = 0
- L(i) = (L(j)+1) for j = 1,...,n && A[j] < A[i]
-
方法二:单调栈维护(O(nlogn))
依次扫描数组A的每个元素,维护一个单调递增的栈,入栈规则:
- stack[top] < A[i] A[i]入栈并成为新的栈顶元素
- stack[top] >= A[i] 将A[i]放到栈里,替换第一个大于它的元素
最后的结果便是这个单调栈的大小。事实上这是一种贪心的策略,当我们处理的元素比栈顶大时,因为栈是递增的,所以自然地将这个元素添加到最末,序列长度+1。
当处理到比栈顶元素小的元素时,我们不是选择丢弃它,而是用它去替换单调栈中第一个大于它的元素,用题目的例子来说,当我们处理到元素9时,当前的单调栈为{-7, 10},我们将10替换为9,这样仍能保证栈的单调性,且栈顶替换为更小的元素后,可以“贪心”地让后面的元素更加“容易”地放在栈顶。
当处理到1时,单调栈为{-7, 2, 3, 8},这时依据规则我们会将2替换为1,这样操作不会改变结果(上升子序列的最大长度),同时是为了保证出现A={-7, 10, 9, 2, 3, 8, 8, 1, 2, 3, 4}这样的情况,即在1后面还有一个更长的上升子序列,因此我们不能丢弃1,而是用它替换第一个大于它的元素,这样在保证结果不变的同时对于后面可能存在的更长的子序列保留了获得更大长度的可能。
所以这里的单调栈的内容并不是一个上升子序列(虽然也是递增但元素的index不是连续的)