673. 最长递增子序列的个数(dp)

方法1:dp

在用 O ( n 2 ) O(n^2) O(n2) 求 L I S LIS LIS的长度时维护一个 c n t i cnt_i cnti​ 表示以 i i i结尾的 L I S LIS LIS的个数,然后再维护一个 m a x L e n , a n s maxLen,ans maxLen,ans即可。

时间复杂度: O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
int findNumberOfLIS(vector<int>& a) {
int n=(int)a.size();
vector<int>f(n+1),g(n+1);
int mx=0,ans=0;
for(int i=1;i<=n;i++){
f[i]=g[i]=1;
for(int j=1;j<i;j++){
if(a[i-1]>a[j-1]){
if(f[j]+1>f[i]){
f[i]=f[j]+1;
g[i]=g[j];
}
else if(f[j]+1==f[i]){
g[i]+=g[j];
}
}
}
if(f[i]>mx){
mx=f[i];
ans=g[i];
}
else if(f[i]==mx) ans+=g[i];
}
return ans;
}
};

方法2:二分+前缀和+贪心。

这个方法比较妙,需要维护两个二维数组。

一个 d ( i , j ) d(i,j) d(i,j) 表示长度为 i − 1 i-1 i−1的 L I S LIS LIS的第 j j j个末尾元素, d i d_i di​中的数是非递增的。

c n t ( i , j ) cnt(i,j) cnt(i,j) 是对应 d ( i , j ) d(i,j) d(i,j)的 L I S LIS LIS前缀和。

每次我们对于当前数 n u m num num 二分找到 d i d_i di​ 满足 d [ i ] . b a c k ( ) ≥ n u m d[i].back() \ge num d[i].back()≥num 的最小位置 p p p。

673. 最长递增子序列的个数(dp)_动态规划

时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

class Solution {
int binarySearch(int n, function<bool(int)> f) {
int l = 0, r = n;
while (l < r) {
int mid = (l + r) / 2;
if (f(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}

public:
int findNumberOfLIS(vector<int> &nums) {
vector<vector<int>> d, cnt;
for (int v : nums) {
int i = binarySearch(d.size(), [&](int i) { return d[i].back() >= v; });
int c = 1;
if (i > 0) {
int k = binarySearch(d[i - 1].size(), [&](int k) { return d[i - 1][k] < v; });
c = cnt[i - 1].back() - cnt[i - 1][k];
}
if (i == d.size()) {
d.push_back({v});
cnt.push_back({0, c});
} else {
d[i].push_back(v);
cnt[i].push_back(cnt[i].back() + c);
}
}
return cnt.back().back();
}
};