673. 最长递增子序列的个数
题目:给定一个未排序的整数数组,找到最长递增子序列的个数。
方法一:动态规划。
考虑前i个元素,令
d
p
[
i
]
dp[i]
dp[i]为以第
i
i
i个元素为结尾的最长递增子序列的长度,令
c
n
t
[
i
]
cnt[i]
cnt[i]表示以第
i
i
i个元素为结尾的最长递增子序列的个数,这里nums[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]
c
n
t
[
i
]
cnt[i]
cnt[i]为所有满足
d
p
[
j
]
+
1
=
d
p
[
i
]
dp[j]+1=dp[i]
dp[j]+1=dp[i]且
0
≤
j
<
i
0≤j<i
0≤j<i的
c
n
t
[
j
]
cnt[j]
cnt[j]的和。
最后,数组的最长递增子序列的长度
m
a
x
l
e
n
=
m
a
x
(
d
p
[
i
]
)
,
其
中
0
≤
i
<
n
maxlen=max(dp[i]),其中0≤i<n
maxlen=max(dp[i]),其中0≤i<n。 最长递增子序列的个数为
r
e
s
u
l
t
=
∑
(
c
n
t
[
i
]
)
result=\sum(cnt[i])
result=∑(cnt[i]),对于那些
i
i
i满足
d
p
[
i
]
=
L
dp[i]=L
dp[i]=L。
代码:
int findNumberOfLIS(vector<int>& nums) {
int n=nums.size();
if(n==0) return 0;
if(n==1) return 1;
vector<int> dp(n);
vector<int> cnt(n);
dp[0]=1;
cnt[0]=1;
for(int i=1;i<n;i++)
{
cnt[i]=0;
int max=0;
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j])
{
if(dp[j]>max) max=dp[j];
}
}
dp[i]=max+1;
if(dp[i]==1) cnt[i]=1;
else
{
for(int j=0;j<i;j++)
{
if(nums[i]>nums[j] && dp[j]==max)
{
cnt[i]+=cnt[j];
}
}
}
}
int maxlen=0;
int result=0;
for(int i=0;i<n;i++)
{
if(dp[i]>maxlen) maxlen=dp[i];
}
for(int i=0;i<n;i++)
{
if(dp[i]==maxlen)
{
result+=cnt[i];
}
}
return result;
}
方法二:贪心算法+二分查找
(1)仅求最长递增序列长度: 想要递增的序列尽可能长,希望末位加上的数越小越好。令
d
[
i
]
d[i]
d[i]为长度为
i
i
i的递增序列的末位元素的最小值,用
l
e
n
len
len表示目前最长递增序列的长度,
l
e
n
len
len的起始值为1,
d
[
1
]
=
n
u
m
[
0
]
d[1]=num[0]
d[1]=num[0];
证明,序列
d
d
d是关于
i
i
i是递增的。若
d
[
j
]
>
d
[
i
]
d[j]>d[i]
d[j]>d[i]且
j
<
i
j<i
j<i,则我们考虑从长度为
i
i
i的递增数列中删掉最后
i
?
j
i-j
i?j个数,可知长度为
i
i
i的序列的第
j
j
j个数小于
d
[
i
]
d[i]
d[i],从而小于
d
[
j
]
d[j]
d[j],由序列
d
d
d的定义可知矛盾。
遍历
n
u
m
num
num,更新
d
d
d和
l
e
n
len
len,若
n
u
m
[
i
]
>
d
[
l
e
n
]
num[i]>d[len]
num[i]>d[len],则
l
e
n
=
l
e
n
+
1
len=len+1
len=len+1,否则,在
d
[
1
,
2
,
.
.
.
,
l
e
n
]
d[1,2,...,len]
d[1,2,...,len]中找到
i
i
i,满足
d
[
i
?
1
]
<
n
u
m
[
j
]
<
d
[
i
]
d[i-1]<num[j]<d[i]
d[i?1]<num[j]<d[i],令
d
[
i
]
=
n
u
m
[
j
]
d[i]=num[j]
d[i]=num[j].根据
d
d
d的单调性,通过二分法查找合适的
i
i
i,优化时间复杂度。
总结: 对于
n
u
m
s
nums
nums,按如下方法更新: 设当前已求出的最长上升子序列的长度为
len
\textit{len}
len(初始时为 1),从前往后遍历数组
nums
\textit{nums}
nums,在遍历到
nums
[
i
]
\textit{nums}[i]
nums[i]时:
如果
nums
[
i
]
\textit{nums}[i]
nums[i] >
d
[
len
]
d[\textit{len}]
d[len] ,则直接加入到
d
d
d 数组末尾,并更新
len
=
len
+
1
\textit{len} = \textit{len} + 1
len=len+1;
否则,在
d
d
d 数组中二分查找,找到第一个比
nums
[
i
]
\textit{nums}[i]
nums[i]小的数
d
[
k
]
d[k]
d[k] ,并更新
d
[
k
+
1
]
=
nums
[
i
]
d[k + 1] = \textit{nums}[i]
d[k+1]=nums[i]。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int len = 1, n = (int)nums.size();
if (n == 0) {
return 0;
}
vector<int> d(n + 1, 0);
d[len] = nums[0];
for (int i = 1; i < n; ++i) {
if (nums[i] > d[len]) {
d[++len] = nums[i];
} else {
int l = 1, r = len, pos = 0;
while (l <= r) {
int mid = (l + r) >> 1;
if (d[mid] < nums[i]) {
pos = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
d[pos + 1] = nums[i];
}
}
return len;
}
};
|