动态规划专训5——子序列系列

动态规划题目中,常出现子序列相关问题,这里单独挑出来训练

注意:子序列与子数组不同,子数组要求的是原数组中连续的一部分,而子序列要的是原数组以原排列方式,抽取一部分

1.最长递增子序列

300. 最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列

1.状态表示:用dp[ i ]表示选到第i个元素的子序列的最长递增长度

2.状态转移方程:dp[ i ] = dp[ j ]  +  1

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

4.填表顺序:从左往右填

5.返回值:dp表中最大值

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> dp(n, 1);
        int ret = 1;
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j < i; ++j)
                if(nums[i] > nums[j])
                    dp[i] = max(dp[i], dp[j] + 1);
                    
            ret = max(ret, dp[i]);
        }

        return ret;
    }
};

这是ac代码

2.摆动序列

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列

给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度

1.状态表示:用 f [ i ]表示选到第i个元素的子序列以大结尾的最长摆动序列长度

                     用 g [ i ]表示选到第i个元素的子序列以小结尾的最长摆动序列长度

2.状态转移方程:

            f[ i ] = g [ j ] + 1

            g[ i ] = f [ j ] + 1

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

4.填表顺序:从左往右两个表一起填

5.返回值:两个表中的最大值

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> f(n, 1), g(n, 1);
        int ret = 1;
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j < i; ++j)
            {
                if(nums[i] > nums[j])
                    f[i] = max(f[i], g[j] + 1);
                if(nums[i] < nums[j])
                    g[i] = max(g[i], f[j] + 1);
            }
            ret = max(ret, max(f[i], g[i]));
        }

        return ret;
    }
};

这是ac代码

3.最长递增子序列的个数

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

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。

注意 这个数列必须是 严格 递增的

1.状态表示:用 len [ i ]表示以第 i 个元素为结尾的所有子序列中最长递增子序列的长度

                     用 count [ i ]表示以第 i 个元素为结尾的所有子序列中最长递增子序列的次数

2.状态转移方程:

                    if( len[ i ]  == len[ j ] + 1) count[ i ]  += count[ j ] ;

                    else if ( len[ i ]  < len[ j ] + 1) count[ i ] = count[ j ], len[ i ] = len[ j ] + 1;

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

4.填表顺序:从左往右两个表一起填

5.返回值:retcount

注意:完成这道题之前必须熟练掌握下面这个小算法,一次遍历如何找到数组中最大值出现个数

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> len(n, 1);
        auto count = len;
        int retcount = 1, retlen = 1;
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j < i; ++j)
            {
                if(nums[i] > nums[j])
                {
                    if(len[i] == len[j] + 1) count[i] += count[j];
                    else if(len[i] < len[j] + 1) count[i] = count[j], len[i] = len[j] + 1;
                }
            }

            if(retlen == len[i]) retcount += count[i];
            else if(retlen < len[i]) retcount = count[i], retlen = len[i];
        }

        return retcount;
    }
};

这是ac代码

4.最长数对链

646. 最长数对链

给你一个由 n 个数对组成的数对数组 pairs ,其中 pairs[i] = [lefti, righti] 且 lefti < righti 。

现在,我们定义一种 跟随 关系,当且仅当 b < c 时,数对 p2 = [c, d] 才可以跟在 p1 = [a, b] 后面。我们用这种形式来构造 数对链 。

找出并返回能够形成的 最长数对链的长度 。

你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造

注意:利用排序,对数据进行处理,使可以用动态规划来解决

1.状态表示:用dp[ i ]表示以第 i 个元素为结尾所有子序列中,最长子序列

2.状态转移方程:dp[ i ] = dp[ j ]  +  1

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

4.填表顺序:从左往右填

5.返回值:dp表中最大值

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) 
    {
        sort(pairs.begin(), pairs.end());
        int n = pairs.size();
        vector<int> dp(n, 1);
        int ret = 1;
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j < i; ++j)
                if(pairs[i][0] > pairs[j][1])
                    dp[i] = dp[j] + 1;
                    
            ret = max(ret, dp[i]);
        }

        return ret;
    }
};

这是ac代码

5.最长定差子序列

1218. 最长定差子序列

给你一个整数数组 arr 和一个整数 difference,请你找出并返回 arr 中最长等差子序列的长度,该子序列中相邻元素之间的差等于 difference 。

子序列 是指在不改变其余元素顺序的情况下,通过删除一些元素或不删除任何元素而从 arr 派生出来的序列

1.状态表示:用dp[ i ]表示以第 i 个元素为结尾所有子序列中,最长定差子序列

2.状态转移方程:dp[ i ] = dp[ j ]  +  1

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

4.填表顺序:从左往右填

5.返回值:dp表中最大值

class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) 
    {
        int n = arr.size();
        int ret = 1;
        vector<int> dp(n, 1);
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j < i; ++j)
                if(arr[i] == arr[j] + difference)
                    dp[i] = dp[j] + 1;

            ret = max(ret, dp[i]);
        }

        return ret;
    }
};

提交发现超时

再仔细思考,不难发现,只要有一个数与公差,我们可以退出来整个等差序列,于是我们可以借助哈希表来完成任务

于是,我们有以下ac代码

class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) 
    {
        unordered_map<int, int> hash;
        int n = arr.size();
        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;
    }
};

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

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

如果序列 X_1, X_2, ..., X_n 满足下列条件,就说它是 斐波那契式 的:

  • n >= 3
  • 对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}

给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。如果一个不存在,返回  0 。

(回想一下,子序列是从原序列 arr 中派生出来的,它从 arr 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列)

1.状态表示:用dp[ i ][ j ]表示以第 i 个元素, 第 j 个元素为结尾的所有子序列中最长的斐波那契序列长度(i  <  j)

2.状态转移方程:dp[ i ][ j ] = dp[ hash [ a ] ][ i ] + 1;

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

4.填表顺序:从上到下每一行,从左到右每一列

5.返回值:dp表中最大值(最大值为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;

        int ret = 2;
        vector<vector<int>> dp(n, vector<int>(n, 2));
        for(int j = 2; j < n; ++j)
        {
            for(int i = 1; i < j; ++i)
            {
                int a = arr[j] - arr[i];
                if(a < arr[i] && hash.count(a))
                    dp[i][j] = dp[hash[a]][i] + 1;
                ret = max(ret, dp[i][j]);
            }
        }

        return ret == 2 ? 0 : ret;
    }
};

这是ac代码

7.最长等差数列

1027. 最长等差数列

给你一个整数数组 nums,返回 nums 中最长等差子序列的长度

回想一下,nums 的子序列是一个列表 nums[i1], nums[i2], ..., nums[ik] ,且 0 <= i1 < i2 < ... < ik <= nums.length - 1。并且如果 seq[i+1] - seq[i]0 <= i < seq.length - 1) 的值都相同,那么序列 seq 是等差的

1.状态表示:用dp[ i ][ j ]表示以第 i 个元素, 第 j 个元素为结尾的所有子序列中最长的等差数列长度(i  <  j)

2.状态转移方程:dp[ i ][ j ] = dp[ hash [ a ] ][ i ] + 1;

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

4.填表顺序:从上到下每一行,从左到右每一列

5.返回值:dp表中最大值(最大值为2时找不到斐波那契子序列,返回0)

注意:由于给定数组是无序的,找到的值应该是离 i 最近的,这就要随 i 的遍历实时更新哈希表

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) 
    {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n, 2));
        unordered_map<int, int> hash;
        hash[nums[0]] = 0;

        int ret = 2;
        for(int i = 1; i < n; ++i)
        {
            for(int j = i + 1; j < n; ++j)
            {
                int a = nums[i] * 2 - nums[j];
                if(hash.count(a) && hash[a] < i)
                    dp[i][j] = dp[hash[a]][i] + 1;
                ret = max(ret, dp[i][j]); 
            }
            hash[nums[i]] = i;
        }

        return ret;
    }
};

这是ac代码

8.等差数列划分II-子序列

446. 等差数列划分 II - 子序列

给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。

如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列

1.状态表示:用dp[ i ][ j ]表示以第 i 个元素, 第 j 个元素为结尾的所有子序列中等差数列个数(i  <  j)

2.状态转移方程:dp[ i ][ j ] += (dp[e][i] + 1);

3.初始化:无需初始化

4.填表顺序:从上到下每一行,从左到右每一列

5.返回值:dp表的和

注意:由于数组无序,以及重复数字的影响,这里需要用值,下标数组的哈希表来对数据进行映射

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) 
    {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n));
        unordered_map<long long, vector<int>> hash;
        for(int i = 0; i < n; ++i)
            hash[nums[i]].push_back(i);

        int ret = 0;
        for(int i = 1; i < n; ++i)
        {
            for(int j = i + 1; j < n; ++j)
            {
                long long a = (long long)nums[i] * 2 - nums[j];
                if(hash.count(a))
                    for(auto e : hash[a])
                        if(e < i)
                            dp[i][j] += (dp[e][i] + 1);

                ret += dp[i][j];
            }
        }

        return ret;
    }
};

这是ac代码

  • 15
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值