题解 | #最长递增子序列#
最长递增子序列
http://www.nowcoder.com/practice/9cf027bf54714ad889d4f30ff0ae5481
题意整理::对于给出的数组,要从其中找到一个子序列,满足序列中的数字是单调上升的,在满足的子序列中找到长度最长的并进行输出。子序列是指一个数组删除若干元素,但不改变其他元素相对位置形成的序列
方法一:动态规划(超时)
核心思想:
定义为对前
个数中,以第
个数字结尾的最长上升子序列的长度,且
必须被选取。
分析知最大上升序列需要满足 , 其中
。
对,可以由在
之前且满足
的状态转移得到
可得状态转移方程为:,其中
,且
.
在计算完成后,为了得到最长递增子序列,需要从后往前对dp数组进行一次遍历,找到符合条件的数填入答案中。由于
的值绝对能在左侧找到更小值
,故只需要选取第一次遇见的满足条件的数即可
核心代码:
class Solution {
public:
vector<int> LIS(vector<int>& arr) {
vector<int> dp(arr.size(), 1);
int maxn = 0, n = arr.size();
for(int i = 1; i < n; ++i){
//找到满足 j < i,arr[j] < arr[i],的最大dp[j]进行转移
int res = 0;
for(int j = 0; j < i; ++j){
if(arr[i] > arr[j]) {
res = max(res, dp[j]);
}
}
dp[i] = res + 1;//加上arr[i]
maxn = max(maxn, dp[i]); //找到最大长度
}
vector<int> ans(maxn);
for(int i = n - 1, j = maxn; j > 0; --i){
//逆向找到第一个满足所需大小的数
if(dp[i] == j){
ans[--j] = arr[i];//填入后继续寻找下一个数
}
}
return ans;
}
};复杂度分析:
时间复杂度:,n为数组长度。动态规划的状态数为n,每个状态需要O(n)时间进行遍历,所以总的时间复杂度为
空间复杂度:,需要使用与输入数组等长的dp数组
方法二:动态规划+二分
核心思想:
方法一中代码的问题在于状态转移时需要的时间复杂度,可以使用贪心的思想进行优化。
贪心思路:
对于一个序列,如果想让上升子序列尽量的长,那么需要让序列上升的尽可能的慢,因为需要每次在上升子序列末尾添加的数字尽可能小。
举例说明:如对序列 。构建长度为3的上升子序列时,应该选择
而不是
.
代码实现:基于上述方法,可以维护一个数字 ,表示长度为
的最长上升子序列的末尾数字的最小值,同时使用
记录当前最长上升子序列的长度。由此方法构建的
数组关于
单调递增。若不为单调递增,既存在
, 且
,则可以通过从
尾部删除元素使得两序列长相等,而此时
仍大于
,说明
不满足最长上升序列的最小值,导出矛盾。
通过遍历数组 中的每个元素,并对
数组和
的值进行更新。
当 时,更新
;
否则使用二分法找到满足 的下标
进行更新。
由于需要返回该子序列,需要添加一个辅助数组 表示当前值对于
值。最后通过倒序遍历这个数组找出最长递增子序列
核心代码:
class Solution {
public:
vector<int> LIS(vector<int>& arr) {
int n = arr.size();
vector<int> d(n + 1, -1), p(n);
int len = 1;//初始化长度为1,元素为序列第一个数字
d[len] = arr[0];
p[0] = 1;
for(int i = 1; i < n; ++i) {
if(arr[i] > d[len]) {
//此时将该数字添加到末尾
d[++len] = arr[i];
p[i] = len;
} else {
//二分查找恰好合适的位置
int left = 1, right = len, pos = 0;
while(left <= right) {
int mid = (left + right) / 2;
if(d[mid] < arr[i]) {
pos = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
//对该位置数字进行更新
d[pos + 1] = arr[i];
p[i] = pos + 1;
}
}
vector<int> ans(len);
//逆向查找对应序列值
for(int i = n - 1; i >= 0; --i) {
if(p[i] == len) {
ans[--len] = arr[i];
}
}
return ans;
}
};复杂度分析:
时间复杂度:,需要对
个状态进行更新,每次更新使用二分查找复杂度为
。最后需要
时间遍历
数组,故总时间复杂度为
空间复杂度:,使用了
数组和
数组

查看19道真题和解析