给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
算法1(动态规划)
时间复杂度:\(O(N^2)\)
分析题目不难发现寻找最长递增子序列的过程存在着重叠子问题,即例如在寻找12345
这个序列的最长递增子序列时,也会先求出1234
的最长递增子序列。同时该问题也符合最优子结构,即子问题之间相互独立,不存在制约关系。所以我们可以考虑使用动态规划的求解方法。
定义dp数组:dp[i]
表示以nums[i]
结尾的最长递增子序列的长度。
根据上述定义,我们可以推出base case为dp[i]
的初始值为1,即一个以nums[i]
结尾序列的最长递增子序列至少包含nums[i]
自己。
计算以nums[i]
结尾的最长递增子序列时,将nums[i]
与nums[0]~nums[i-1]
依次比较,若大于则最长子序列长度在原序列的情况下加一。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> dp(nums.size(), 1);
for (int i = 0; i < nums.size(); i ++ ) {
for (int j = 0; j < i; j ++ ) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
int res = 0;
for (int i = 0; i < dp.size(); i ++ ) {
res = max(res, dp[i]);
}
return res;
}
};
如何确定动态规划的状态转移关系?
1、明确dp数组的定义
2、假设已知dp[0...i-1],求出dp[i]的结果。完成了这一步,整个问题也就基本解决了。
算法2(二分查找)
时间复杂度:\(O(N\log N)\)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int piles = 0; //纸牌堆数
vector<int> top(nums.size()); //记录每堆牌顶的值
for (int i = 0; i < nums.size(); i ++ ) {
int poker = nums[i]; //当前要处理的纸牌
//二分查找--将区间划分为 [l,mid],[mid+1, r]
//由于遇到多个可选择堆的时候要放到最左边的堆上(这样可以保证牌堆顶的牌有序)
int left = 0, right = piles;
while (left < right) {
int mid = left + right >> 1;
if (top[mid] >= poker) right = mid;
else left = mid + 1;
}
//如果没有找到合适位置 则新建一个牌堆
if (left == piles) piles ++;
top[left] = nums[i];
}
return piles;
}
};