【动态规划四】子序列问题

目录

leetcode题目

一、最长递增子序列

二、摆动序列

三、最长递增子序列的个数

四、最长数对链

五、最长定差子序列

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

七、最长等差数列

八、等差数列划分 II


leetcode题目

一、最长递增子序列

300. 最长递增子序列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/longest-increasing-subsequence/1.题目解析

子序列与子数组的区别在于:子序列可以不连续,但是要求元素顺序与原数组元素顺序不变~

因此子序列本质是包含子数组的~, 本题要求求出最长的严格递增子序列的长度

2.算法分析

1.状态表示

dp[i] 表示 以 i 位置元素为结尾的所有子序列中, 最长递增子序列的长度

2.状态转移方程

3.初始化
dp表全部初始化成1

4.填表顺序

从左向右

5.返回值

返回dp表的最大值

3.算法代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        //1.创建dp表
        int n = nums.size();
        vector<int> dp(n, 1);
        //2.填表 + 返回值
        int ret = 1;
        for(int i = 1; i < n; i++)
        {
            for(int j = i - 1; j >= 0; j--)
            {
                if(nums[j] < nums[i]) 
                    dp[i] = max(dp[i], dp[j] + 1);  
            }         
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

二、摆动序列

376. 摆动序列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/wiggle-subsequence/description/

1.题目解析

摆动序列是指相邻元素之间的差值正负交替,只有1个元素或2个元素也叫做摆动序列,求数组中满足摆动序列的最长子序列

2.算法分析

1.状态表示

f[i] 表示 以 i 位置元素为结尾的所有子序列中,最后呈现"上升"趋势,最长摆动序列的长度

g[i] 表示 以 i 位置元素为结尾的所有子序列中,最后呈现"下降"趋势,最长摆动序列的长度

2.状态转移方程

3.初始化

两个表都初始化成1

4.填表顺序

从左向右两个表一起填

5.返回值

两个表的最大值

3.算法代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums)
    {
        //1.创建dp表
        int n = nums.size();
        vector<int> f(n, 1), g(n, 1);
        //2.填表 + 返回值
        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(g[j] + 1, f[i]);
                if(nums[i] < nums[j])
                    g[i] = max(f[j] + 1, g[j]);
            }
            ret = max(ret, max(f[i], g[i]));
        }
        return ret;
    }
};

三、最长递增子序列的个数

673. 最长递增子序列的个数 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/number-of-longest-increasing-subsequence/1.题目解析

求最长递增子序列的个数(严格递增)

2.算法分析

补充知识: 在数组中找出最大值出现的次数(要求遍历一次)

maxval = arr[0], count = 1, 遍历到数组中的元素x

1. x == maxval: count += 1

2. x < maxval: 无视

3. x > maxval: maxval = x, count = 0

1.状态表示

len[i]: 以 i 位置元素为结尾的所有的子序列中,最长递增子序列的长度

count[i]: 以 i 位置元素为结尾的所有的子序列中,最长递增子序列的个数

2.状态转移方程(参考上面的补充知识)

len[i] = count[i] = 1

j在[0, i-1]遍历, 在nums[j] < nums[i]的前提下:

①len[j] + 1 == len[i]:count[i] += count[j]

②len[j] + 1 < len[i]:无视

③len[j]+1 > len[i]:len[i] = len[j] + 1, count[i] = count[j]

3.初始化

两个表都初始化成1

4.填表顺序

从左往右两个表一起填

5.返回值

遍历len表的同时找出最大值出现的次数

3.算法代码

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) 
    {
        //1.创建dp表
        int n = nums.size();
        vector<int> len(n, 1), count(n, 1);
        //2.填表 + 返回值
        int retlen = 1, retcount = 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];
                }
            }
            if(retlen == len[i]) retcount += count[i];
            else if(retlen < len[i]) retlen = len[i], retcount = count[i];
        }
        return retcount;
    }
};

四、最长数对链

646. 最长数对链 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/maximum-length-of-pair-chain/1.题目解析

给定一个数组,数组每个元素是一个数对[left, right], left 是严格 < right 的,[a, b], [c, d], [e, f]要能构成数对链,要求b < c, d < e,  求能构成的最长数对链的长度

2.算法分析

依旧是动态规划,但是在分析状态转移方程时,求dp[i]往往要依赖前一个dp[i-1]的值或者依赖后一个dp[i+1]的值,而本题分析到某一个数对时,无法保证要用到的上一个数对是在该数对的前面

比如 [[1, 2], [7, 8], [4, 5]]这个数组,最终形成的数对链中的[4, 5]就链到了[7, 8]的前面

因此我们需要先对数组排序,  而排序之后:

对于[a, b], [c, d],  d > c >= a, 推出 d > a, 因此[c, d]是不可能链到[a, b]后面的, 这样就可以得出状态转义移方程了~

1.状态表示

dp[i]: 以 i 位置元素为结尾的所有数对链中,最长的数对链的长度

2.状态转移方程

3.初始化

dp表全部初始化成1

4.填表顺序

从左往右填表

5.返回值

dp表的最大值

3.算法代码

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) 
    {
        //排序
        sort(pairs.begin(), pairs.end());
        //1.创建dp表
        int n = pairs.size();
        vector<int> dp(n, 1);
        //2.填表 + 返回值
        int ret = 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);
            }
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

五、最长定差子序列

1218. 最长定差子序列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/description/1.题目解析

给定一个数组和difference, 返回最长定差子序列(相邻元素之间差为difference)的长度

2.算法分析

1.状态表示

dp[i]: 以 i 位置元素为结尾的所有子序列中,最长的定差子序列的长度

2.状态转移方程

3.初始化

hash[arr[0]] = 1

4.填表顺序

从左往右

5.返回值

dp表的最大值(哈希表的最大值)

3.算法代码

class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) 
    {
        //1.创建dp表(哈希表)
        unordered_map<int, int> hash; //arr[i]-dp[i]
        //2.初始化
        hash[arr[0]] = 1;
        //3.填表 + 返回值
        int ret = 1;
        for(int i = 1; i < arr.size(); i++)
        {
            hash[arr[i]] = hash[arr[i] - difference] + 1; //这一行既保证了b不存在的时候,hash[i]是1, 也保证了b是最后一个b
            ret = max(ret, hash[arr[i]]);
        }
        return ret;
    }
};

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

873. 最长的斐波那契子序列的长度 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/length-of-longest-fibonacci-subsequence/1.题目解析

给定一个数组,求满足斐波那契式的最长的子序列的长度

2.算法分析

1.状态表示

dp[i][j]:以 i 位置 以及 j 位置 为结尾的所有子序列中,最长的斐波拉契子序列的长度

ps:开始是定义的dp[i], 但是推不出状态转移方程, 因为只知道子序列的个数,但是无法确定具体的斐波那契式的子序列,也就无法根据dp[i]前面的值推导出dp[i]~

题目七与题目八的状态表示也是同样的得到方法~

2.状态转移方程

3.初始化

把dp表中的所有值都初始化成2(只会用到dp表中 i < j 的位置)

4.填表顺序

dp[i][j] = dp[k][i] + 1,  k < i && i < j, 因此从上往下填表即可

5.返回值

ret < 3 ? 0 : dp表中的最大值

3.算法代码

class Solution 
{
public:
    int lenLongestFibSubseq(vector<int>& arr) 
    {
        //1.将元素和下标绑定丢进哈希表
        int n = arr.size();
        unordered_map<int, int> hash;
        for(int i = 0; i < n; i++)
            hash[arr[i]] = i;
        //2.创建dp表
        vector<vector<int>> dp(n, vector<int>(n, 2));
        //3.填表+返回值
        int ret = 2;
        for(int j = 2; j < n; j++) //最后一个位置
        {
            for(int i = 1; i < j; i++) //倒数第二个位置
            {
                int a = arr[j] - arr[i];
                if(hash.count(a) && hash[a] < i) 
                    dp[i][j] = dp[hash[a]][i] + 1;
                ret = max(ret, dp[i][j]);
            }
        }
        return ret < 3 ? 0 : ret;
    }
};

七、最长等差数列

1027. 最长等差数列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/longest-arithmetic-subsequence/description/1.题目解析

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

2.算法分析

1.状态表示

dp[i][j]:以 i 位置 以及 j 位置 为结尾的所有子序列中,最长的等差序列的长度

2.状态转移方程

3.初始化

把dp表中的所有值都初始化成2(只会用到dp表中 i < j 的位置)

4.填表顺序

先固定倒数第2个数(b),再枚举倒数第一个数(c) --- 因为优化中,我们要保存的的是离i位置最近的a元素的下标,因此选择把b固定住,让c去移动

5.返回值

dp表中的最大值

3.算法代码

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) 
    {
        //优化
        unordered_map<int, int> hash;
        hash[nums[0]] = 0;

        //1.创建dp表 + 初始化
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n, 2));
        //2.填表 + 返回值
        int ret = 2;
        for(int i = 1; i < n; i++) //固定倒数第2个数
        {
            for(int j = i + 1; j < n; j++) //枚举倒数第一个数
            {
                int a = 2 * nums[i] - nums[j];
                if(hash.count(a))
                    dp[i][j] = dp[hash[a]][i] + 1;
                ret = max(ret, dp[i][j]);
            }
            hash[nums[i]] = i; //i位置固定完之后,将nums[i]与i存入哈希表
        }
        return ret;
    }
};

八、等差数列划分 II

446. 等差数列划分 II - 子序列 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/arithmetic-slices-ii-subsequence/1.题目解析

求数组中等差子序列的个数(本题的等差子序列至少要有3个元素)

2.算法分析

1.状态表示

dp[i][j]:以 i 位置 以及 j 位置 为结尾的所有子序列中,等差子序列的个数

2.状态转移方程

3.初始化

dp表所有的值都初始化0(因为最坏情况下,两个元素是无法构成等差子序列的)

4.填表顺序

固定倒数第1个数,枚举倒数第2个数

5.返回值

dp表所有元素的和

3.算法代码

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

        //1.创建dp表 + 初始化
        vector<vector<int>> dp(n, vector<int>(n));
        //2.填表 + 返回值
        int sum = 0;
        for(int j = 2; j < n; j++) //固定倒数第1个数
        {
            for(int i = 1; i < j; i++) //枚举倒数第2个数
            {
                long long a = (long long)2 * nums[i] - nums[j];
                if(hash.count(a))
                {
                    for(auto k : hash[a])
                    {
                        if(k < i) 
                            dp[i][j] += dp[k][i] + 1;
                    }
                }
                sum += dp[i][j];
            }
        }
        return sum;
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值