子序列问题

目录

最长递增子序列

摆动序列

最长递增子序列的个数

最长数对链

最长定差子序列

最长的斐波那契子序列的长度

最长等差数列

等差数列划分II-子序列


声明:接下来主要使用动态规划来解决问题!!!

最长递增子序列

题目

思路

接下来,我们将屡试不爽的尝试使用以某个位置为结尾来分析问题

状态表示:dp[i]表示以i位置为结尾的递增序列中,最长子序列的长度。

状态转移方程:

初始化:全都初始化为1.

填表顺序:从左往右。

返回值:dp表中最大值。

代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int> dp(n,1);
        for(int i=1;i<n;i++)
            for(int j=0;j<i;j++)
                if(nums[j]<nums[i])
                    dp[i]=max(dp[i],dp[j]+1);
        return *max_element(dp.begin(),dp.end());
    }
};
摆动序列

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

以某个元素为结尾,此时可能呈“上升”趋势,也可能呈“下降”趋势,如果我们只是使用dp[i]来表示的话,并不能表达出这种趋势,下面我们使用两个dp表,分别为f[i]和g[i],意思分别为:

f[i]表示以i位置为结尾的子序列中,呈“上升”趋势的最长摆动序列的长度;

g[i]表示以i位置为结尾的子序列中,呈“下降”趋势的最长摆动序列的长度。

状态转移方程:

填表顺序:从左往右,两个表同时填。

返回值:两个表中的最大值。

代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n=nums.size();
        vector<int> f(n,1);
//以I位置为结尾的所有子序列中,最后一个位置呈“上升”的最长递增子序列
        vector<int> g(n,1);
//以I位置为结尾的所有子序列中,最后一个位置呈“下降”的最长递增子序列
        for(int i=1;i<n;i++)
        {
            for(int j=0;j<i;j++)
            {
                if(nums[j]>nums[i])
                    g[i]=max(g[i],f[j]+1);
                if(nums[j]<nums[i])
                    f[i]=max(f[i],g[j]+1);
            }
        }
        int a=*max_element(f.begin(),f.end());
        int b=*max_element(g.begin(),g.end());
        return max(a,b);
    }
};
最长递增子序列的个数

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

但是在分析这道题之前,我们先讲解一种“思想”,比如找出一个数组中最大值出现的次数,我们可能会先遍历一遍找出最大值,然后再遍历一遍找出最大值出现的次数。

但是我们不妨改变一下做法:定义两个变量,maxval用来保存已遍历过位置的最大值,count用来保存最大值出现的次数。

当nums[i]<maxval,直接无视;

当nums[i]=maxval,count++;

当nums[i]>maxval,count=1,maxval=nums[i]。

下面将上面这种思路用到解题中。

状态表示:len[i]表示以i位置为结尾的子序列中,最长递增子序列的长度;

                  count[i]表示以i位置为结尾的子序列中,最长递增子序列出现的次数。

状态转移方程:

初始化:全都初始化为1.

填表顺序:从左往右。

返回值;最大长度出现的次数。

代码

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n=nums.size();
        vector<int> len(n,1);
        vector<int> count(n,1);
        for(int i=1;i<n;i++)
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    if(len[j]+1==len[i]) count[i]+=count[j];
                    else if (len[j]+1>len[i]){
                        len[i]=len[j]+1;
                        count[i]=count[j];
                    }
                }
            }
        int maxlen=len[0],ans=1;
        for(int i=1;i<n;i++){
            if(len[i]==maxlen) ans+=count[i];
            else if(len[i]>maxlen){
                ans=count[i];
                maxlen=len[i];
            }
        }
        return ans;
    }
};
最长数对链

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

但是在解决问题前,我们先回顾之前的题目,在确定以某个位置为结尾时,状态是确定的,只需考虑该位置之前的元素,但是这道题不一样,可能还需要考虑该位置后面的元素,为了解决这个问题,我们先根据数对的第一个元素对数对进行从小到大排序,这样就不必再考虑确定了某位置为结尾还需考虑该位置之后元素的情况了。

接下来,就和《最长递增子序列》这道题一模一样了。

 

状态表示:dp[i]表示以i位置为结尾的递增序列中,最长子序列的长度。

状态转移方程:

初始化:全都初始化为1.

填表顺序:从左往右。

返回值:dp表中最大值。

代码

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        sort(pairs.begin(),pairs.end());
        int n=pairs.size();
        vector<int> dp(n,1);
        for(int i=1;i<n;i++)
            for(int j=0;j<i;j++){
                if(pairs[j][1]<pairs[i][0])
                    dp[i]=max(dp[i],dp[j]+1);
            }
        return *max_element(dp.begin(),dp.end());
    }
};
最长定差子序列

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

下面在解决本题时,使用优化的方法来解决,使用哈希表来代替dp表,哈希表的K代表元素,V代表以该元素为结尾的等差子序列长度。

代码

class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) {
        int n=arr.size();
        unordered_map<int,int> hash;
        hash[arr[0]]=1;
        int ret=1;
        for(int i=1;i<n;i++){
            hash[arr[i]]=hash[arr[i]-difference]+1;
            ret=max(ret,hash[arr[i]]);
        }
        return ret;
    }
};
最长的斐波那契子序列的长度

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

如果定义的是一维dp,我们会发现不能确定连续三个斐波那契数,因此我们使用二维dp来解决。

其中0<i<j<n-1,并且对于c+a=b,c有三种情况,c在a之前,c在a和b之间,c不存在。 

为了便于更快确定c是否存在并且在哪个位置,使用哈希表来存储每个值的位置。

状态表示:dp[i][j]表示以i,j位置为结尾的斐波那契数列的长度。

状态转移方程:if(hash.count(arr[j]-arr[i]) && hash[arr[j]-arr[i]]<i)

                                dp[i][j]=dp[hash[arr[j]-arr[i]]][i]+1.

初始化:全都初始化为2.

返回值:如果最大长度是2则返回0,否则返回最大长度。

代码

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n=arr.size();
        unordered_map<int,int> hash;
        for(int i=0;i<n;i++)
            hash[arr[i]]=i;
        vector<vector<int>> dp(n,vector(n,2));
        //dp[i][j]=dp[k][i]+1;
        int ret=2;
        for(int j=2;j<n;j++)
            for(int i=1;i<j;i++){
                if(hash.count(arr[j]-arr[i]) && hash[arr[j]-arr[i]]<i)
                {
                    dp[i][j]=dp[hash[arr[j]-arr[i]]][i]+1;
                    ret=max(ret,dp[i][j]);
                }
            }
        return ret<3?0:ret;
    }
};
最长等差数列

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

但是经过分析会发现使用一维dp是不能解决的,接下来尝试使用二维dp来解决。

其中0<i<j<n-1,并且对于c+a=b,c有三种情况,c在a之前,c在a和b之间,c不存在。  

状态表示:dp[i][j]表示以i,j为结尾的所有等差数列中,数列的最大长度。

状态转移方程:

int a=2*nums[i]-nums[j];

if(hash.count(a))

        dp[i][j]=dp[hash[a]][i]+1;

初始化:全部都初始化为2.

填表顺序:从上到下,从左往右,固定倒数第二个位置,枚举倒数第一个位置。

为什么不是固定倒数第一个位置,枚举倒数第二个位置,而是固定倒数第二个位置,枚举倒数第一个位置,是因为这样的话,需要不断的更新哈希表。

代码

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) {
        int n=nums.size();
        unordered_map<int,int> hash;
        hash[nums[0]]=0;
        vector<vector<int>> dp(n,vector<int>(n,2));
        int ret=2;
        for(int i=1;i<n-1;i++){
            for(int j=i+1;j<n;j++){
                if(hash.count(2*nums[i]-nums[j])){
                    dp[i][j]=dp[hash[2*nums[i]-nums[j]]][i]+1;
                    ret=max(ret,dp[i][j]);
                }
            }
            hash[nums[i]]=i;
        }
        return ret;
    }
};
等差数列划分II-子序列

题目

思路

我们依旧屡试不爽的尝试使用以某个位置为结尾来分析问题。

但是经过分析会发现使用一维dp是不能解决的,接下来尝试使用二维dp来解决。

其中0<i<j<n-1,并且对于c+a=b,c有三种情况,c在a之前,c在a和b之间,c不存在。  

状态表示:dp[i][j]表示以i,j为结尾的所有等差数列中,数列的个数。

状态转移方程:

long long a=(long long)2*nums[i]-nums[j];

if(hash.count(a))

for(int x:hash[a])

        if(x<i)

              dp[i][j]+=dp[x][i]+1;

初始化:全都初始化为0.

填表顺序:固定倒数第一个位置,枚举倒数第二个位置。 

代码

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int n=nums.size();
        unordered_map<long long,vector<int>> hash;
        for(int i=0;i<n;i++)
            hash[nums[i]].push_back(i);
        vector<vector<int>> dp(n,vector<int>(n));
        int ret=0;
        for(int j=2;j<n;j++){
            for(int i=1;i<j;i++){
                long long a=(long long)2*nums[i]-nums[j];
                if(hash.count(a)){
                    for(int x:hash[a]){
                        if(x<i)
                            dp[i][j]+=dp[x][i]+1;
                    }
                    ret+=dp[i][j];
                }
            }
        }
        return ret;
    }
};

  • 81
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 86
    评论
评论 86
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

新绿MEHO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值