【动态规划篇】专题(六):子序列问题——不连续的艺术

【动态规划篇】专题(六):子序列问题——不连续的艺术

文章目录

LIS 模型及其衍生:回头看,全是风景

一、 前言:从 O(N) 到 O(N²)

💬 开篇:和子数组(必须连续)不同,子序列可以跳着选。

🚀 核心心法状态定义dp[i] 表示以 i 位置结尾的最长子序列…状态转移:因为不连续,所以 i 可以接在 0i-1 任何一个符合条件的 j 后面。因此通常需要一个双层循环高阶技巧:如果一个数定不下规律(比如等差、斐波那契),那就定两个数dp[i][j] 表示以 ij 结尾)。

二、 最长递增子序列 (Medium)

2.1 题目描述

题目链接300. 最长递增子序列

描述
找到最长严格递增子序列的长度。

示例
输入:[10,9,2,5,3,7,101,18]
输出:4 ([2,3,7,101])

2.2 核心思路:LIS 模型

  • 状态dp[i] 表示以 nums[i]结尾的最长递增子序列长度。
  • 转移
    我站在 i 位置,往回看所有 j (0 <= j < i)。
    如果 nums[j] < nums[i],说明我能接在 j 后面。
    我要选一个最长的 dp[j] 接上去。
    dp[i] = max(dp[j] + 1)

2.3 代码实现

classSolution{public:intlengthOfLIS(vector<int>& nums){int n = nums.size();// 初始化为 1,因为每个元素自己就是一个长度为 1 的子序列 vector<int>dp(n,1);int ret =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);}} ret =max(ret, dp[i]);}return ret;}};

(注:这道题有 O(NlogN) 的贪心+二分据解法,但那是贪心专题的内容,这里专注 DP)


三、 摆动序列 (Medium)

3.1 题目描述

题目链接376. 摆动序列

描述
差值正负交替。求最长子序列长度。

示例
输入:[1,7,4,9,2,5]
输出:6 (差值:+6, -3, +5, -7, +3)

3.2 状态定义:波峰与波谷

我们不仅要知道长度,还要知道结尾是还是,才能决定下一个数怎么接。

  • f[i]:以 i 结尾,且最后一步是 上升 的最长摆动序列。
  • g[i]:以 i 结尾,且最后一步是 下降 的最长摆动序列。

3.3 代码实现

classSolution{public:intwiggleMaxLength(vector<int>& nums){int n = nums.size();// 初始化为 1 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[j]< nums[i]){// 此时是上升,接在之前的下降 g[j] 后面 f[i]=max(f[i], g[j]+1);}elseif(nums[j]> nums[i]){// 此时是下降,接在之前的上升 f[j] 后面 g[i]=max(g[i], f[j]+1);}} ret =max(ret,max(f[i], g[i]));}return ret;}};

(注:此题 O(N) 贪心解法更优,但 DP 解法有助于理解状态机思想)


四、 最长递增子序列的个数 (Medium)

4.1 题目描述

题目链接673. 最长递增子序列的个数

描述
同样是 LIS,这次要问:长度等于 LIS 的子序列一共有几个?

4.2 双重状态

只记录长度不够了,还要记录个数

  • len[i]:以 i 结尾的最长长度。
  • count[i]:以 i 结尾的最长长度的方案数

转移逻辑
nums[j] < nums[i] 时:

  1. 如果 len[j] + 1 > len[i]
    说明找到了一个更长的序列。更新 len[i],并且 count[i]重置count[j]
  2. 如果 len[j] + 1 == len[i]
    说明找到了一个长度一样长的序列。len[i] 不变,但 count[i]累加count[j]

4.3 代码实现

classSolution{public:intfindNumberOfLIS(vector<int>& nums){int n = nums.size(); vector<int>len(n,1),count(n,1);int maxLen =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]){// 发现更长的,重置 len[i]= len[j]+1; count[i]= count[j];}elseif(len[j]+1== len[i]){// 长度相同,累加方案数 count[i]+= count[j];}}} maxLen =max(maxLen, len[i]);}// 统计所有达到 maxLen 的个数int ret =0;for(int i =0; i < n; i++){if(len[i]== maxLen) ret += count[i];}return ret;}};

五、 最长数对链 (Medium)

5.1 题目描述

题目链接646. 最长数对链

描述
[a, b] 后面能接 [c, d] 当且仅当 b < c。求最长链。

5.2 预处理:排序

这题简直就是 LIS 的翻版。唯一的区别是:数组可能是乱序的。
所以第一步:根据第一个元素排序
然后就是标准的 LIS 模板:if pairs[j][1] < pairs[i][0],则接上。

5.3 代码实现

classSolution{public:intfindLongestChain(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[j][1]< pairs[i][0]){ dp[i]=max(dp[i], dp[j]+1);}} ret =max(ret, dp[i]);}return ret;}};

六、 最长定差子序列 (Medium)

6.1 题目描述

题目链接1218. 最长定差子序列

描述
求差值固定为 difference 的最长子序列。
arr.length <= 10^5

6.2 优化:Hash Map

如果还用双层循环 O(N²),在这道题 10^5 的数据量下会超时
关键点:这题的差是固定的!
对于 x = arr[i],我们要找的前一个数必然是 x - difference
我们可以用哈希表 hash[val] 记录以 val 结尾的最长长度。
状态转移变成 O(1):hash[x] = hash[x - diff] + 1

6.3 代码实现

classSolution{public:intlongestSubsequence(vector<int>& arr,int difference){ unordered_map<int,int> hash;// val -> lengthint ret =1;for(int x : arr){// 直接查找前一个数是否存在 hash[x]= hash[x - difference]+1; ret =max(ret, hash[x]);}return ret;}};

七、 最长的斐波那契子序列的长度 (Medium)

7.1 题目描述

题目链接873. 最长的斐波那契子序列的长度

描述
找最长的子序列,满足 x_i + x_{i+1} = x_{i+2}

7.2 升维:双指针 DP

一个数确定不了斐波那契数列,两个数才能确定。

  • 状态dp[i][j] 表示以 ij (i < j) 结尾的斐波那契子序列长度。
  • 转移
    nums[j] 是当前数,nums[i] 是前一个数。
    我们要找前前一个数 target = nums[j] - nums[i]
    如果能找到这个 target (下标为 k),那么:
    dp[i][j] = dp[k][i] + 1

优化:用哈希表预存 Value -> Index 的映射,方便快速找 k

7.3 代码实现

classSolution{public:intlenLongestFibSubseq(vector<int>& arr){int n = arr.size();// 值 -> 下标 映射 unordered_map<int,int> idxMap;for(int i =0; i < n; i++) idxMap[arr[i]]= i;// dp[i][j] 以 i, j 结尾 vector<vector<int>>dp(n,vector<int>(n,2));int ret =0;// 先固定 j (最后一个数)for(int j =2; j < n; j++){// 再枚举 i (倒数第二个数)for(int i =1; i < j; i++){int target = arr[j]- arr[i];// 如果 target 存在,且在 i 之前if(target < arr[i]&& idxMap.count(target)){int k = idxMap[target]; dp[i][j]= dp[k][i]+1; ret =max(ret, dp[i][j]);}}}return ret <3?0: ret;}};

八、 最长等差数列 (Medium)

8.1 题目描述

题目链接1027. 最长等差数列

描述
求最长的等差子序列长度。公差不固定。

8.2 思路

和斐波那契几乎一样,两个数确定公差

  • 状态dp[i][j]i, j 结尾的等差数列长度。
  • 公差diff = nums[j] - nums[i]
  • 前一个数target = nums[i] - diff

这题可以直接存下标,也可以在遍历过程中动态维护 Hash。

8.3 代码实现

classSolution{public:intlongestArithSeqLength(vector<int>& nums){int n = nums.size();// dp[i][j] 表示以 i 和 j 结尾 vector<vector<int>>dp(n,vector<int>(n,2));int ret =2;// 为了加速找 k,可以用 map 存// 但这题数据范围小,也可以遍历,或者边走边存// 既然已经 O(N^2),最内层最好是 O(1)// 这里用一个 map 数组,indexMap[x] 表示值为 x 的下标// 实际上这题可以直接枚举公差,或者用 nums[i] 的值域做 hash// 下面是标准双指针解法,配合 hash 优化// 值 -> 下标// 注意:因为有重复元素,我们要在遍历过程中动态更新 hash unordered_map<int,int> hash;for(int i =0; i < n; i++){for(int j = i +1; j < n; j++){int target =2* nums[i]- nums[j];// nums[i] - (nums[j] - nums[i])if(hash.count(target)){int k = hash[target]; dp[i][j]= dp[k][i]+1;} ret =max(ret, dp[i][j]);}// 关键:遍历完 i 之后,才把 i 放入 hash// 这样保证取到的 k 一定在 i 之前 hash[nums[i]]= i;}return ret;}};

九、 等差数列划分 II - 子序列 (Hard)

9.1 题目描述

题目链接446. 等差数列划分 II - 子序列

描述
求等差子序列的个数

9.2 状态定义与累加

  • 状态dp[i][j]i, j 结尾的等差数列个数
  • 转移
    找到 k 后,dp[i][j] += dp[k][i] + 1
    为什么要 +1
    dp[k][i] 是以 k, i 为结尾的,加上 j 构成了 ... k, i, j,这些都是旧的序列延长。
    +1 是指 k, i, j 这三个数新构成的一个长度为 3 的序列。
  • 注意:因为是求个数,可能有多个相同的 k,所以哈希表要存下标列表 List<Integer>

9.3 代码实现

classSolution{public:intnumberOfArithmeticSlices(vector<int>& nums){int n = nums.size();longlong ans =0;// dp[i][j] 以 i, j 结尾的等差数列个数 vector<vector<int>>dp(n,vector<int>(n,0));// 优化:值 -> 下标列表 unordered_map<longlong, vector<int>> map;for(int i =0; i < n; i++) map[nums[i]].push_back(i);for(int j =1; j < n; j++){for(int i =0; i < j; i++){longlong target =2LL* nums[i]- nums[j];if(map.count(target)){for(int k : map[target]){if(k < i){// 累加个数 dp[i][j]+= dp[k][i]+1;}else{break;// 因为 map 里下标是递增的}}} ans += dp[i][j];}}return(int)ans;}};

十、 总结

💬 总结:子序列问题的核心在于不连续
  1. LIS 模型dp[i] 依赖 dp[0...i-1],双层循环。
  2. 定值确定性:如果一个数能确定(如定差),用哈希表优化到 O(N)。
  3. 两数确定性:如果需要两个数确定规律(斐波那契、等差),升维到二维 DP dp[i][j]

恭喜你!到这里,你已经掌握了 线性 DP 的绝大部分套路。下一篇,我们将进入 回文串问题 —— 这是一个关于对称美学的专题,也是区间 DP 的入门课。准备好了吗? 🚀

Read more

计算机毕业设计:Python电商数据可视化分析与销量预测系统 Flask Selenium 机器学习 商品数据采集分析可视化系统 hadoop Agent 算法优化(建议收藏)✅

计算机毕业设计:Python电商数据可视化分析与销量预测系统 Flask Selenium 机器学习 商品数据采集分析可视化系统 hadoop Agent 算法优化(建议收藏)✅

1、项目介绍 技术栈 Python语言、Flask框架、Selenium爬虫、多元线性回归机器学习模型、LayUI框架、Bootstrap、Echarts图表库、MySQL数据库、Pandas数据处理库 功能模块 · 商品数据可视化大屏 · 商品数据后台管理 · 定时爬虫数据采集 · 机器学习销量预测 · 用户注册登录 · 用户管理(管理员) · 后台管理导航 项目介绍 本系统是一套为应对电商领域数据更新缓慢及销量预测难题而研发的一体化数据分析与可视化平台。后端采用Flask框架,通过Selenium技术实现对淘宝商品数据的自动化定时采集,并利用Pandas工具对原始数据进行清洗与规范化处理后存入MySQL数据库。前端融合LayUI与Bootstrap框架,借助Echarts图表库构建可视化大屏,直观展示商品销量与价格分布等核心指标的变化趋势。系统内置基于多元线性回归的机器学习模型,利用历史销量及特征数据进行训练,能够对未来销量进行科学预测。该平台集可视化展示、数据管理、爬虫调度、销量预测与用户权限控制于一体,可为电商企业提供实时的数据洞察与智能决策支持。 2、项目界面

By Ne0inhk
猫头虎AI开源项目推荐:国产开源AI工具爱派(AiPy)|支持本地部署、Python Use自动化操作本地文件的AI办公神器

猫头虎AI开源项目推荐:国产开源AI工具爱派(AiPy)|支持本地部署、Python Use自动化操作本地文件的AI办公神器

猫头虎推荐:国产开源AI工具 爱派(AiPy)|支持本地部署、自动化操作本地文件的AI办公神器 随着AI大模型的迅猛发展,诸如Manus、OpenManus等产品的出现,一款安装即免费使用的AI办公助手成为当下的刚需,各行业正经历着前所未有的数字化转型。尤其对于数据工程师、数据分析师以及日常办公用户而言,如何更高效、更便捷地利用AI工具处理繁琐重复的任务,已成为迫切需要解决的问题。 本文将全面介绍一款领先的国产开源AI工具——爱派(AiPy),它不仅能帮助用户实现数据自动化处理,还能一键赋能AI控制本地文件处理,极大提升工作效率。 背景 作为数据工程师或分析师,你是否经常面对以下困扰? * 频繁处理各种格式的数据文件,包括 CSV、Excel、JSON、HTML、SQLite、Parquet 等; * 数据清洗、转换、聚合、排序、过滤、分析及可视化等工作反复重复,费时费力。 传统的数据处理流程通常十分繁琐: 1. 打开Python环境,输入如import pandas as pd等大量基础代码; 2. 在处理过程中产生许多临时文件;

By Ne0inhk
Python驱动Ksycopg2连接和使用Kingbase:国产数据库实战指南

Python驱动Ksycopg2连接和使用Kingbase:国产数据库实战指南

引言 在国产数据库蓬勃发展的今天,KingbaseES作为国产数据库的佼佼者,凭借其高兼容性、高性能和高安全性,在金融、政府、能源等关键领域得到了广泛应用。本文将介绍如何通过Python的ksycopg2驱动连接并操作Kingbase数据库,从基础连接到高级操作全面掌握这一技术栈。 KingbaseES 数据库【系列篇章】: No.文章地址(点击进入)1电科金仓KingbaseES数据库解析:国产数据库的崛起与技术创新2KingBase数据库迁移利器:KDTS工具深度解析与实战指南3KingBase数据库迁移利器:KDTS工具 MySQL数据迁移到KingbaseES实战4电科金仓KingbaseES V9数据库:国产数据库的自主创新与行业实践深度解析5KingbaseES客户端工具Ksql使用全指南:从安装到高级操作6Spring JDBC与KingbaseES深度集成:构建高性能国产数据库应用实战7深度解析:基于 ODBC连接 KingbaseES 数据库的完整操作与实践 一、ksycopg2驱动:连接Kingbase的桥梁 1.1 驱动架构深度剖析 ksyc

By Ne0inhk

VS Code 中的 Python 代码格式化插件

在 VS Code 中,有几款非常出色的 Python 代码格式化插件可以帮助你保持代码的整洁与规范。下面这个表格整理了目前主流的几款工具,你可以根据它们的特点进行选择。 工具名称核心特点风格理念推荐适用场景Black开箱即用,几乎无需配置;强制统一的代码风格,可预测性强。“无妥协”的格式化器。它决定格式,讨论空间小,保证所有代码风格一致。团队协作项目;希望零配置快速上手的开发者;追求极简和一致性。autopep8基于 PEP 8 规范,主要修复代码风格问题(如缩进、空格)。相对保守,专注于修复而非重新排版。希望代码严格遵循 PEP 8;对现有代码进行温和的格式化修复。yapf高度可定制,可以模仿多种代码风格;格式化策略更“激进”,会重新排版代码。“自成风格”。目标是通过调整代码来达到最佳可读性,而非严格遵循某一规范。需要高度自定义格式化规则;项目有特殊的代码风格要求。 🔧 如何安装与配置 选好工具后,只需简单几步就能在 VS Code 中启用它们。

By Ne0inhk