当前位置: 首页 > news >正文

动态规划解决系列子序列问题

目录

最长递增子序列(LeetCode 300)

 摆动序列(LeetCode 376)

 最长递增子序列的个数(LeetCode 673)

最长数对链(LeetCode 646)

最长定差子序列(LeetCode 1218)

最长的斐波那契子序列的长度(LeetCode 873)

最长等差数列(LeetCode 1027)

等差数列划分 II - 子序列(LeetCode 446)

总结 


在算法领域,子序列相关问题一直是热门且具有挑战性的部分。这类问题往往需要我们在不改变元素相对顺序的前提下,找出满足特定条件的子序列。动态规划作为一种强大的算法思想,非常适合解决这类具有最优子结构和重叠子问题的子序列问题。接下来,我们就来探讨几道经典的子序列问题,看看如何用动态规划来解决它们。

最长递增子序列(LeetCode 300)

问题分析:给定一个整数数组,要找出其中最长严格递增子序列的长度。子序列是删除数组中部分元素(也可不删)后,剩余元素顺序不变的序列。
 
动态规划思路:
 
- 定义  dp[i]  表示以第  i  个元素结尾的最长严格递增子序列的长度。
- 初始时,每个元素自身就是一个长度为  1  的子序列,所以  dp  数组初始化为  1 。
- 对于每个元素  nums[i] ,遍历它之前的所有元素  nums[j] ( j < i ),如果  nums[j] < nums[i] ,说明  nums[i]  可以接在以  nums[j]  结尾的递增子序列后面,此时  dp[i] = max(dp[i[j dp[j] + 1) 。
- 同时维护一个变量  ret ,记录遍历过程中  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[j] < nums[i]) {dp[i] = max(dp[i[j dp[j] + 1);}}ret = max(ret, dp[i]);}return ret;}
};

 摆动序列(LeetCode 376)

问题分析:如果连续数字之间的差严格在正数和负数之间交替,这样的数字序列就是摆动序列。需要找出最长摆动子序列的长度,子序列通过删除原始序列部分元素(也可不删)得到,剩余元素顺序不变。
 
动态规划思路:
 
- 定义  f[i]  表示以第  i  个元素结尾,且最后一个差为正数的最长摆动子序列长度; g[i]  表示以第  i  个元素结尾,且最后一个差为负数的最长摆动子序列长度。
- 初始时, f  和  g  数组都初始化为  1 ,因为单个元素自身就是长度为  1  的摆动序列(符合仅有一个元素的情况)。
- 遍历每个元素  nums[i] ,再遍历它之前的元素  nums[j] ( j < i ):
- 如果  nums[j] < nums[i] ,说明可以形成最后一个差为正数的摆动序列,此时  f[i] = max(g[j] + 1, f[i]) 。
- 如果  nums[j] > nums[i] ,说明可以形成最后一个差为负数的摆动序列,此时  g[i] = max(f[j] + 1, g[i]) 。
- 维护变量  ret ,记录  f  和  g  数组中的最大值,即为最长摆动子序列的长度。
 
代码:

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

 最长递增子序列的个数(LeetCode 673)


问题分析:给定未排序的整数数组,返回最长递增子序列的个数,要求子序列严格递增。
 
动态规划思路:
 
- 定义  len[i]  表示以第  i  个元素结尾的最长递增子序列的长度; count[i]  表示以第  i  个元素结尾的最长递增子序列的个数。
- 初始时, len  和  count  数组都初始化为  1 ,因为每个元素自身是长度为  1  的递增子序列,个数为  1 。
- 遍历每个元素  nums[i] ,再遍历它之前的元素  nums[j] ( j < i ):
- 如果  nums[j] < nums[i] :
- 若  len[j] + 1 > len[i] ,说明找到更长的递增子序列,此时  len[i] = len[j] + 1 ,并且  count[i] = count[j] (继承以  nums[j]  结尾的最长递增子序列的个数)。
- 若  len[j] + 1 == len[i] ,说明找到长度相同的递增子序列,此时  count[i] += count[j] (累加个数)。
- 遍历过程中,维护  retlen (最长递增子序列的长度)和  retcount (对应的个数)。若当前  len[i]  大于  retlen ,则更新  retlen  为  len[i] , retcount  为  count[i] ;若  len[i] == retlen ,则  retcount += count[i] 。
 
代码:

class Solution {
public:int findNumberOfLIS(vector<int>& nums) {int n = nums.size();vector<int> count(n, 1), len(n, 1);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]) {len[i] = len[j] + 1;count[i] = count[j];} else if (len[j] + 1 == len[i]) {count[i] += count[j];}}}if (retlen == len[i]) {retcount += count[i];}if (retlen < len[i]) {retlen = len[i];retcount = count[i];}}return retcount;}
};

最长数对链(LeetCode 646)


问题分析:给定数对数组,数对  [a,b]  满足  a < b ,定义数对  p2 = [c,d]  可跟在  p1 = [a,b]  后当且仅当  b < c ,要找出最长数对链的长度。
 
动态规划思路:
 
- 先对数对数组按照数对的第一个元素进行排序,这样方便后续判断数对之间的跟随关系。
- 定义  dp[i]  表示以第  i  个数对结尾的最长数对链的长度,初始化为  1 ,因为每个数对自身可作为长度为  1  的数对链。
- 遍历每个数对  pairs[i] ,再遍历它之前的数对  pairs[j] ( j < i ),如果  pairs[j][1] < pairs[i][0] ,说明  pairs[i]  可以跟在  pairs[j]  后面,此时  dp[i] = max(dp[i[j dp[j] + 1) 。
- 维护变量  ret ,记录  dp  数组中的最大值,即为最长数对链的长度。
 
代码:
 

class Solution {
public:int findLongestChain(vector<vector<int>>& pairs) {int n = pairs.size();vector<int> dp(n, 1);sort(pairs.begin(), pairs.end(), [](const vector<int>& a, const vector<int>& b) {return a[0] < b[0];});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[j dp[j] + 1);}}ret = max(ret, dp[i]);}return ret;}
};

最长定差子序列(LeetCode 1218)


问题分析:给定整数数组和整数  difference ,找出最长等差子序列的长度,子序列中相邻元素差等于  difference 。
 
动态规划思路:
 
- 使用哈希表  hash  来记录每个数字对应的最长定差子序列长度。
- 初始时, hash[arr[0]] = 1 ,因为第一个元素自身是长度为  1  的子序列。
- 遍历数组从第二个元素开始,对于当前元素  arr[i] ,计算  arr[i] - difference ,如果该值在哈希表中存在,说明  arr[i]  可以接在以  arr[i] - difference  结尾的定差子序列后面,此时  hash[arr[i]] = hash[arr[i] - difference] + 1 ;否则, hash[arr[i]] = 1 (自身作为长度为  1  的子序列)。
- 维护变量  ret ,记录哈希表中的最大值,即为最长定差子序列的长度。
 
代码:
 

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

最长的斐波那契子序列的长度(LeetCode 873)


问题分析:斐波那契式序列满足  n >= 3  且对于所有  i + 2 <= n ,有  x_i + x_{i+1} = x_{i+2} 。给定严格递增的正整数数组,找出最长斐波那契式子序列的长度,不存在则返回  0 。
 
动态规划思路:
 
- 首先用哈希表  hash  记录每个数字在数组中的索引,方便快速查找。
- 定义二维数组  dp ,其中  dp[i][j]  表示以第  i  个和第  j  个元素结尾的斐波那契子序列的长度。初始时, dp  数组每个元素初始化为  2 ,因为两个元素自身还不能构成斐波那契序列(需要至少三个元素)。
- 遍历数组,对于每个  j (从  2  开始),再遍历每个  i (从  1  开始,且  i < j ),计算  a = arr[j] - arr[i] 。如果  a  在哈希表中且其索引小于  i ,说明存在  a  使得  a, arr[i], arr],]  构成斐波那契序列的前三个元素,此时  dp[i][j] = dp[hash[a]][i] + 1 。
- 维护变量  ret ,记录  dp  数组中的最大值。最后如果  ret < 3 ,说明不存在符合要求的斐波那契子序列,返回  0 ,否则返回  ret 。
 
代码:

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

最长等差数列(LeetCode 1027)


问题分析:返回数组中最长等差子序列的长度,等差子序列要求子序列中相邻元素差相同。
 
动态规划思路:
 
- 用哈希表  hash  记录每个数字在数组中的索引,方便查找。
- 定义二维数组  dp ,其中  dp[i][j]  表示以第  i  个和第  j  个元素结尾的等差子序列的长度。初始时, dp  数组每个元素初始化为  2 ,因为两个元素可作为长度为  2  的等差子序列(差为两数之差)。
- 遍历数组,对于每个  i (从  1  开始),再遍历每个  j (从  i+1  开始),计算  a = 2 * nums[i] - nums[j] 。如果  a  在哈希表中,说明存在  a  使得  a, nums[i[j nums[j]  构成等差序列的前三个元素,此时  dp[i][j] = dp[hash[a]][i] + 1 。
- 维护变量  ret ,记录  dp  数组中的最大值,即为最长等差子序列的长度。
 
代码:

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

等差数列划分 II - 子序列(LeetCode 446)


问题分析:返回数组中所有等差子序列的数目,等差子序列要求至少有三个元素且相邻元素差相同。
 
动态规划思路:
 
- 用哈希表  hash  记录每个数字对应的索引列表,方便查找相同数字的位置。
- 定义二维数组  dp ,其中  dp[i][j]  表示以第  i  个和第  j  个元素结尾的等差子序列的数目(长度至少为  3 )。
- 遍历数组,对于每个  j (从  2  开始),再遍历每个  i (从  1  开始,且  i < j ),计算  a = (long long)2 * nums[i] - nums[j] 。如果  a  在哈希表中,遍历  a  对应的所有索引  e (且  e < i ),此时  dp[i][j] += dp[e][i] + 1 ( dp[e][i]  是之前以  e  和  i  结尾的等差子序列数目, +1  是因为  e, i, j  构成新的等差子序列)。
- 维护变量  sum ,累加所有  dp[i][j]  的值,即为所有等差子序列的数目。
 
代码:

class Solution {
public:int numberOfArithmeticSlices(vector<int>& nums) {unordered_map<long long, vector<int>> hash;int n = nums.size();for (int i = 0; i < n; i++) {hash[nums[i]].push_back(i);}vector<vector<int>> dp(n, vector<int>(n, 0));int sum = 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 (auto e : hash[a]) {if (e < i) {dp[i][j] += dp[e][i] + 1;}}}sum += dp[i][j];}}return sum;}
};

总结 

这些子序列问题都可以通过动态规划来解决,核心是找到合适的状态定义以及状态转移方程。不同的问题根据其特定的条件,状态定义和转移方式有所不同,但整体思路都是利用动态规划来记录子问题的解,从而推导出原问题的解。通过这些问题的练习,可以更好地掌握动态规划在子序列问题中的应用。


文章转载自:

http://DwCyg6zY.tnjkg.cn
http://yg8y3xF2.tnjkg.cn
http://YLw0z75n.tnjkg.cn
http://FSFdUHoQ.tnjkg.cn
http://vFTuHPx0.tnjkg.cn
http://9jZ2NStQ.tnjkg.cn
http://lrNDyYP0.tnjkg.cn
http://a5Zb4Vud.tnjkg.cn
http://tkca3pEl.tnjkg.cn
http://YkPNHLhP.tnjkg.cn
http://iyPeGqqv.tnjkg.cn
http://1PNe2d6i.tnjkg.cn
http://yBZleq0r.tnjkg.cn
http://qG45aQeE.tnjkg.cn
http://YZg6XbGK.tnjkg.cn
http://5Uc0YeQh.tnjkg.cn
http://qw3opVwP.tnjkg.cn
http://Nl72D6K4.tnjkg.cn
http://BMrWuzKk.tnjkg.cn
http://Ygjn9lUt.tnjkg.cn
http://yG3dZkcY.tnjkg.cn
http://H9oLuRbZ.tnjkg.cn
http://qL01bXAy.tnjkg.cn
http://Bqi6EV5g.tnjkg.cn
http://rgJ4pGWu.tnjkg.cn
http://Toun5stU.tnjkg.cn
http://HP5tVtZ5.tnjkg.cn
http://VOIL6ZB8.tnjkg.cn
http://1vaz2ZCj.tnjkg.cn
http://2VmhZxar.tnjkg.cn
http://www.dtcms.com/a/386413.html

相关文章:

  • SCADE One vs Scade 6 - 标量积建模比较
  • Next.js 身份验证与授权:使用 NextAuth.js 保护你的应用
  • Spring MVC 的案例小练习
  • 贪心算法与动态规划
  • 香港期权市场的主要参与者有哪些?
  • 系统中间件与云虚拟化-serverless-基于阿里云函数计算的简单邮件发送服务设计与体验
  • 【LLM】GPT-OSS架构变化详解
  • 【开题答辩全过程】以 “寄情绿苑”绿色殡葬服务小程序的设计和实现为例,包含答辩的问题和答案
  • 容器化部署之dockerfile07
  • 一篇读懂Pormise!!【前端ES6】
  • spring-kafka的消息过滤器RecordFilterStrategy
  • gin中sse流式服务
  • 论文笔记(九十一)GWM: Towards Scalable Gaussian World Models for Robotic Manipulation
  • Simulink(MATLAB)与 LabVIEW应用对比
  • [BX]和loop指令,debug和masm汇编编译器对指令的不同处理,循环,大小寄存器的包含关系,操作数据长度与寄存器的关系,段前缀
  • Django RBAC权限实战全流程
  • 智启燃气新未来丨众智鸿图精彩亮相2025燃气运营与安全研讨会
  • Docker Push 常见报错及解决方案汇总
  • OCR 后结构化处理最佳实践
  • 软考 系统架构设计师系列知识点之杂项集萃(148)
  • P1425 小鱼的游泳时间
  • 弧焊机器人氩气焊接节能方法
  • 机器人导论 第六章 动力学(2)——拉格朗日动力学推导与详述
  • 在uniapp中调用虚拟机调试vue项目
  • UE5 GAS 技能系统解析:EGameplayAbilityTriggerSource 枚举详解
  • MySQL 基础概念与简单使用
  • PostgreSQL高可用架构实战:构建企业级数据连续性保障体系
  • (二)昇腾AI处理器计算资源层基础
  • C++17新特性:用[*this]告别悬垂指针,提升并发健壮性
  • Buck电路输出电容设计:从理论到实践的完整指南