赞
踩
给定一个未排序的整数数组,找到最长递增子序列的个数。
示例 1:
输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。示例 2:
输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。注意: 给定的数组长度不超过 2000 并且结果一定是32位有符号整数。
方法一:动态规划
思路与算法
定义 dp[i]表示以 nums[i]结尾的最长上升子序列的长度,cnt[i]表示以 nums[i] 结尾的最长上升子序列的个数。设 nums的最长上升子序列的长度为 maxLen,那么答案为所有满足 dp[i]=maxLen的 i所对应的 cnt[i]之和。
我们从小到大计算 dp数组的值,在计算 dp[i]之前,我们已经计算出 dp[0…i−1]的值,则状态转移方程为:
dp[i]=max(dp[j])+1,其中 0≤j<i 且 num[j]<num[i]
即考虑往 dp[0…i−1]中最长的上升子序列后面再加一个 nums[i]。由于 dp[j]代表 nums[0…j]中以 nums[j]结尾的最长上升子序列,所以如果能从 dp[j]这个状态转移过来,那么 nums[i]必然要大于 nums[j],才能将 nums[i]放在 nums[j]后面以形成更长的上升子序列。
对于 cnt[i],其等于所有满足 dp[j]+1=dp[i]的 cnt[j]之和。在代码实现时,我们可以在计算 dp[i]的同时统计 cnt[i]的值。
- class Solution {
- public:
- int findNumberOfLIS(vector<int>& nums) {
- int n = nums.size();
- vector<int> dp(n), cnt(n);
- int ans = 0, maxLen = 0;
- for(int i =0; i < n; ++i) {
- dp[i] = 1;
- cnt[i] = 1;
- for(int j = 0; j < i; ++j) {
- if(nums[i] > nums[j]) {
- if(dp[j]+1 > dp[i]) { //相当于 dp[i] = max(dp[j]+1, dp[i]);
- dp[i] = dp[j]+1;
- cnt[i] = cnt[j];
- }else if(dp[j]+1 == dp[i]){ //dp[j]+1 == dp[i] 有相同的长度
- cnt[i] += cnt[j];
- }
- }
- }
- if(dp[i] > maxLen) {
- maxLen = dp[i];
- ans = cnt[i];
- } else if(dp[i] == maxLen) {
- ans += cnt[i];
- }
- }
- cout << ans << endl;
- return ans;
- }
- };
时间复杂度:O(n^2),其中 n 是数组 nums的长度。
空间复杂度:O(n)。
下面以示例 [10,2,5,3,7,101,4,6,5] 开始。
上面的每个元素的意义:以第二列,第二个元素 3,2为例,代表了在长度为 2的递增子序列中,尾数 ≥3的上升子序列的个数 总和 为 2(2→5 和 2→3)。
对于每一行,每一列的 末尾数字 是 递增 的(2,3,4,5)。
对于每一列,元素自上向下递减。因此,我们在插入一个新元素时,
首先寻找“生长点”,(类似 leetcode300 题的思路),二分查找 每一列的尾数,找到可以延长的 最长序列。
然后,在找到的列中,二分查找 可以插入的最大尾数。则新插入的数字可以在 可以插入的最大尾数 和 比它小的任意数字 之后插入。将这些序列个数求和即可。下面的动图描述了 在之前序列 [10,2,5,3,7,101,4,6,5]的基础上,再插入 ‘7’ 的算法流程:
我们定义两个矩阵。其中:
elms[i]表示长度为 i+1的递增序列中,可能为最长递增序列做出贡献 的 所有尾数(降序排列)。
ops[i][j]表示在长度为 i+1的递增序列中,尾数≥elms[i][j]的所有上升子序列的个数总和。二分查找用 STL 库函数 lower_bound 和 upper_bound。
- class Solution {
- public:
- int findNumberOfLIS(vector<int>& nums) {
- vector<vector<int>> elms, ops;
-
- for(int v : nums) {
- int pos, paths = 1;
-
- /* 一、二分查找 “生长点”。 */
- if(elms.size() == 0 || v > elms.back().back()) {
- pos = elms.size();
- } else {
- pos = lower_bound(elms.begin(), elms.end(), v, [](vector<int> &a, const int &b){
- return a.back() < b;
- }) - elms.begin();
- }
-
- /* 二、二分查找 “可以插入的最大尾数”。*/
- if(pos > 0) {
- int pre = pos - 1;
- int p2 = upper_bound(elms[pre].begin(), elms[pre].end(), v, greater<int>()) - elms[pre].begin();
- paths = ops[pre].back() - (p2? ops[pre][p2-1] : 0);
- }
-
- /* 三、计算以元素 v 结尾的, 长度为 pos + 1 的上升子序列个数,并累加前缀和。*/
- if(pos == elms.size()) {
- elms.push_back({v}), ops.push_back({paths});
- } else {
- elms[pos].push_back(v);
- ops[pos].push_back(paths + ops[pos].back());
- }
- }
-
- return ops.size()? ops.back().back() : 0;
- }
- };
时间复杂度:O(nlog(n))。
空间复杂度:O(n)。
我们注意到,在求 最长递增子序列的个数 的过程中,每添加一个元素,我们需要知道:
在该元素之前,比该元素小的元素中,以其为结尾的递增子序列的 最大长度 lmax;
长度 == 最大长度所对应的序列个数 cnt。则添加该元素后,以该元素为结尾的递增子序列的最大长度 = lmax+1,最大长度对应的个数 = cnt。
这和树状数组有什么关系呢?
我们定义在区间 [l,r]中的一种“值”,它包含:区间 [l,r]的最大值 max。
区间 [l,r]的最大值出现的次数 cnt。现在,考虑两个区间 [l1,r1]和 [l2,r2],两个区间没有重叠部分,两个区间的值分别为 V1和V2。如果将两个区间合并,则合并后的区间的值V12为多少?
如果 V1.max==V2.max,
则 V12.max=V1.max=V2.max,V12.cnt=V1.cnt+V2.cnt
如果 V1.max>V2.max,
则 V12.max=V1.max,V12.cnt=V1.cnt
如果 V1.max<V2.max,
则 V12.max=V2.max,V12.cnt=V2.cnt基于上述做法,我们定义一个新运算:V12=V1⊕V2。
这样,我们就可以用类似 区间和 的思路来处理 将互不重叠的 N个区间的值的合并。
- class Solution {
- class Node {
- public:
- int m, c;
- Node() : m(0), c(0) {}
- Node& operator+=(Node& b) {
- if(b.m > m)
- m = b.m, c = b.c;
- else if(b.m == m)
- c += b.c;
- return *this;
- }
- };
- void add(Node nodes[], int rk, Node &val, int N) {
- for(; rk <= N; rk += (rk & (-rk))) nodes[rk] += val;
- }
- Node query(Node nodes[], int rk) {
- Node res;
- for(; rk; rk -= (rk & (-rk))) res += nodes[rk];
- return res;
- }
- public:
- int findNumberOfLIS(vector<int>& nums) {
- if(nums.size() == 0) return 0;
-
- /* 离散化, 并用树状数组求解 */
- vector<int> numsort(nums.begin(), nums.end());
- sort(numsort.begin(), numsort.end());
- Node *nodes = new Node[nums.size() + 1], res = Node();
-
- for(int i : nums) {
- /* 离散化, 求出当前数字的 排名 - 1 */
- int rk = lower_bound(numsort.begin(), numsort.end(), i) - numsort.begin();
-
- /* 求出当前尾数的 最长序列长度 和 个数 */
- Node cur = query(nodes, rk);
- cur.m++, cur.c = max(cur.c, 1);
-
- /* 更新全局 最长序列长度 和 个数 */
- res += cur;
-
- /* 更新树状数组 */
- add(nodes, rk + 1, cur, nums.size());
- }
-
- return res.c;
- }
- };
时间复杂度:O(nlog(n))。
空间复杂度:O(n)。
引用链接:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。