动态规划设计:最长递增子序列

300. 最长递增子序列

354. 俄罗斯套娃信封问题

 

前文详细讲解了「动态规划解题套路框架」详情可见 动态规划解题套路框架

在这篇文章中首先介绍动态规划的三个特点,即:存在「重叠子问题」具备「最优子结构」正确的「状态转移方程」

其次给出了一个动态规划的框架,即:明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义

最长递增子序列

题目详情可见 最长递增子序列

下面我们结合「三个特点」和「动态规划框架」来详细分析这个问题

分析:原问题「最长严格递增子序列的长度」,我们把原问题稍微转换一下,「以nums[i]结尾的最长严格递增子序列的长度」

对于一个长度为n的数组来说,如果我们要计算以nums[n - 1]结尾的最长严格递增子序列的长度,我们只需要在以nums[0]...nums[n - 2]结尾的最长严格递增子序列中找到尾数比nums[n - 1]小的子序列,然后在该数量上 +1 即可,最后选出最大值即为以nums[n - 1]结尾的最长严格递增子序列的长度

举个简单的例子,对于:nums = {10, 9, 2, 5, 3, 7, 101, 18}的数组来说,先看下图:

1

假设0 - 6的结果都已经计算出来了,现在需要计算以18结尾的最长递增序列长度。按照我们上面的分析,18nums[0]...nums[6]相比,只有10, 9, 2, 5, 3, 718小,所以18可以接在比它小的元素后面形成长度 +1 的递增子序列,我们只需要在长度 +1 的递增子序列中挑选出最长的即为以18结尾的最长递增子序列的长度

有了上述的分析,我们现在把「动态规划框架」中四个要素明确一下:

根据上面的分析,我们可以很容易的写出完整代码:

根据上面的代码,很容易给出这个问题的「状态转移方程」

dp(n)={1,n=0max{dp(ni)+1 | 0i<n  &&  nums[n]>nums[i]},n>0

至此我们已经分析完了,但是我们并没有说这个问题是否存在「重叠子问题」具备「最优子结构」,我们都用动规完整写出来了,你说有没有呢!!!哈哈哈哈哈

不过呢,我还是准备从这两个方面分析一下

先来说说是否具备「最优子结构」?思考一个很简单的问题,如果我们计算出了{1, 4, 3, 4}的最优解,现在我们增加一个数字5,我们之前计算的结果是否可以重用!?根据上面的分析,显然是可以的,因为数字之间不存在相互制约,所以这个问题是具备「最优子结构」

然后再来说说是否存在「重叠子问题」?关于这个问题,最简单明了的方法就是画出递归树。不说废话,直接看图叭!

2

至此,这道题所有问题都解决了,时间复杂度 O(N2)。总结一下如何找到动态规划的状态转移关系:

但如果无法完成这一步,很可能就是dp数组的定义不够恰当,需要重新定义dp数组的含义;或者可能是dp数组存储的信息还不够,不足以推出下一步的答案,需要把dp数组扩大成二维数组甚至三维数组

优化:利用二分搜索

二分搜索详情可见 二分搜索

这种方法就不展开赘述了,有兴趣的可自行搜索,反正自己是想不到这种方法滴滴滴!!!

俄罗斯套娃信封问题

题目详情可见 俄罗斯套娃信封问题

这就是一个二维的最长递增子序列问题!

第一版思路:先对信封先按照宽度递增排序,如果宽度相等,再按照高度递增排列

注意:这里对宽和高都是递增排序

第二版思路:先对信封先按照宽度递增排序,如果宽度相等,再按照高度递减排列

这样做的一个好处就是,我们可以只用根据高度来计算最长递增子序列。如图:

3

代码如下:

很遗憾,超时了!!😭😭😭

第三版思路:利用二分搜索