问题简析
哈喽,动态规划问题的解析又来了!这次带来的是最长不下降子序列和最长上升子序列问题,因为这两个问题在代码上是99%以上的相似(仅有一个符号不同),所以把它们放在一起讲。那么什么叫最长不下降子序列?
假设序列
,我们要找一个最长不下降子序列,什么意思呢?就是设序列
,
。我们看这个序列,可以直观看出一个最长的不下降子序列为:
。如果我们直接用暴力搜索的方法,时间复杂度是
,只要序列再长一点,就已经是不可接受的时间了。
那么我们现在来讲一种
的搜索方法,至于
的方法,留待下一次讲吧~
最长不下降子序列问题
我们先来想一下,最长不下降子序列是一种什么样的问题?
它是一种有状态的问题,一个更长的不下降子序列,必定由一个较短的不下降子序列拼接而来。这样说不太严谨,我们来点数学定义:
若:
是一个不下降子序列,并且有
,那么
也是一个不下降子序列。这里的
是序列
中的任意一项(也即
),且序列
是一个不下降的序列。
我们可以从上述定义中看出,状态就是移动到第
个数,状态转移就是选择序列第
个数与否来构建新子序列。状态的初始条件就是把第
个数作为子序列的开头。
啊啊啊啊啊啊!怎么这么抽象!你是学数学的吗?
抱歉,并不是,接下来我们还是构建一个
序列,来看看子序列是怎么被找到的。
还是假设序列
,我们构建一个等长的
序列,
的意义是:在选择第
个状态与否的时候子序列最长的长度。
好,我们先来初始化
序列,因为序列
中的每个数单独拎出来做成一个序列,都是一个不下降子序列,比如
,
等等,它们都是序列
不下降的子序列。所以
的初始化条件为:
其中
是序列
的长度,在这里是6。
接下来我们开始遍历
序列,
,因为选择第0个元素构成的子序列
是当前的最长不下降子序列。
那么
是多少呢?我们首先得看
可以跟谁组成最长不下降子序列,我们发现
(并且也只有
可以选了),说明我们可以把6拼到
的后面,组成当前的最长不下降子序列
,这个时候,
。
接下来我们看
,和上面相同地,我们观察所有满足
的
,发现这个时候
。那么我们回想,
的含义是
状态下的不下降子序列的长度,因为我们要求的是子序列长度最长,所以有多个
满足的情况下,我们当然选
,这里的
代表取多个数中的最大值。我们发现
。所以也就是说延续1状态把
接上去,会得到最长的序列。
什么意思呢,如果采用
,得到的不下降子序列就是
。如果采用
,得到的不下降子序列就是
,那肯定是继承
的状态要优于
。那么这个时候
。
接下来我们看
,我们知道,首先得满足
的
,然后才能继续谈其他的。我们发现这个时候只有唯一解,那就是
。但是这个时候的
,那还不如直接不选
的情况,保留
这个子序列,也比选
所构造的
这个子序列长。
那后续就好办了,
,此时子序列为
。
,此时子序列为
。此时已经到最后一个序列元素了,状态转移也就停止了。
那么上面分析了那么多,如何得到状态转移方程呢?我们归纳一下,就是:
就是说,要么我不选第
个元素,维持上一个状态的不下降子序列。要么我去翻前面的可以构成不下降子序列的序列末尾元素(
),然后把
加到这个不下降子序列里,构成更长的不下降子序列。选哪种?当然是哪种可以让我的不下降子序列更长我选哪种。
比如我们上面分析
的时候,选不选
?选了,就只能构成
这个子序列。不选,还可以保留上个状态构成的
子序列。那明显不选更长,所以
函数就是这样使用的。
复杂度分析
遍历一遍
序列时间复杂度为
,在每一次搜索
的时间复杂度为
。所以总的时间复杂度就是
。空间复杂度就是
数组的空间,空间复杂度为
。
最长上升子序列问题
解法同最长不下降子序列问题,只是状态转移方程为:
可以发现大于等于号变成了大于号,这就是最长上升子序列的定义。
具体实现的代码留作课后习题。
思考题:你可以进一步下降这个问题的时间复杂度吗?