【动态规划】子序列系列

简介: 【动态规划】子序列系列

动态规划(子序列系列)

1. 最长递增子序列

题目链接

  1. 状态表示
    dp[i]表示到 i 位置时,所有子序列当中最长的子序列的长度
  2. 状态转移方程

  3. 初始化
    表中的所有数据初始化为1
  4. 填表
    从左到右
  5. 返回值
    返回整个dp表里面最大的值

AC代码:

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

2. 摆动序列

题目链接

  1. 状态表示
    f[i]表示:以 i 位置为结尾的所有子序列当中,最后一个位置是上升的最长摆动序列的长度
    g[i]表示:以 i 位置为结尾的所有子序列当中,最后一个位置是下降的最长摆动序列的长度
  2. 状态转移方程

  3. 初始化
    表中的所有值初始化为1
  4. 填表
    从左到右
  5. 返回值
    返回两个表中的最大值

AC代码:

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);
                else if (nums[i] < nums[j]) g[i] = max(g[i], f[j] + 1);
            }
            ret = max(ret, max(f[i], g[i]));
        }
        return ret;
    }
};

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

题目链接

这里需要用到一种思想:

如何一次遍历数组,就可以找到最大数出现的次数

代码实现:

#include <iostream>
using namespace std;
int main()
{
  int arr[] = {2, 3, 1, 234, 43, 342, 234, 5, 34, 43, 8, 342};
  int n = sizeof(arr)/sizeof(arr[0]);
  int maxval = 0;
  int count = 0;
  for (int i = 0; i < n; i++)
  {
    if (arr[i] > maxval) maxval = arr[i], count = 1;
    else if (arr[i] == maxval) count++;
  }
  cout << maxval << endl;
  cout << count << endl;
  return 0;
}
  1. 状态表示
    len[i]表示以 i 位置为结尾所有子序列当中,最长递增子序列的长度
    count[i]表示以 i 位置为结尾所有子序列当中,最长递增子序列的个数
  2. 状态转移方程

  3. 初始化
    所有值都初始化为1
  1. 填表
    从左到右
  2. 返回值
    count表最后一个

AC代码:

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

4. 最长数对链

题目链接

分析:状态表示以某个位置为结尾的时候,后面的元素不能影响当前的填表,但是这个题目已经影响打了,所有需要将数组排序

  1. 状态表示
    dp[i]表示以 i 位置为结尾的所有数对链当中,最长的数对链的长度
  2. 状态转移方程

  3. 初始化
    所有初始化为1
  1. 填表
    从做到右
  2. 返回值
    返回整个表的最大值

AC代码:

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] = max(dp[i], dp[j] + 1);
                }
            }
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

5. 最长定差子序列

题目链接

  1. 状态表示
    dp[i]表示到 i 位置时,所有的子序列当中最长的定差子序列的长度
  2. 状态转移方程

  3. 初始化
    将第一个元素对应的dp值初始化为1
  4. 填表
    从左到右
  5. 返回值
    返回整个dp表里的最大值

AC代码:

class Solution 
{
public:
    int longestSubsequence(vector<int>& arr, int difference) 
    {
        unordered_map<int, int> hash;
        hash[arr[0]] = 1;
        int ret = 1;
        for (int i = 1; i < arr.size(); i++)
        {
            hash[arr[i]] = hash[arr[i] - difference] + 1;
            ret = max(ret, hash[arr[i]]);
        }
        return ret;
    }
};

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

题目链接

  1. 状态表示
    dp[i][j]表示以 i j 为结尾的所有子序列当中,最长的斐波那契数列的长度
  2. 状态转移方程

    优化:将数组的元素和下标绑定,方便查找
  1. 初始化
  2. 填表
  3. 返回值
    返回值可能小于3, 这是应该返回0

AC代码:

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<int>(n, 2));
        int ret = 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 < 3 ? 0 : ret;
    }
};

7. 最长等差数列

题目链接

  1. 状态表示
    dp[i][j] 表示 以 i j 为结尾的所有子序列当中最长的等差子序列的长度
  2. 状态转移方程

    优化:一边dp一边保存离它最近元素的下标,当 i 位置填完之后将它填入哈希表中即可。所以需要先固定第倒数第二个元素,接着固定倒数第一个元素
  1. 初始化
  2. 填表
  3. 返回值
    返回是整个dp表里的最大值

AC代码:

class Solution 
{
public:
    int longestArithSeqLength(vector<int>& nums) 
    {
        unordered_map<int, int> hash;
        int n = nums.size();
        hash[nums[0]] = 0;
        vector<vector<int>> dp(n, vector<int>(n, 2));
        int ret = 2;
        for (int i = 1; i < n; i++)
        {
            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;
        }
        return ret;
    }
};

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

题目链接

  1. 状态表示
    dp[i][j]表示以 i j 为是等差数列的子序列的个数
  2. 状态表示

  3. 初始化
  4. 填表
  5. 返回值

AC代码:

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 sum = 0;
        for (int j = 2; j < n; j++) // 固定倒数第一个
        {
            for (int i = 1; i < j; i++)
            {
                long long a = (long long)nums[i] * 2 - nums[j];
                if (hash.count(a))
                {
                    for (auto k : hash[a])
                    {
                        if (k < i) dp[i][j] += dp[k][i] + 1;
                        else break;
                    }
                }
                sum += dp[i][j];
            }
        }
        return sum;
    }
};
相关文章
|
6月前
|
机器学习/深度学习 人工智能 自然语言处理
Qwen3:小而强,思深,行速
Qwen3(千问3)于北京时间4月29日凌晨发布,是Qwen系列大型语言模型的最新成员,具备全系列、开源最强、混合推理等特性。它包括两款MoE模型(Qwen3-235B-A22B和Qwen3-30B-A3B)及六个Dense模型,支持119种语言。Qwen3在代码、数学和通用能力测试中超越行业顶尖模型,如DeepSeek-R1和Grok-3。其旗舰版Qwen3-235B-A22B仅需4张H20即可本地部署,成本为DeepSeek-R1的35%。此外,Qwen3原生支持思考模式与非思考模式切换,降低复杂任务门槛,并支持MCP协议优化Agent架构。
6081 1
|
机器学习/深度学习 PyTorch 算法框架/工具
pytorch中nn.ReLU()和F.relu()有什么区别?
pytorch中nn.ReLU()和F.relu()有什么区别?
994 0
|
JSON 资源调度 安全
CCF-CSP认证历年题解
CCF-CSP认证历年题解
2754 1
CCF-CSP认证历年题解
|
3天前
|
搜索推荐 编译器 Linux
一个可用于企业开发及通用跨平台的Makefile文件
一款适用于企业级开发的通用跨平台Makefile,支持C/C++混合编译、多目标输出(可执行文件、静态/动态库)、Release/Debug版本管理。配置简洁,仅需修改带`MF_CONFIGURE_`前缀的变量,支持脚本化配置与子Makefile管理,具备完善日志、错误提示和跨平台兼容性,附详细文档与示例,便于学习与集成。
271 116
|
18天前
|
域名解析 人工智能
【实操攻略】手把手教学,免费领取.CN域名
即日起至2025年12月31日,购买万小智AI建站或云·企业官网,每单可免费领1个.CN域名首年!跟我了解领取攻略吧~
|
12天前
|
安全 Java Android开发
深度解析 Android 崩溃捕获原理及从崩溃到归因的闭环实践
崩溃堆栈全是 a.b.c?Native 错误查不到行号?本文详解 Android 崩溃采集全链路原理,教你如何把“天书”变“说明书”。RUM SDK 已支持一键接入。
663 219
|
5天前
|
数据采集 人工智能 自然语言处理
Meta SAM3开源:让图像分割,听懂你的话
Meta发布并开源SAM 3,首个支持文本或视觉提示的统一图像视频分割模型,可精准分割“红色条纹伞”等开放词汇概念,覆盖400万独特概念,性能达人类水平75%–80%,推动视觉分割新突破。
349 34
Meta SAM3开源:让图像分割,听懂你的话