leetcode 300题
切入点:
- 题目中有“递增”、“序列”这样的字眼,过程中前一个状态和后一个状态是有关系,想到使用动态规划。
- 尝试定义状态,并从题目中抽象出前后状态的转移关系: x i = f ( x i − 1 ) x_i = f(x_{i-1}) xi=f(xi−1)
动手写出状态转移方程:
-
定义状态
题目要求最长子序列,可以定义子序列长度为状态。
为满足无后效性,定义状态 d p [ i ] dp[i] dp[i]为以 n u m s [ i ] nums[i] nums[i]为结尾的递增子序列的最大长度。
最终的输出为 m a x ( d p [ i ] ) , i = 0 , 1.. , n max(dp[i]), i=0,1..,n max(dp[i]),i=0,1..,n -
状态转移
现在考虑:从 d p [ i − 1 ] dp[i-1] dp[i−1]是否能直接得到 d p [ i ] dp[i] dp[i],似乎并不可以。 d p [ i ] dp[i] dp[i]是以 n u m s [ i ] nums[i] nums[i]结尾的最长递增子序列的长度。它的转移过程具体是如果 n u m s [ j ] < n u m s [ i ] nums[j] < nums[i] nums[j]<nums[i],则 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) dp[i] = max(dp[i], dp[j] + 1) dp[i]=max(dp[i],dp[j]+1), 其中 0 < = j < i 0<= j < i 0<=j<i
完整代码(C++):
int longestIncreasingSubsequence(vector<int>& nums){
int n = nums.size();
vector<int> dp(n, 1);
for (int i = 0; i < n; ++i){ //计算一nums中每个元素为结尾的最长递增子序列的长度
for (int j = 0; j < i; ++j){ //nums[i]之前,以nums[j]为结尾的最长递增子序列的长度
if (nums[i] > num[j]){ //满足这个条件,nums[i]可以拼接到以nums[j]为结尾的序列的后边,形成以nums[i]为结尾的递增序列。
dp[i] = max(dp[i], dp[j] + 1); //取最长的长度。
}
}
}
int max_len = *max_element(dp.begin(), dp.end()); //返回最长的长度。
return max_len;
}
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)
方法二:贪心算法
思想:推演过程中,使递增子序列结尾的元素尽量小(贪心),那么最终的长度才可能尽量长
定义 d p [ l e n ] = n u m s [ i ] dp[len]=nums[i] dp[len]=nums[i],长度为 l e n len len的递增子序列,最小的结尾
完整代码(c++)
int longestIncreasingSubsequence(vector<int>& nums){
int n = nums.size();
vector<int> dp(n+1,0);
int len = 1;
dp[len] = nums[0]; //长度为1的递增子序列,末尾元素置为nums[0],后续会更新。
for(int i = 1; i < n; ++i){
//第i个元素大于长度为len的递增子序列的末尾元素,则可以拼接到后边,是最长递增子序列的长度+1.
if(dp[len] < nums[i]){
dp[++len] = nums[i];
}else{ //否则,更新前面长度为j的最长子序列的末尾元素。
int pos = 0;
for(int j = 0; j < len; ++j){
if(dp[n] < nums[i]){
pos = j;
}
}
dp[pos+1]=nums[i];
}
}
return len;
}
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n ) O(n) O(n)
方法2优化,二分查找
方法2中,更新当
d
p
[
l
e
n
]
>
=
n
u
m
s
[
i
]
dp[len] >= nums[i]
dp[len]>=nums[i]时,需要从
d
p
[
1
,
.
.
.
,
l
e
n
−
1
]
dp[1,...,len-1]
dp[1,...,len−1]中找到一个
p
o
s
pos
pos,使得
d
p
[
p
o
s
]
<
n
u
m
s
[
i
]
<
d
p
[
p
o
s
+
1
]
dp[pos] < nums[i] < dp[pos+1]
dp[pos]<nums[i]<dp[pos+1],并更新
d
p
[
p
o
s
+
1
]
=
n
u
m
s
[
i
]
dp[pos+1]=nums[i]
dp[pos+1]=nums[i]。
由于
d
p
[
1
,
.
.
.
,
l
e
n
]
dp[1,...,len]
dp[1,...,len]是单调递增的,可以使用二分法来优化时间复杂度。
完整代码(C++)
int longestIncreasingSubsequence(vector<int>& nums){
int n = nums.size();
vector<int> dp(n,0);
int len = 0;
dp[len] = nums[0];
for(int i=0; i<n; ++i)
{
if(dp[len] < nums[i]){
dp[++len] = nums[i];
}else{
int pos = 0;
int l=1, r = len;
while(l <= r){
int mid = (l+r) >> 1;
if(dp[mid] < nums[i]){
pos = mid;
l = mid+1;
}else{
r = mid-1;
}
}
dp[pos+1] = nums[i];
}
}
return len;
}
时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),空间复杂度 O ( n ) O(n) O(n)