版权声明:转载请注明出处 https://blog.csdn.net/zhouchen1998/article/details/88776772
最长递增子序列问题
-
简述
- 经典的动态规划问题。
-
问题描述
- 给定一个序列,求解其中长度最长的递增子序列。
-
问题分析
- 这种可以向下查询答案的很容易想到动态规划的解法。
- 要求长度为i的序列Ai={a1,a2,a3,ai}的最长递增子序列,需要先求出序列Ai-1{a1,a2,a3,ai-1}中各元素(a1,a1,ai-1)作为最大元素的最长递增子序列,然后将这i-1个递增序列与ai进行比较。如果某个长度为m的序列的末尾元素aj(j<i)比ai小,则将元素ai加入这个递增子序列,得到一个长度为m+1的新序列,否则其长度不变,将处理后的i-1个序列的长度进行比较,最长的就是要求的最长递增子序列。
- 举个例子
- 对于序列A{3,1,4,5,9,2,6,5,0},当处理到第7个元素“6”时,i-1个序列为。
- 3
- 1
- 3,4
- 3,4,5
- 3,4,5,9
- 1,2
- 经过上述处理后,序列变为如下。有两个最长的。可见,以“6”为结尾的最长递增子序列为{3,4,5,6}。
- 3,6
- 1,6
- 3,4,6
- 3,4,5,6
- 3,4,5,9
- 1,2,6
- 同样,处理到第8个元素“5”的时候,原始序列处理后变为如下。
- 3,5
- 1,5
- 3,4,5
- 3,4,5
- 3,4,5,9
- 1,2,5
- 可见,以第8个元素“5”为结尾的最长递增子序列为{3,4,5}。
- 最后一个0,无法在后面添加,因此,这个题目的答案为{3,4,5,9}。序列A的最长递增子序列长度为4.
- 注意:求解到的最长递增子序列可能不止一个。
- 对于序列A{3,1,4,5,9,2,6,5,0},当处理到第7个元素“6”时,i-1个序列为。
- 设f(i)表示序列中以ai为末尾元素的最长递增子序列的长度,则在求以ai为末尾元素的最长递增子序列是,找到所有序号在i前面且小于ai的元素aj,即j<i且aj<ai。
- 如果这样的元素存在,那么对所有的aj,都有一个以aj为末尾元素的最长递增子序列的长度f(j)。把其中最大的f(j)找出来,f(i)就等于这个最大的f(j)+1。也就是说,以ai为末尾元素的最长递增子序列,等于在使f(j)最大的那个aj为末尾元素的递增子序列最后再加上ai;如果这样的元素不存在,那么自身构成一个长度为1的以ai为末尾元素的递增子序列。
-
代码
-
def f(arr): n = len(arr) dp = [0] * n for i in range(n): dp[i] = 1 for j in range(i): if arr[j] < arr[i]: dp[i] = max(dp[i], dp[j]+1) return dp def generate_lis(arr, dp): n = max(dp) index = dp.index(n) lis = [0] * n n -= 1 lis[n] = arr[index] # 从右向左查 for i in range(index, -1, -1): if arr[i] < arr[index] and dp[i] == dp[index] - 1: n -= 1 lis[n] = arr[i] index = i return lis if __name__ == '__main__': arr = [3, 1, 4, 5, 9, 2, 6, 5, 0] print(generate_lis(arr, f(arr)))
- 这种不确定向下查找的方向的不适合使用递归。
-
-
算法改进
- 上面那种解法的复杂度达到了O(n^2),其实可以改进为O(nlogn)。
- 在基本算法中,当需要计算前i个元素的最长递增子序列的时候,前i-1个元素作为最大元素的i-1个递增序列,无论是长度,还是最大元素值,都毫无规律,所以在开始计算前i个元素的时候只能遍历前i-1个元素,以找到满足的j值,使得aj<ai且满足条件的j中,以aj作为最大元素的递增子序列最长。
- 其实。在一个序列中,长度为n的递增子序列可能不只有一个,但是最大元素最小的递增子序列只有一个。上面例子中,当处理第7个元素“6“时,长度为2的子序列有两个,即{3,4}和{1,2},这个{1,2}就是最大元素最小的递增子序列。(可以轻易证明唯一性)随着序列长度的变化,满足条件的子序列不断变化。
- 如果将这些子序列安装长度由短到长排序,将每个序列的最大元素放在一起,形成新序列B{b1,b2,bj},则序列B一定是递增的。(此处不证明)
- 发现了这个严格递增,眼前一亮,可以利用此序列的严格递增进行二分查找,找到最大元素刚好小于aj的元素bk,将aj加入这个序列尾部,形成长度为k+1但是最大元素又小于b(k+1)的新序列,取代之前的b(k+1)。如果aj比B中所有元素都大,说明发现了以aj为最大元素,长度为n+1的递增序列,将aj作为B(n+1)的第n+1个元素。从b1依次递推,可以在O(nlogn)时间内找出A的最长递增子序列。
- 这种方法大幅度提高了运行效率。
- 优化代码见我的Github
-
补充说明
- 具体代码可以查看我的Github,欢迎Star或者Fork
- 参考书《你也能看得懂的Python算法书》,书中略微有一点不合理之处,做了修改
- 到这里,其实你已经体会到了动态规划的简约之美,当然,要注意Python是有递归深度限制的,如不是必要,建议使用循环控制