【算法】动态规划(dp问题),持续更新

这篇具有很好参考价值的文章主要介绍了【算法】动态规划(dp问题),持续更新。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

0. 动态规划

介绍本篇之前,我想先用人话叙述一般解决动态规划问题的思路:

动态规划的问题,本身有许多产生结果的可能,需要在具体题目下得到满足某个条件的解。

如何得到呢?

我们就需要根据这个具体问题,建立一个状态表(dp 表),在这张 dp 表中的每一个位置的数据都有明确的、相同的定义,称为 状态表示,而储存的每个数据,就是由原问题产生的每一个子问题的解。

子问题的解又该怎么得到呢?

这就需要通过对子问题如何推导(状态的转移过程)进行具体分析,要求是每个解都能由前一个解得到,从而得到所有的子问题的解。根据得到的解把 dp 表填满,最后就能在这张 dp 表中找到我们要的具体答案。

梳理一下:

🎯五个思考步骤 和 注意事项

  • 状态表示:自定义一下 dp 表里的每个数据(子问题的解)有什么统一的含义,就是 dp[i] 是什么
    • dp 表可能是一维数组、二维数组…需要具体分析进行选择
    • 这里的含义需要我们根据题目和经验得出,这里就给大家一个分享一个经验,你可以试着把 i 位置定义成 “以 i 位置为结尾 / 为开头的 xxxx”
  • 状态转移方程:一个通用公式,可以用 最近一步 已知状态推出下一个状态,最终达到填满 dp 表的所有状态(即得到所有子问题的解)
  • 初始化:保证填表时不越界(根据状态转移方程看哪里有越界的可能,再决定怎么初始化)
  • 填表顺序:为了填表时,所需要的状态是已知的
  • 返回值:题目要求 + 状态表示

🎯技巧

  1. 如果 dp[i] 有例如 “选 / 不选” 两种或多种状态,那么建立两个或多个dp表,也可以把几个表写成二维数组
  2. 巧妙利用数组元素和数组下标(尤其是对于 “无序 + 重复” 数据可用)
    • eg:下标对应原数据,元素对应出现个数 / 总合…(见题 1.5)
  3. 画出每种状态的转移方程(状态机),对照图去写 dp 转移方程会更加清楚,不容易出错。
  4. 初始化:
    • 多加一位数 / 一行 / 一列
      • 此种情况需要注意对原数组访问的时候下标还需要多减一位

      • 如果原数组是字符串,可以对原数组加一个虚拟位置(见题1.4),如下

        str = '-' + str;
        
    • 为越界的位置增加一个 if() 判断,会稍微改变一点 dp 转移方程的结构
    • 对于用最大或最小值初始化时会有一个计算后溢出的问题,强烈建议使用这个数据 ±0x3f3f3f3f(int 最大值的一半)
  5. 对于 “环形xx” 想办法转成普通线性关系会简单很多

优化思路

  1. 滚动数组,只留下所需状态求下一个状态,滚动进行。需要注意的是,滚动复制的顺序不能被覆盖。
    ps:此种优化对于算法本身不是很重要,可以作为了解。
  2. 涉及到类似字典搜索,考虑插入后用 hash 表搜索。(见题 1.4)

1. 子数组系列

分析状态时的通用方法:

  1. 分成 自身、自身+之前一个元素 进行具体考虑
  2. 如果题目和数字有关,分成 >0、<0 进行考虑

1.1 乘积为正数的最长子数组长度

🔗详解点击

给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。
一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。
请你返回乘积为正数的最长子数组长度。

1.2 等差数列划分

🔗详解点击

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
例如,[1,3,5,7,9]、[7,7,7,7] 和 [3,-1,-5,-9] 都是等差数列。 
给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的子数组个数。 
子数组是数组中的一个连续序列。

1.3 最长湍流子数组

🔗详解点击

给定一个整数数组 arr ,返回 arr 的最大湍流子数组的长度 。
如果比较符号在子数组中的每个相邻元素对之间翻转,则该子数组是湍流子数组 。
ps: 就是元素两两之间的增长情况是 ↗↘↗↘↗.... 这样

1.4 单词拆分

🔗详解点击

给你一个字符串 s 和一个字符串列表 wordDict 作为字典。
请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。

1.5 环绕字符串中的子字符串

🔗详解点击

定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串,
所以 base 看起来是这样的:
"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....".
给你一个字符串 s ,请你统计并返回 s 中有多少 不同非空子串 也在 base 中出现。

2. 子序列系列

需要和子数组区别,子序列两个要求是:不改变原序列顺序,不要求连续取数
分析状态时的通用方法:

  1. 分成 自身、自身+之前一个元素 进行具体考虑;
  2. 由于取数不连续,除了分析 dp[] 表中 i 位置,还需要一个 j 位置,确定子序列结尾的前一个元素。时间复杂度是 n2
  3. dp[i][j] 这样的二维数组,可以表示子序列倒数两个数的位置 (i, j),用到了二维数组的上三角(i 为行,j 为列,不包括对角线)

2.1 最长递增子序列

🔗详解点击

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而 不改变其余元素的顺序。
例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

2.2 摆动序列

🔗详解点击

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。
第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

2.3 最长递增子序列的个数

🔗详解点击

给定一个未排序的整数数组 nums,返回最长递增子序列的个数 。
注意 这个数列必须是 严格 递增的。

2.4 最长数对链

🔗题目连接

tips:sort() 排序

ps:经典课表安排问题,前面看过了就知道这道很简单,类似的题解有很多,欢迎点击链接直接刷题


2.5 最长定差子序列

🔗详解点击

给你一个整数数组 arr 和一个整数 difference,请你找出并返回 arr 中最长等差子序列的长度,该子序列中相邻元素之间的差等于 difference 。
子序列 是指在不改变其余元素顺序的情况下,通过删除一些元素或不删除任何元素而从 arr 派生出来的序列。

ps:题目本身不难,突出一个优化手段


2.6 最长的斐波那契子序列的长度

🔗详解点击

如果序列 X_1, X_2, ..., X_n 满足下列条件,就说它是 斐波那契式 的:
n >= 3
对于所有 i + 2 <= n,都有 X_i + X_{i+1} = X_{i+2}
给定一个严格递增的正整数数组形成序列 arr ,找到 arr 中最长的斐波那契式的子序列的长度。
如果一个不存在,返回  0 。

2.7 最长等差数列(hard)

🔗详解点击

给你一个整数数组 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 是等差的。

2.8 等差序列划分Ⅱ(hard)

🔗详解点击

这一题已经跟随上一题完整的分析过一遍了,放一个 leetcode 题解和代码的链接,不懂的看 2.6+2.7。

给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。
如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差

3. 回文串系列

📕子串问题:
子串和子数组基本一致,要求连续,但是即使回文串内容相同,但是如果在原数组中的首尾位置不同,也算作不同的回文串。

分析回文串问题,通常可以看到三种方法,对于 3.1 题目来分析一下这三种方法:

  • 中心扩展算法(后面更新):时间复杂度(n2),空间复杂度(1)
  • 马拉车算法():时间、空间复杂度都是(n),但是只局限使用在回文串题目中,感兴趣的可以自己了解一下
  • 动态规划算法:时间、空间复杂度都是(n2),我们这里选择动态规划的需求很明确,一是了解思想,二是可以将后续一些hard级别的回文串题目直接拉到easy等级(笑)。利用的就是一张dp表,能够将所有的字串是否是回文的信息,保存在dp表里面。

给一个比较通用的状态表示方式:

  • dp[i][j] 表示,以 i 为开头 j 为结尾的子串,是否为回文串。

📕子序列问题:
回文串 + 子序列 问题的变式,这里再次提醒:注意状态表示,和 2.x 中子序列又是有区别的。2.x 中子序列多是能由末尾两个推导出来所有子序列所以定义 dp[i][j] 为以 i、j 结尾的子序列,回文串子序列并没有固定的推导规则,这里的状态表示如下:

  • dp[i][j] 表示,以 i 为开头 j 为结尾的子序列,xxxxx。

👇回文串系列集合度高,适合一起看,一个系列的题都在这一个链接里
🔗详解点击


3.1 回文子串

给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

3.2 最长回文子串

解法与上题类似

给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

3.3 分割回文串IV(hard)

标记是 hard,但是用动规预处理一下数据,生成dp表,在分析就变得很简单了。

给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false 。

3.4 分割回文串II(hard)

首先,预处理数据,方便判断子串是否是回文串;
接着,剩下的部分与 1.4 单词拆分题 分析方法一样。

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。
返回符合要求的 最少分割次数 。

3.5 最长回文子序列

注意状态表示

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

3.6 让字符串成为回文串的最少插入次数(hard)

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。
请你返回让 s 成为回文串的 最少操作次数 。
「回文串」是正读和反读都相同的字符串。

4. 双数组系列

通常来说,有几个点需要注意:

  • 选取第一个字符串的 [0, i] 区间和第二个字符串的 [0, j] 区间作为研究对象,根据题目要求确定状态表示。(注意不是以 i 或 j 为结尾)
  • 空串在有时候是有研究意义的,同时,空串还能方便我们初始化

ps:为了让主页好看一点,博主后续会把一个系列的题解放一块啦~
👉🔗双数组系列题解链接


4.1 最长公共子序列

🔗题目链接

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

4.2 不相交的线

🔗题目链接

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。
现在,可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足满足:  
nums1[i] == nums2[j] 且绘制的直线不与任何其他连线(非水平线)相交。
请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。
以这种方法绘制线条,并返回可以绘制的最大连线数。

4.3 不同的子序列(hard)

🔗题目链接

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 10^9^ + 7 取模。

4.4 通配符匹配(hard)

🔗题目链接

给你一个输入字符串 (s) 和一个字符模式 (p) ,请你实现一个支持 '?' 和 '*' 匹配规则的通配符匹配:
'?' 可以匹配任何单个字符。
'*' 可以匹配任意字符序列(包括空字符序列)。
判定匹配成功的充要条件是:字符模式必须能够 完全匹配 输入字符串(而不是部分匹配)。

4.5 正则表达式匹配(hard)

🔗题目链接文章来源地址https://www.toymoban.com/news/detail-766145.html

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配
'.' 匹配任意单个字符 
'*' 匹配零个或多个前面的那一个元素 
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。
其中,保证每次出现字符 * 时,前面都匹配到有效的字符

到了这里,关于【算法】动态规划(dp问题),持续更新的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 算法第十五期——动态规划(DP)之各种背包问题

    目录 0、背包问题分类 1、 0/1背包简化版 【代码】 2、0/ 1背包的方案数 【思路】

    2023年04月09日
    浏览(41)
  • DP算法:动态规划算法

    (1)确定初始状态 (2)确定转移矩阵,得到每个阶段的状态,由上一阶段推到出来 (3)确定边界条件。 蓝桥杯——印章(python实现) 使用dp记录状态,dp[i][j]表示买i张印章,凑齐j种印章的概率 i表示买的印章数,j表示凑齐的印章种数 情况一:如果ij,不可能凑齐印章,概

    2024年02月07日
    浏览(51)
  • 第十三周代码(动态规划1)(持续更新)

    动态规划第十二周也写了一部分,只不过忘记放上去了。 新的一年啦,看到这里砸个祝福:新年快乐,身体健康,考试顺利,心想事成! 【解题思路】 其实新出生半分钟吃一个是干扰项,实际上是一分钟吃一个 【参考代码】 【输出结果】  分为线性DP,状态压缩DP,数位

    2024年02月01日
    浏览(51)
  • 算法——动态规划(DP)——递推

    动态规划常用于解决优化问题。 动态规划通常以自底向上或自顶向下的方式进行求解。 自底向上的动态规划从最简单的子问题开始,逐步解决更复杂的问题,直到达到原始问题。 自顶向下的动态规划则从原始问题出发,分解成子问题,并逐步求解这些子问题。 动态规划算法

    2024年01月20日
    浏览(57)
  • ★动态规划(DP算法)详解

    什么是动态规划:动态规划_百度百科 内容太多了不作介绍,重点部分是无后效性,重叠子问题,最优子结构。 问S-P1和S-P2有多少种路径数,毫无疑问可以先从S开始深搜两次,S-P1和S-P2找出所有路径数,但是当这个图足够大,那就会超时。 动态规划旨在用 空间换时间 ,我们

    2024年02月04日
    浏览(50)
  • 动态规划(DP)(算法笔记)

    本文内容基于《算法笔记》和官方配套练题网站“晴问算法”,是我作为小白的学习记录,如有错误还请体谅,可以留下您的宝贵意见,不胜感激。 动态规划(Dynamic Programming,DP)是一种用来解决一类最优化问题的算法思想。简单来说,动态规划将一个复杂的问题分解成若干个子

    2024年02月05日
    浏览(47)
  • 动态规划例题(代码随想录学习)——持续更新

    dp[i][j]的含义是:从(0,0)到(i,j)的不同路径 当路线中有了障碍,此路不通,所以在不同路径的递推公式上需要增加条件 if(obs[i,j]==0)没有障碍,dp[i][j]= dp[i-1][j]+dp[i][j-1] if(obs[i][j]==1)有障碍,不进行推导 obs数组表示障碍 障碍的后面应该是0(原因:遇到障碍后,即

    2024年04月12日
    浏览(44)
  • C++动态规划-线性dp算法

    莫愁千里路 自有到来风 CSDN 请求进入专栏                                    X 是否进入《 C++ 专栏》? 确定 目录  线性dp简介 斐波那契数列模型  第N个泰波那契数 思路: 代码测试:  三步问题 思路: 代码测试: 最小花费爬楼梯 思路: 代码测试:  路径问题 数字三

    2024年02月19日
    浏览(48)
  • c++ 算法之动态规划—— dp 详解

    dp 是 c++ 之中一个简单而重要的算法,每一个 OIer 必备的基础算法,你知道它究竟是什么吗? 目录 一、简介         1.为什么不能贪心?         2.揭秘 dp   二、应用         1.定义         2.边界值         3.动态转移方程         4.结果         5.代码

    2024年04月09日
    浏览(48)
  • 算法套路十三——动态规划DP入门

    动态规划和递归都是通过将大问题分解为较小的子问题来解决问题。它们都可以用来解决具有重叠子问题和最优子结构特性的问题。 递归是一种自顶向下的方法, 它从原始问题开始 ,递归地将问题分解为较小的子问题dfs(i)—— dfs(i)代表的是从第i个状态开始进行递归求解能

    2024年02月15日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包