动态规划总结

目录

斐波那契数列

509. 斐波那契数  

70. 爬楼梯(数量和)

746. 使用最小花费爬楼梯(最小值)

343. 整数拆分(最大值)

96. 不同的二叉搜索树(数量和)

62. 不同路径(数量和)

63. 不同路径 II(数量和)

01背包

416. 分割等和子集(最大值)

1049. 最后一块石头的重量 II(最大值)

494. 目标和(数量和)

474. 一和零(最大值)

完全背包

518. 零钱兑换 II(组合数量和)

377. 组合总和 Ⅳ(排列数量和)

322. 零钱兑换(最小值)

279. 完全平方数(最小值)

139. 单词拆分(排列布尔)

打家劫舍

198. 打家劫舍(最大值)

213. 打家劫舍 II

337. 打家劫舍 III

买卖股票

121. 买卖股票的最佳时机

122. 买卖股票的最佳时机 II

123. 买卖股票的最佳时机 III     

188. 买卖股票的最佳时机 IV

309. 买卖股票的最佳时机含冷冻期

714. 买卖股票的最佳时机含手续费

子序列问题

300. 最长递增子序列

674. 最长连续递增序列

718. 最长重复子数组

1143. 最长公共子序列

1035. 不相交的线

53. 最大子数组和

392. 判断子序列

115. 不同的子序列

583. 两个字符串的删除操作

72. 编辑距离

回文子串

647. 回文子串

516. 最长回文子序列


斐波那契数列

509. 斐波那契数  

dp定义:dp[i]  代表F(i)

递推公式:dp[i] = dp[i - 1] + dp[i - 2]

初始化:dp[0] = 0;   dp[1] = 1;

70. 爬楼梯(数量和)

dp[i] 表示走到第 i 个楼梯的方法数目

第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和

dp[i] = dp[i - 1] + dp[i - 2]

不考虑dp[0], 初始化dp[1] 和 dp[2]

746. 使用最小花费爬楼梯(最小值)

dp[i] 代表到达第i个台阶最低花费

到达i层,要么从i-1层走一步,要么从i-2层走两步

即,dp[i] = dp[i-1] + cost[i-1] 或 dp[i] = dp[i-2] + cost[i-2]

dp[i] = min( dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2] )

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯,所以到达第0个台阶或第一个台阶不花费费用:dp[0] = 0;     dp[1] = 0;

343. 整数拆分(最大值)

dp[i] 代表将i拆分为多个正整数的乘积

假设0<j<i, 那么i可以拆为j 和 i-j两个数,那么dp[i] 可以为  j *(i-j) 也可以为   dp[j] * (i-j)

j的值  要从1到 i 一个一个的试,看哪个 j 对应的 dp[i] 最大

dp[i] = max(  j*(i-j),  dp[j]*(i-j)  )

for(int i = 3; i <= n; i++){
    for(int j = 1; j < i; j++){
        //找最大的dp[i]
        dp[i] = Math.max(dp[i],Math.max(j*(i-j), dp[j]*(i-j)));
    }
}

96. 不同的二叉搜索树(数量和)

dp[i]  代表i个结点组成的二叉树的个数,   f(k)代表在  dp[i]  中以k为头结点的二叉树的个数

dp[i] = f(1) + f(2) + ... + f(i)

每个f(k)中都有i个结点,左子树的结点是[1, k-1]共k - 1个结点,右子树的结点是[k+1, i]共有i - k个结点

所以左子树的结点可以构成 dp[k-1] 个平衡二叉树,右子树的结点可以构成 dp[i-k] 个平衡二叉树

根据排列组合,f(k) = dp[k - 1] * dp[i - k]  (1 <= k <= i)

综上所述,dp[n] = \sum f(i)  (1 <=  i  <= n)

class Solution {
    public int numTrees(int n) {
        /**
        * G(n)代表n个结点组成的二叉树的个数
        * f(i)代表在G(n)以i为头结点的二叉树的个数
        * 则G(n) = f(1)+f(2)+...+f(n)
        * 在f(i)中,头结点左边有i-1个结点,右边有n-i个结点
        * 则f(i) = G(i-1)*G(n-i)
        * 最终:G(n) = G(0)G(n-1)+G(1)G(n-2)+...+G(n-1)G(0)
        */
        int[] dp = new int[n+1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= n; i++){
            //G(i-1)*G(n-i) 相当于 dp[j-1]*dp[i-j]
            for(int j = 1; j <= i; j++){
                //把i=1,j=1代入,需要初始化dp[0]和dp[1]
                dp[i] = dp[i] + dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
}

62. 不同路径(数量和)

dp[i][j]   代表到达坐标(i,j)不同的路径数量

到达左边位置的路径数量  +  到达上边位置的路径数量

dp[i][j] = dp[i-1][j] + dp[i][j-1]

初始化:dp[i][j]由上边和左边推导而来,初始化第一行和第一列

63. 不同路径 II(数量和)

dp[i][j]   代表到达坐标(i,j)不同的路径数量

石头在(i, j)的左边,只能从上边到达,dp[i][j] = dp[i-1][j]

石头在(i, j)的上边,只能从左边到达,dp[i][j] = dp[i][j-1]

如果我们把石头位置的  dp[i][j]  设置为 0

那么就不用管石头在哪了,直接dp[i][j] = dp[i-1][j] + dp[i][j-1]即碰到石头就跳过不进行计算

初始化还是第一行第一列

01背包

416. 分割等和子集(最大值)

背包容量就是所有元素的和的一半,判断能不能把背包装满

每个数只能选一次,01背包

dp[i] 背包容量为i时最多能装多少重量的物品

一维dp数组下,

        选择不装入,dp[j] = dp[j]

        选择装入,dp[j] = 背包剩余容量最多装入  +  当前物品重量 = dp[j-nums[i]] + nums[i]

即,dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i])

一维dp数组下,先遍历物品再遍历容量,容量倒序遍历

初始化dp[0] = 0,  背包容量为0时最多能装重量为0

最后  dp[总和二分之一] == 总和二分之一,可以分为两个相等的集合

1049. 最后一块石头的重量 II(最大值)

求两堆石头的最小差值,一堆装入背包,一堆不装入背包

背包容量为石头总重量的二分之一,物品是石头

每个石头只能选一次,01背包

dp[i] 背包容量为i时最多能装多少重量的石头

一维dp数组下,

        选择不装入,dp[j] = dp[j]

        选择装入,dp[j] = 背包剩余容量最多装入  +  当前石头重量 = dp[j-stones[i]] + stones[i]

即,dp[j] = Math.max(dp[j], dp[j-stones[i]] + stones[i])

一维dp数组下,先遍历石头再遍历容量,容量倒序遍历

初始化dp[0] = 0,  背包容量为0时最多能装重量为0

一堆石头放入背包重量为   dp[sum/2],  另一堆石头没进背包重量为 sum-dp[sum/2]

两堆石头的差值 就是 结果

494. 目标和(数量和)

+号堆的和为x,-号堆的和为sum-x

 x - (sum - x) = target              x = (sum + target)/2

问题转化为:从nums中选一些数,使得这些数的和为(sum + target)/2

每个数只能选一次,01背包

dp[i] 背包容量为i时有几种选择nums的方法

一维dp数组下,

        选择 不选,dp[j] = dp[j]

        选择 选,dp[j] = 背包容量为 j-nums[i] 时有几种选择nums的方法 = dp[j-nums[i]]

即,dp[j] = dp[j]  +  dp[j-nums[i]]

一维dp数组下,先遍历物品再遍历容量,容量倒序遍历

初始化dp[0] = 1,  背包容量为0时有1种选择nums的方法就是都不选

474. 一和零(最大值)

选哪几个字符串组成一个字符数组,满足最多 有 m 个 0 和 n 个 1,且数组长度最长

dp[i][j]  字符数组中最多有i个0,j个1的最大长度

对于一个字符串,有x个0, y个1

        若x > i 或 y > j , 无法加入当前字符数组,  因为字符数组中最多允许有i个0,j个1

        若i >= x 且 j >= y, 可以加入

                若不加入字符数组,dp[i][j] = dp[i][j]

                若加入字符数组,dp[i][j] = 剩余容量最长 + 1  =  dp[i - x][j - y] + 1

                        (为什么+1?  加入一个字符串长度肯定要+1)

                        (dp[i - x][j - y]?  加进来后,0的位数占了x,1的位数占了y,剩余容量就是i-x和j-y)

                最终取值:看加入的值大 还是 不加入的大,即max一下

                dp[i][j] = Math.max(dp[i][j], dp[i - x][j - y] + 1);

遍历顺序:遍历字符数组在外层,m和n就是容量在内层倒序遍历

初始化:字符数组中最多有0个0,0个1的最大长度为0,  dp[0][0] = 0;

完全背包

518. 零钱兑换 II(组合数量和)

dp[j] 组成金额j的方案数量(方案数量 公式是相加)

每一种面额的硬币有无限个,完全背包

不选,dp[j] = dp[j]

选,dp[j] = dp[j - coins[i]]

dp[j] = dp[j] + dp[j - coins[i]];

遍历顺序:完全背包都是正序遍历,物品在外背包在内求组合,反之求排列

初始化:组成金额0的方案数量为1,dp[0] = 1

377. 组合总和 Ⅳ(排列数量和)

dp[i]  代表总和为 i 的元素排列的个数

数组元素可以重复选,完全背包

不选,dp[j] = dp[j]

选,dp[j] = dp[j - nums[i]]

dp[j] = dp[j] + dp[j - nums[i]];

遍历顺序:完全背包都是正序遍历,物品在外背包在内求组合,反之求排列

初始化:总和为 0的元素排列的个数为1,dp[0] = 1

322. 零钱兑换(最小值)

dp[i]   代表凑成金额  i  所需的最少硬币数量

不选数量不变:  dp[j] = dp[j]

选数量+1:        dp[j] = 金额 j-coins[i] 所需最少数量  +  1 =      dp[j - coins[i]] + 1

遍历顺序:完全背包都是正序遍历,物品在外背包在内求组合,反之求排列

初始化:凑成0 需要0个硬币,dp[0] = 1

279. 完全平方数(最小值)

物品是完全平方数,  n是背包的容量

完全平方数可以重复使用,完全背包

dp[i]  和为 i 的完全平方数的最少数量 

不选,数量不变:dp[j] = dp[j]

选,数量+1:        dp[j] = dp[ j - i*i ] + 1

 dp[j] = Math.min(dp[j], dp[j - i*i] + 1)

遍历顺序:完全背包都是正序遍历,物品在外背包在内求组合,反之求排列

初始化:和为 0 的完全平方数的最少数量是0 ,dp[0] = 0

139. 单词拆分(排列布尔)

组成字符串是要有顺序的,排列完全背包

dp[i]   代表s[0, i-1]是否可以由字典组成

当前背包容量为j,    0............j-len.........j

s[j-len, j]存在于字典中,且dp[j-len] = true,  那么dp[j] = true

遍历顺序:先遍历背包 再遍历物品

初始化:空串可以由字典组成     dp[0] = true

打家劫舍

198. 打家劫舍(最大值)

dp[i] 表示[0, i-1]家 偷的最大金额

不偷i-1家,那么最大金额就是偷 [0,i-2] 的,  dp[i] = dp[i-1]

偷i-1家,那么i-2家不能偷,dp[i] = 偷[0,  i-3] 的 + i-1家的 = dp[i-2] + nums[i-1]

偷还是不偷,选收益最大的方案

dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1])

根据递推公式,i 要从2开始,需要初始化dp[1] 和 dp[0]

dp[0] = 0;        偷-1家,等于不偷,0

dp[1] = nums[0];        偷第0家,第0家金钱,nums[0]

213. 打家劫舍 II

对于环形打家劫舍,分为不偷第0家 和 不偷第n家

p1 = 偷[1,  n] 最大金额

p2 = 偷[0,  n-1] 最大金额

取p1 和 p2 的最大值

337. 打家劫舍 III

树形打家劫舍,用后序遍历从底部向上返回最大金额

每层向上返回的是一个数组:{不偷本结点时最大金额,偷本结点时最大金额}

本结点不偷时最大金额:左右子树偷不偷都行: 左子树偷与不偷的最大值 + 右子树偷与不偷的最大值

本结点偷时最大金额:左右子树不能偷:本结点值 + 左子树不偷值 + 右子树不偷值

买卖股票

121. 买卖股票的最佳时机

存在两种状态:持有股票  和  未持有股票  且只能买卖一次

dp[i]  代表手中的现金

dp[1]  持有股票

        1、昨天就持有股票,手中现金不变:dp[1] = dp[1]

        2、今天买入的(只能买卖一次,这次就是第一次买入),0减去股票价格:dp[1] = -prices[i]

dp[0] 未持有股票

        1、昨天就未持有股票,手中现金不变:dp[0] = dp[0]

        2、今天卖出的,昨天手中现金加上今天股票价格:dp[0] = dp[1] + prices[i]

综上,dp[1] = Math.max(dp[1], -prices[i]);

           dp[0] = Math.max(dp[0], prices[i] + dp[1]);

初始化:  第0天未持有股票,手中现金为0,dp[0] = 0

                第0天持有股票,手中现金为 -prices[0]

122. 买卖股票的最佳时机 II

存在两种状态:持有股票  和  未持有股票  且能买卖多次

dp[i]  代表手中的现金

dp[1]  持有股票

        1、昨天就持有股票,手中现金不变:dp[1] = dp[1]

        2、今天买入的(不一定是第一次买入),昨天手中现金减去股票价格:dp[1] = dp[0] - prices[i]

dp[0] 未持有股票

        1、昨天就未持有股票,手中现金不变:dp[0] = dp[0]

        2、今天卖出的,昨天手中现金加上今天股票价格:dp[0] = dp[1] + prices[i]

综上,dp[1] = Math.max(dp[1], dp[0] - prices[i]);

           dp[0] = Math.max(dp[0], prices[i] + dp[1]);

初始化:  第0天未持有股票,手中现金为0,dp[0] = 0

                第0天持有股票,手中现金为 -prices[0]

123. 买卖股票的最佳时机 III     

         

存在4种状态:第一次持有股票  和  第一次未持有股票

                        第二次持有股票  和  第二次未持有股票

且只能买卖两次

dp[i]  代表手中的现金

dp[1]  第一次持有股票

        1、昨天就持有股票,手中现金不变:dp[1] = dp[1]

        2、今天第一次买入的,0减去股票价格:dp[1] =  - prices[i]

dp[2] 第一次未持有股票

        1、昨天就未持有股票,手中现金不变:dp[2] = dp[2]

        2、今天第一次卖出的,第一次持有时手中现金加上今天股票价格:dp[2] = dp[1] + prices[i]

dp[3] 第二次持有股票

        1、昨天就持有股票,手中现金不变:dp[3] = dp[3]

        2、今天第二次买入的,第一次未持时手中现金减去今天股票价格:dp[3] = dp[2] + prices[i]

dp[4] 第二次未持有股票

        1、昨天就未持有股票,手中现金不变:dp[4] = dp[4]

        2、今天第二次卖出的,第二次持有时手中现金加上今天股票价格:dp[4] = dp[3] + prices[i]

综上, dp[1] = Math.max(dp[1],           - prices[i]);

            dp[2] = Math.max(dp[2], dp[1] + prices[i]);

            dp[3] = Math.max(dp[3], dp[2] - prices[i]);

            dp[4] = Math.max(dp[4], dp[3] + prices[i]);

初始化:  第0天第一次持有股票,手中现金为-prices[0],dp[1] = -prices[0]

                第0天第一次未持股票,手中现金为0,dp[2] = 0

                第0天第二次持有股票,手中现金为-prices[0],dp[3] = -prices[0]

                第0天第二次未持股票,手中现金为0,dp[1] = 0

188. 买卖股票的最佳时机 IV

  买卖2次有四种状态,买卖k次,则有2k种状态

dp[i]  代表手中的现金

dp[j]  j 是奇数,持有股票

        1、昨天就持有股票,手中现金不变:dp[j] = dp[j]

        2、今天买入的,昨天未持有时现金 减去 股票价格:dp[j] =  dp[j-1]- prices[i]

dp[j]  j 是偶数,未持有股票                                  

        1、昨天就未持有股票,手中现金不变:dp[j] = dp[j]

        2、今天卖出的,昨天持有时现金 加上 股票价格:dp[j] =  dp[j-1] + prices[i]

综上,

                if(j % 2 == 1){
                    dp[j] = Math.max(dp[j], dp[j-1] - prices[i]);
                }else{
                    dp[j] = Math.max(dp[j], dp[j-1] + prices[i]);
                }

初始化:

        for(int i = 1; i < 2*k + 1; i = i + 2){
            //奇数,持有,都是-prices[0]
            dp[i] = -prices[0];
        }

309. 买卖股票的最佳时机含冷冻期

存在冷冻期,有四种状态:

持有股票、未持有股票且度过冷冻期、未持有股票且今天刚卖出、冷冻期

dp[0] 持有股票

         1、昨天就持有股票:dp[0] = dp[0]

         2、今天买入的且昨天是冷冻期:dp[0] = dp[3] - prices[i]

         3、今天买入的且昨天非冷冻期:dp[0] = dp[1] - prices[i]

dp[1] 未持有股票且度过冷冻期

         1、昨天是冷冻期:dp[1] = dp[3]

         2、昨天非冷冻期:dp[1] = dp[1]

dp[2] 未持有股票且今天刚卖出

         1、dp[2] = dp[0] + prices[i]

dp[3] 冷冻期

         1、昨天卖出的:dp[3] = dp[2]

综上,

dp[0] = Math.max(Math.max(dp[0], dp[3]-prices[i]),dp[1]-prices[i]);

dp[1] = Math.max(dp[1], dp[3]);

dp[2] = dp[0] + prices[i]

dp[3] = dp[2]

我们知道,一维dp数组原理是利用数组的老数据计算新数据,并覆盖老数据

但是这里, 在计算dp[2]之前,dp[0]被更新了;   计算dp[3]之前,dp[2]被更新了

所以使用临时变量记录旧的dp[0]和dp[2]

int temp0 = dp[0];

int temp2 = dp[2];

dp[0] = Math.max(Math.max(dp[0], dp[3]-prices[i]),dp[1]-prices[i]);

dp[1] = Math.max(dp[1], dp[3]);

dp[2] = temp0 + prices[i];

dp[3] = temp2;

初始化: 只有dp[0]持有股票,  dp[0] = -prices[0]

714. 买卖股票的最佳时机含手续费

可以多次买卖股票,但是每次卖出要扣一笔手续费

存在两种状态:持有股票  和  未持有股票

dp[i]  代表手中的现金

dp[1]  持有股票

        1、昨天就持有股票,手中现金不变:dp[1] = dp[1]

        2、今天买入的(不一定是第一次买入),昨天手中现金减去股票价格:dp[1] = dp[0] - prices[i]

dp[0] 未持有股票

        1、昨天就未持有股票,手中现金不变:dp[0] = dp[0]

        2、今天卖出的,昨天手中现金加上今天股票价格减去手续费:dp[0] = dp[1] + prices[i] - fee

综上,dp[1] = Math.max(dp[1], dp[0] - prices[i]);

           dp[0] = Math.max(dp[0], dp[1] + prices[i] - fee);

初始化:  第0天未持有股票,手中现金为0,dp[0] = 0

                第0天持有股票,手中现金为 -prices[0]

子序列问题

300. 最长递增子序列

子序列不是连续的, 子数组是连续的

dp[i]  代表结尾为nums[i]的递增子序列的最大长度

用 j 遍历 [0, i), 若nums[ j ] < nums[ i ]

nums[i]可以跟在dp[j]对应的序列后面,新序列长度为dp[j] + 1

            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i]){
                    //并不是要比较 dp[i] 和 dp[j] + 1
                    //循环中每个j对应一个dp[i], 要找出最大的dp[i]
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }

初始化: 数组总每个元素都可以组成长度为1的子序列, 即全部初始化为1

674. 最长连续递增序列

dp[i] 以nums[i]为结尾的连续递增子序列的最大长度

连续的序列,通常dp[i] 代表以nums[i] 为结尾的数组和字符串

不连续的序列,通常dp[i] 代表以nums[i - 1] 为结尾的数组和字符串

连续的话, 只和前一个元素比较即可,不用遍历整个字符串了

若nums[ i ] < nums[ i-1 ]

nums[i] 可以跟在 dp[ i-1 ] 对应的序列后面,新序列长度为dp[i-1] + 1

        for(int i = 1; i < len; i++){
            if(nums[i] > nums[i-1]){
                dp[i] = dp[i-1] + 1;
            }
            res = Math.max(res, dp[i]);
        }

初始化: 数组总每个元素都可以组成长度为1的子序列, 即全部初始化为1

718. 最长重复子数组

dp[i][j]  代表以nums1[i-1]为结尾 和 以nums2[j-1]为结尾 这两个数组中公共的长度最长的子数组的长度

如果nums1[i-1] == nums2[j-1]

nums1[0, i-2] 可以拼接上nums1[i-1]    和  nums2[0, j-2] 拼接上 nums2[j-1]

形成新的最长重复子数组:  nums1[0, i-1] 和 nums2[0, j-1]

即:   dp[i][j] = dp[i-1][j-1] + 1

因为要连续,所以如果最后一个元素不同,dp[i][j]就要清零

例如:2345 和 2347 的dp[i][j]是0,而不是3

i 和 j 遍历从1开始, 因为要判断nums1[i-1] == nums2[j-1]

从递推公式上看,递推依赖于左上角元素,我们从(1,1)开始遍历,初始化第一行第一列

dp[i][0] = 0,   dp[0][j] = 0

1143. 最长公共子序列

dp[i][j]  代表以text1[i-1]结尾 和 以text2[j-1]结尾 这两个字符串中公共的长度最长的子串的长度

如果text1[i-1] == text2[j-1]   值相同,公共子序列长度至少为 1,再加上前面串的长度

        即:   dp[i][j] = dp[i-1][j-1] + 1

如果text1[i-1] != text2[j-1]

         值不相同,当前公共子序列:

         不选text2[j-1]: text1[0,i)和text2[0,j-1)的最长公共子序列

          不选text1[i-1]: text1[0,i-1)和text2[0,j)的最长公共子序列

           选一个最长的: dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);

i 和 j 遍历从1开始, 因为要判断text1[i-1] == text2[j-1]

从递推公式上看,递推依赖于左上角左边和上边 元素,我们从(1,1)开始遍历,初始化第一行第一列

dp[i][0] = 0,   dp[0][j] = 0

1035. 不相交的线

 转化为寻找两个数组的最长公共子序列

dp[i][j]  nums1[0,i-1]和nums2[0,j-1]的最长公共子序列

53. 最大子数组和

dp[i]  以nums[i]结尾的连续子数组的最大和

连续的序列,通常dp[i] 代表以nums[i] 为结尾的数组和字符串

不连续的序列,通常dp[i] 代表以nums[i - 1] 为结尾的数组和字符串

nums[i]加入,前面累计的和 + 当前数组值,    dp[i] = dp[i-1] + nums[i]

nums[i]不加入,前面累计的和清零 + 当前数组值, dp[i] = 0 + nums[i]

加入还是不加入,取最大值

dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);

初始化:  以第0个元素结尾的连续数组最大和为  nums[0]

392. 判断子序列

dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度

若  s.charAt(i-1) == t.charAt(j-1)

        相同子序列长度至少为1 + 之前串的相同子序列的长度 : dp[i][j] = dp[i-1][j-1] + 1;

若  s.charAt(i-1)  !=  t.charAt(j-1)

        那s[0,i-1] 和 t[0,j-1]相同子序列的长度 = s[0,i-1] 和 t[0,j-2] : dp[i][j] = dp[i][j-1];

从递推公式上看,递推依赖于左上角上边 元素,我们从(1,1)开始遍历,初始化第一行第一列

dp[i][0] = 0,   dp[0][j] = 0

115. 不同的子序列

dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]

s[i-1] == t[j-1]

        用s[0, i-1]来匹配t[0,j-1] : dp[i][j] = dp[i-1][j-1]

                (s[i-1] 和 t[j-1]相等不用匹配了, 相当于再匹配s[0, i-2] 和 t[0,j-2])

        用s[0, i-2]来匹配t[0,j-1] : dp[i][j] = dp[i-1][j]

        综上, dp[i][j] = dp[i-1][j-1] + dp[i-1][j]

s[i-1] != t[j-1]

        用s[0, i-2]匹配t[0, j-1], dp[i][j] = dp[i-1][j]

初始化 : dp[i][j]由上方和左上方 或 左方 推导而来, 所以dp[i][0] 和 dp[0][j] 要初始化,dp[i][0] 在s[0,i-1]中空字符串出现的个数,都是1, dp[0][j] 在空串中t[0, j-1]出现的个数,都是0

583. 两个字符串的删除操作

即求,两个字符串总长度 - 最长公共子序列 * 2

72. 编辑距离

dp[i][j] 表示将下标i-1为结尾的字符串word1 转化为 下标j-1为结尾的字符串word2  的最少操作数

当word1[i-1] == word2[j-1]

        word1[i-1] 和 word2[j-1]相等不用操作

        操作下标i-2为结尾的字符串word1  下标j-2为结尾的字符串word2, 即可

        即,dp[i][j] = dp[i-1][j-1]

当word1[i-1] != word2[j-1]

        删除word1[i-1]

                即将word1[0, i-2]转化为word2[0,j-1], dp[i][j] = dp[i-1][j] + 1(等同添加)

        删除word2[j-1]

                即将word1[0, i-1]转化为word2[0,j-2], dp[i][j] = dp[i][j-1] + 1(等同添加)

        替换word1[i-1]或word2[j-1]

                使得word1[i-1] == word2[j-1], dp[i][j] = dp[i-1][j-1] + 1

        综上, dp[i][j] = Math.min(Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1), dp[i-1][j-1] + 1);

初始化 : dp[i][0] 将word1[0, i-1]  转化为 空串 的最少操作数 为 i

              dp[0][j] 将 空串 转化为 word2[0,j-1]的最少操作数 为 j

回文子串

647. 回文子串

dp[i][j]  s[i,j]是否是回文

若s[i] != s[j]

        s[i,j]不是回文:dp[i][j] = false

若s[i] == s[j]

        1、i - j = 0,单个字符肯定是回文,dp[i][j] = true

        2、j - i = 1,两个字符肯定是回文,dp[i][j] = true

        3、j - i > 1,要看s[i+1,j-1]是否是回文,dp[i][j] = dp[i+1][j-1]

        综上,  j-i <= 1  或者   s[i+1,j-1]是回文---> dp[i][j] = true;

遍历顺序: 存在dp[i][j] 通过 dp[i+1][j-1]判断,即左下角元素,所以遍历顺序从下到上,从左到右

初始化 : 单个字符肯定是回文   dp[i][i] = true;

516. 最长回文子序列

dp[i][j] 表示s[i,j]内的最长回文子序列的长度

当s[i] == s[j]时

        s[i]和s[j]能和s[i+1,j-1]内的最长回文子序列组成一个新的最长回文子序列

        即,dp[i][j] = dp[i+1][j-1] + 2

当s[i] != s[j]时

         一起加入不能组成新的回文子序列,分别加入看看

        1、把s[i]加入,即s[i,j-1], dp[i][j] = dp[i][j-1]

        2、把s[j]加入,即s[i+1,j], dp[i][j] = dp[i+1][j]

        综上,取最大值 dp[i][j] = max(dp[i][j-1], dp[i+1][j]);

遍历顺序 : 根据状态转移公式,dp[i][j]由左边或下边 以及 左下角推导而来,所以遍历:从下到上、从左到右

初始化 :  单个字符肯定是回文子序列,且长度为1 , dp[i][i] = 1;

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

益生李佳菌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值