leetcode 673最长递增自序列的个数 二分查找 *

O(n2)比较慢

#define MAX(x,y) (x>y?x:y)

int findNumberOfLIS(int* nums, int numsSize){
    int len[numsSize];//存储以nums[i]为结尾的最长子序列的长度
    int cnt[numsSize];//存储以nums[i]为结尾的最长子序列的个数
    int ans[numsSize+1];//存储长度为i的最长子序列的个数
    memset(len,0,sizeof(len));
    memset(cnt,0,sizeof(cnt));
    memset(ans,0,sizeof(ans));
    len[0]=1;
    cnt[0]=1;
    ans[1]=1;
    
    int maxx_lenth;
    int maxx=0;
    for(int i=1;i<numsSize;i++)
    {
        maxx_lenth=0;
        for(int j=0;j<i;j++)
        {
            if(nums[j]<nums[i])
            {
                maxx_lenth=MAX(maxx_lenth,len[j]);
            }
        }
        len[i]=maxx_lenth+1;
        

        int tempsum=0;
        for(int j=0;j<i;j++)
        {
            if(len[j]==maxx_lenth&&nums[j]<nums[i])
            {
                tempsum+=cnt[j];
            }
        }
        if(tempsum!=0)
        cnt[i]=tempsum;
        else
        cnt[i]=1;


        maxx=MAX(maxx,len[i]);

        ans[len[i]]+=cnt[i];
    }
    maxx=MAX(maxx,len[numsSize-1]);
    return ans[maxx];
}

优化后比较快
优化的思想是,在这个程序中涉及到求cnt[i]和最后的答案
前者要进行的操作是在小于i的所有数j中选择最大的len[j],求sigma(cnt[j]),len[j]==max
后者要进行的操作是在所有i中选择最大的len[j],求sigma(cnt[i]),len[i]==max

都涉及到求值为最大值的个数这一操作
所以可以维护最大值,然后如果当前值比最大值大,则重置cnt为当前值的cnt,并更新最大值
如果当前值和最大值一样则ans_cnt+=cnt[当前值]



int findNumberOfLIS(int* nums, int numsSize){
    int len[numsSize];//存储以nums[i]为结尾的最长子序列的长度
    int cnt[numsSize];//存储以nums[i]为结尾的最长子序列的个数
    len[0]=1;
    cnt[0]=1;
    
    int maxlen=1;
    int ans=1;
    for(int i=1;i<numsSize;i++)
    {
        len[i]=1;
        cnt[i]=1;
        for(int j=0;j<i;j++)
        {
            if(nums[j]<nums[i])
            {
                if(len[j]+1>len[i])
                {
                   len[i]=len[j]+1;
                   cnt[i]=cnt[j];
                }
                else if(len[j]+1==len[i])
                {
                    cnt[i]+=cnt[j];
                }
            }
        }
        if(len[i]>maxlen)
        {
           maxlen=len[i];
           ans=cnt[i];
        }
        else if(len[i]==maxlen)
        {
            ans+=cnt[i];
        }
    }
    return ans;
}

但还可以更快
首先先考虑只求一个序列的最长递增子序列的长度

官方题解
在这里插入图片描述
在这里插入图片描述

int lengthOfLIS(int* nums, int numsSize){
    int d[numsSize+1]; //长度为i<->d[i];
    d[1]=nums[0];
    int len=1;
    for(int i=1;i<numsSize;i++)
    {
        if(nums[i]>d[len])
        {
            len++;
            d[len]=nums[i];
        }
        else
        {
            //二分查找第一个比nums[i]小的
            int left=1,right=len,mid;
            while(left<=right)
            {
                mid=(left+right)>>1;
                if(d[mid]<nums[i])
                {
                    left=mid+1;
                }
                else
                    right=mid-1;
            }
            d[right+1]=nums[i];
        }
    }
    return len;
}

首先明确d[i][j]这个二维的数组,每次最后加入的数是单调递增的
在这里插入图片描述
整个算法的过程是遍历nums数组
对于每个数寻找他的插入位置,因为每个数的插入位置前面的数都要比他小,比如d[len][j]<当前要插入的数,插入后,len变成了len+1
第一步根据最后一步加入的数也就是1 4 6 7 寻找要插入的一维数组的位置,(第一个比他大的数)比如插入4的时候,他可以插在1后面,这时候长度变成了2,也就是d[2]这个数组
第二部在d[i-1]数组内部寻找准确插入的位置,在一维数组的内部是递减的,因为如果有个数比前面的数大,那他就可以插在后一个长度的位置上了。
就找第一个比他小的位置k,从k到最后的数都满足
所以cnt[i][当前位置的数]=cnt[i-1][k]+……+cnt[i-1][最后的数]
这一步又可以用前缀和进行优化,变成cnt[i-1][最后的数]-cnt[i-1][k-1]又因为cnt[i]是从0开始,cnt[i][0]=0,也就是比如k=2 照理来说应该是cnt[i-1][最后的数]-cnt[1][k-1],但是cnt[i]是这样的0,cnt[i][第一个数],cnt[i][第二个数]所以还是cnt[i-1][最后的数]-cnt[i-1][k],找到在len=i-1的插入位置后,再把当前的数pushback到d[len]里

仅作记录

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();
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/solution/zui-chang-di-zeng-zi-xu-lie-de-ge-shu-by-w12f/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值