题目
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
1
2
3 输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(nlogn)吗?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
审题
需要注意题目要求的是最长上升子序列,而不是最长上升连续子序列,这一点在示例中得到体现。
测试用例
为了更好地理解题目并考虑到可能的情况,我们写几个有代表性的测试用例:
1 | 输入 输出 |
- 第一个是正常的升序数组
- 第二个是极端情况,没有升序序列
- 第三个是考虑升序序列不需要连续而给出的 case
- 第四个是为了考虑到对一个元素计算以其为首的上升子序列长度不一定是最优解
- 第五个是考虑到简单地维护一个单调栈可能使结果错过最优解
解决
动态规划
如果对每个元素i,记以其为结尾的子数组的最长上升序列长度为dp(i),由于我们不要求连续数组,所以需要考虑前面的每一个元素。那么对于前i−1个元素为结尾的数组dp(j),第i个元素是否能使dp(j)的长度增加1,取决于i是否大于j,因此,有状态转移关系式:
1 | for(int i=1; i<nums.size(); ++i){ |
注意这里有个坑,因为dp[i]不一定就是长度为i的数组的最大上升子序列长度,只是i在序列中的前提下的最大上升子序列长度,因此需要找出dp中最大的值。
1 | class Solution { |
贪心+二分查找
考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
因此,维护一个数组d[i],表示长度为i的最长上升子序列的末尾元素的最小值,并维护当前最大上升子序列长度len。
同时可以使用反证法证明d[i]关于i单调递增。
遍历nums,查找d[]找到可以将当前元素插入末尾的数组,当然,我们贪心地希望可以插入当前最长长度的数组。如果不行,就插入到可插入的最长长度数组。值得注意的是,由于d[]是单调的,可以使用二分查找,使查找的时间复杂度降为O(logN),总的时间复杂度O(NlogN)。
最终,d[]的长度即为最大上升子序列长度。
代码:
1 | class Solution { |