DP_最长不下降子序列_LIS
Contents
序言
同类的问题还有*“最长上升子序列”, “最长下降子序列”, …*
他们的不同就在于定义的core规则不同, 有的是>=, 有的是>, 有的是<
由此启发, 我们可以在解决其他的问题, 不一定是比较数的大小的问题里面抽象出这种模型.
下面介绍这种动态规划入门都会介绍的问题的思路.
首先我们从头开始分析这个问题.
一. 最容易想到的最暴力的方法
对这个序列中的每一个数的"有"和"无"分两种情况讨论. 代码实现上就是递归.
时间复杂度就是O(2^n)
代码实现上较为简单. 不展示
二. 第二种方法是O(n^2)的DP方法
动态规划的问题是无后效性的, 每个问题都可以分解为更小的子问题, 从而求解.
这道题也不例外.
这个序列的每一个数为止都有一个解, 作为子问题的解. 后面的问题的解就是从这些子问题的最优解继承过来的.
so, 给这个序列的解建立数组dp[n], 0 - n分别是截止到Ai的解.
当下一个数要加入来的时候, 有两种情况
前面的数都比当前数更大, 因此以这个数为止的最长不下降子序列的长度就是1. 遍历到第一个数的情况也包含在内.
前面的数有不比当前数大的, 那么这个数的结果dp[i] = max(dp[i], dp[j] + 1). 这个过程遍历前面所有数的dp[j]进行比较.
最后的答案就是所有dp[i]里面的最大值.
这种方法的时间复杂度是O(n^2), 可以看到相比于前面暴力递归的方法有了极大的进步.
代码通过样例, 但不一定能过题, 请谨慎使用.
|
|
三. O(nlogn)方法, 维护单调数组
这个方法也是DP方法
时间复杂度可以从O(n^2)降到O(n log n).
我们从最长上升子序列的角度来探讨
假设对一个序列n[1…9] = {2 1 5 3 6 4 8 9 7}, 维护一个单调数组, 使得这个数组为最长上升子序列. 设这个数组为d[ ].
对n[1] = 2, 使得d[1] = 2;
对n[2] = 1, 因为1比2小, 所以修改d[1]的值, 使其为1
对n[3] = 5, 5比1大, 所以len++, d[2] = 5
对n[4] = 3, 3比1大比5小, 所以替换掉5, 使d[2] = 3
对n[5] = 6, 6比d[2]大, 所以len++, d[3] = 6
对n[6] = 4, 4比3大比6小, 所以替换掉6, 使d[3] = 4
对n[7] = 8, 8比4大, 所以len++, 使d[4] = 8
对n[8] = 9, 9比8大, 所以len++, 使d[5] = 9
对n[9] = 7, 7比4大比8小, 所以替换掉8, 使d[4] = 7.
至此这个序列遍历完了, 最小的长度也出来了. 最后的序列是1 3 4 7 9, len = 5
仔细琢磨会觉得, 如最后一步操作, 为什么后面的7反而到他前面的8, 9的前面去了. 其实仔细一想并无问题, 因为即使7出现在前面, 它并不影响最终结果, 因为我们已经得出最后结果就是len, 而以7为结尾的最长序列在该题中是len - 1, 如果后面还有序列, 那么这里把7替换掉8会使得当前状态更优, 因为这样的修改是不会改变当前结果的, 但是确实后续最优状态的基础. 而这道题的动态规划思想就是这样, 不断地获取最优状态.
经过前面的分析我们也许会发现, 这个DP的过程无法存储中间结果, 也就是说我们只能知道最长的子序列是多长, 而无法得到是哪个序列. 可谓是有利有弊.
利用我们维护的数组的单调性, 我们可以用二分法查找这个比当前数更大数的位置, 从而方便的实现替换.
所以时间复杂度为O(n log n).
|
|
Author 姬小野
LastMod 2018-07-11