最长上升子序列
Longest Increasing Subsequence
解
方法一:动态规划
定义 dp[i] 为考虑前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度。
状态转移方程:
d
p
[
i
]
=
m
a
x
(
d
p
[
i
]
,
d
p
[
j
]
+
1
)
,
0
≤
j
<
i
,
n
u
m
s
[
j
]
<
n
u
m
s
[
j
]
dp[i]=max(dp[i],dp[j]+1),0\leq j<i,nums[j]<nums[j]
dp[i]=max(dp[i],dp[j]+1),0≤j<i,nums[j]<nums[j]
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int size = (int)nums.size();
if (size == 0) {
return 0;
}
vector<int>dp(size, 1);
for (int i = 0; i < size; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
return *max_element(dp.begin(), dp.end());
}
};
方法二:贪心算法+二分查找
考虑一个简单的贪心,如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢。
基于上面的贪心思路,我们维护一个数组
t
a
i
l
s
[
i
]
tails[i]
tails[i] ,表示长度为
i
i
i 的最长上升子序列的末尾元素的最小值,用
l
e
n
len
len 记录目前最长上升子序列的长度。
于是,我们得到如下步骤:
设当前已求出的最长上升子序列的长度为
l
e
n
len
len(初始时为 1),从前往后遍历数组
n
u
m
s
nums
nums,在遍历到
n
u
m
s
[
i
]
nums[i]
nums[i] 时
- 如果 n u m s [ i ] > d [ l e n ] nums[i]>d[len] nums[i]>d[len],则直接加入到 t a i l s tails tails数组末尾,并 l e n = l e n + 1 len=len+1 len=len+1;
- 否则,在 t a i l s tails tails数组中二分查找,找到第一个比 n u m s [ i ] nums[i] nums[i]小的数 t a i l s [ k ] tails[k] tails[k] ,并更新 t a i l s [ k + 1 ] = n u m s [ i ] tails[k+1]=nums[i] tails[k+1]=nums[i]。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int size = (int)nums.size();
int len = 1;
if (size == 0) {
return 0;
}
vector<int>tails(size + 1, 0);
tails[len] = nums[0];
for (int i = 0; i < size; i++) {
if (nums[i] > tails[len]) {
tails[++len] = nums[i];
} else {
int left = 1, right = len, pos = 0;
while (left <= right) {
int mid = (left + right) >> 1;
if (tails[mid] < nums[i]) {
pos = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
tails[++pos] = nums[i];
}
}
return len;
}
};
时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)