LeetCode 一周总结

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; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 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;
    }
};


上一篇:2021-11-03第八天 接雨水


下一篇:SqlServer基础--SQLOS 的任务调度(转)