力扣每日一题:最长递增子序列
1.问题描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
2.示例
3.问题分析
3.1 动态规划
设 d p [ i ] dp[i] dp[i]为考虑前 i i i个元素,以 n u m s [ i ] nums[i] nums[i]结尾(包括 n u m s [ i ] nums[i] nums[i])的最长子序列长度。
从小到大依次计算
d
p
[
0
]
,
d
p
[
1
]
,
…
…
,
d
p
[
n
]
dp[0],dp[1],……,dp[n]
dp[0],dp[1],……,dp[n],再计算
d
p
[
i
]
dp[i]
dp[i]前已经计算了
d
p
[
0
]
,
…
…
d
p
[
i
−
1
]
dp[0],……dp[i-1]
dp[0],……dp[i−1],状态转移方程为i:
d
p
[
i
]
=
m
a
x
(
d
p
[
j
]
)
+
1
,
其
中
0
<
j
<
i
,
且
n
u
m
s
[
j
]
<
n
u
m
s
[
i
]
dp[i] = max(dp[j])+1,其中0<j<i,且nums[j]<nums[i]
dp[i]=max(dp[j])+1,其中0<j<i,且nums[j]<nums[i]
这里没有对
n
u
m
s
[
j
]
和
n
u
m
s
[
i
]
nums[j]和nums[i]
nums[j]和nums[i]的关系中取等于号,是因为这里要求的关系是严格的递增,而不仅仅是递增。
而最终的答案则是:
r
e
s
u
l
t
=
m
a
x
(
d
p
[
i
]
)
,
其
中
0
≤
i
<
n
result = max(dp[i]),其中0≤i<n
result=max(dp[i]),其中0≤i<n
综上所述,我们要做的是每次选择计算
d
p
[
i
]
dp[i]
dp[i]时,对
d
p
[
0
]
,
…
…
,
d
p
[
i
−
1
]
dp[0],……,dp[i-1]
dp[0],……,dp[i−1]进行遍历,找到有
n
u
m
s
[
j
]
<
n
u
m
s
[
i
]
nums[j]<nums[i]
nums[j]<nums[i]且有
d
p
[
j
]
+
1
>
d
p
[
i
]
dp[j]+1>dp[i]
dp[j]+1>dp[i]的
j
j
j值,更新
d
p
[
i
]
dp[i]
dp[i]的值,最后对整个
d
p
[
n
]
dp[n]
dp[n]数组寻找最大值,得到最终的答案。
程序如下:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int dp[nums.size()];
for(int i=0; i<nums.size(); i++){
dp[i] = 1;
for(int j=0; j<i; j++){
if(dp[j]+1>dp[i]&&nums[j]<nums[i]){
dp[i] = dp[j] + 1;
}
}
}
return *max_element(dp, dp+nums.size());
}
};
3.2 贪心+二分
上述的动态规划的时间复杂度较高,为
O
(
n
2
)
O(n^2)
O(n2),下面给出的算法是基于贪心和二分的算法,时间复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
LeetCode给出的官方题解不太好理解,所以转载下面这篇通过纸牌的讲解:从最长递增子序列学会如何推状态转移方程 (qq.com)
程序如下:
public int lengthOfLIS(int[] nums) {
int[] top = new int[nums.length];
// 牌堆数初始化为 0
int piles = 0;
for (int i = 0; i < nums.length; i++) {
// 要处理的扑克牌
int poker = nums[i];
/***** 搜索左侧边界的二分查找 *****/
int left = 0, right = piles;
while (left < right) {
int mid = (left + right) / 2;
if (top[mid] > poker) {
right = mid;
} else if (top[mid] < poker) {
left = mid + 1;
} else {
right = mid;
}
}
/*********************************/
// 没找到合适的牌堆,新建一堆
if (left == piles) piles++;
// 把这张牌放到牌堆顶
top[left] = poker;
}
// 牌堆数就是 LIS 长度
return piles;
}
这里的二分用的是搜索左侧边界的二分查找。详情可以看二分查找的博客: