刷题学习—算法思想(动态规划下)

24.买卖股票的最佳时机系列

24.1买卖股票的最佳时机

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
只能一笔交易

class Solution {
    public int maxProfit(int prices[]) {
        if(prices.length < 2)return 0;
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < prices.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0] , -prices[i]);
            dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);
        }
        return dp[prices.length - 1][1];
    }
}

思路:

  1. 确定dp数组中的含义:定义一个长度为2的dp数组,dp数组的两个位置分别表示两种状态,dp[i][0] 表示第i天持有股票所得最多现金 ,一开始现金是0,那么加入第i天买入股票现金就是 -prices[i]dp[i][1] 表示第 i 天不持有股票所得最多现金
  2. 确定递推公式:
    dp[i][0](持有股票):可以由两个状态推出来:①第i-1天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]②第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]dp[i][0]应该选所得现金最大的
    dp[i][1](不持有股票):可以由两个状态推出来:①第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]②第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]。同样dp[i][1]取最大的
  3. dp数组初始化:由递推公式可以看出,都是要从dp[0][0]dp[0][1]推导出来。
    dp[0][0]表示第0天持有股票:此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];
    dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;
  4. 确定遍历顺序:从递推公式可以看出dp[i]都是有dp[i - 1]推导出来的,那么一定是从前向后遍历。
24.2买卖股票的最佳时机Ⅱ

任意多少笔交易都行

class Solution {
    public int maxProfit(int[] prices) {
        if(prices.length < 2)return 0;
        int[][] dp = new int[prices.length][2];
        dp[0][0] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < prices.length; i++) {
            dp[i][0] = Math.max(dp[i - 1][0] , dp[i - 1][1] - prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1] , dp[i - 1][0] + prices[i]);
        }
        return dp[prices.length - 1][1];
    }
}

思路:
这个区别主要是体现在递推公式上,其他都和上题一样唯一不同的地方,就是推导dp[i][0]的时候,第i天买入股票的情况。因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]一定就是 -prices[i]
而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。

  • 那么第i天持有股票即dp[i][0],如果是第 i 天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
  • 那么第i天不持有股票即dp[i][1],第 i 天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
dp[i][0] = Math.max(dp[i - 1][0] , dp[i - 1][1] - prices[i]);//不持有的前一天或者前一天持有加上今天买入
dp[i][1] = Math.max(dp[i - 1][1] , dp[i - 1][0] + prices[i]);//持有的前一天或者前一天不持有加上今天卖出
24.3买卖股票的最佳时机Ⅲ

最多完成两笔交易

class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[4]; 
        // 存储两次交易的状态就行了
        // dp[0]代表第一次交易的买入
        dp[0] = -prices[0];
        // dp[1]代表第一次交易的卖出
        dp[1] = 0;
        // dp[2]代表第二次交易的买入
        dp[2] = -prices[0];
        // dp[3]代表第二次交易的卖出
        dp[3] = 0;
        for(int i = 1; i <= prices.length; i++){
            // 要么保持不变,要么没有就买,有了就卖
            dp[0] = Math.max(dp[0], -prices[i-1]);
            dp[1] = Math.max(dp[1], dp[0]+prices[i-1]);
            // 这已经是第二次交易了,所以得加上前一次交易卖出去的收获
            dp[2] = Math.max(dp[2], dp[1]-prices[i-1]);
            dp[3] = Math.max(dp[3], dp[2]+ prices[i-1]);
        }
        return dp[3];
    }
}

思路:定义四种状态,分别是①第一次买入②第一次卖出③第二次买入④第二次卖出

  1. 确定dp数组中的含义:一天一共就有四个状态,①第一次买入②第一次卖出③第二次买入④第二次卖出
    dp[i]中 i 表示当前天数交易的状态
  2. 确定递推公式:要么保持不变,要么没有就买,有了就卖;第二次交易时,得加上前一次交易卖出去的收获
  3. dp数组初始化:dp[0]代表第一次交易的买入:dp[0] = -prices[0];
    dp[1]代表第一次交易的卖出 dp[1] = 0;
    dp[2]代表第二次交易的买入 dp[2] = -prices[0];
    dp[3]代表第二次交易的卖出 dp[3] = 0;
  4. 确定遍历顺序:从前到后的
24.4最佳买卖股票时机含冷冻期

两笔交易,但存在一天的冷冻期

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length < 2) {
            return 0;
        }
        int[][] dp = new int[prices.length][2];

        // bad case
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        dp[1][0] = Math.max(dp[0][0], dp[0][1] + prices[1]);
        dp[1][1] = Math.max(dp[0][1], -prices[1]);

        for (int i = 2; i < prices.length; i++) {
            // dp公式
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]);
        }

        return dp[prices.length - 1][0];
    }
}

思路:根据状态来解决,分为以下四种情况:
①买入股票状态
②两天前就卖出了股票,度过了冷冻期
③今天卖出股票,但是度过了冷冻期
④今天为冷冻期状态

  1. 确定dp数组中的含义:dp[i][j],第 i 天状态为 j,所剩的最多现金为dp[i][j]
  2. 确定递推公式:
    状态一dp[i][0],有两个具体操作:
    前一天就是持有股票状态(状态一)dp[i][0] = dp[i - 1][0]
    今天买入了,有两种情况:
    ①前一天是冷冻期(状态四)dp[i - 1][3] - prices[i]
    ②前一天是保持卖出股票状态(状态二)dp[i - 1][1] - prices[i]取三种情况最大值
    状态二dp[i][1],有两个具体操作:
    前一天就是状态二 或者 前一天是冷冻期(状态四)dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
    状态三dp[i][2] 昨天一定是买入股票状态(状态一),今天卖出dp[i][2] = dp[i - 1][0] + prices[i];
    状态四dp[i][3]昨天卖出了股票(状态三)p[i][3] = dp[i - 1][2];
  3. dp数组初始化:
    状态一dp[0][0] = -prices[0],买入股票所省现金为负数
    状态二:第0天没有卖出dp[0][1]初始化为0就行
    状态三,状态四同样dp[0][2]; dp[0][3]初始化为0,因为最少收益就是0,绝不会是负数。
  4. 确定遍历顺序:从递归公式上可以看出,dp[i] 依赖于 dp[i-1],所以是从前向后遍历。
    最后结果取 状态二,状态三,和状态四的最大值,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。
24.5买卖股票的最佳时机含手续费

可以无数笔交易,每笔交易支付手续费

class Solution {
    public int maxProfit(int[] prices, int fee) {
        int len = prices.length;
        // 0 : 持股(买入)
        // 1 : 不持股(售出)
        // dp 定义第i天持股/不持股 所得最多现金
        int[][] dp = new int[len][2];
        dp[0][0] = -prices[0];
        for (int i = 1; i < len; i++) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
            dp[i][1] = Math.max(dp[i - 1][0] + prices[i] - fee, dp[i - 1][1]);
        }
        return Math.max(dp[len - 1][0], dp[len - 1][1]);
    }
}

思路:在卖出时支付手续费

  1. 确定dp数组中的含义:dp数组的含义:dp[i][0] 表示第i天持有股票所省最多现金。 dp[i][1] 表示第i天不持有股票所得最多现金
  2. 确定递推公式:
    dp[i][0]第 i-1 天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
    第 i 天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
    所以:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
    dp[i][1]第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
    第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金,注意这里需要有手续费了即:dp[i - 1][0] + prices[i] - fee
  3. dp数组初始化:dp[0][0] = -prices[0];
  4. 确定遍历顺序:从前往后

股票问题总结

股票问题大总结

25.最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp,1);
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if(nums[i] > nums[j])dp[i] = Math.max(dp[i],dp[j] + 1);
            }
        }
        int res = 0;
        for (int i = 0; i < dp.length; i++) {
            res = Math.max(res,dp[i]);
        }
        return res;
    }
}

思路:

  1. 确定dp数组中的含义:dp[i]表示 i 之前包括 i 的最长上升子序列的长度。
  2. 确定递推公式:位置 i 的最长升序子序列等于 j 从0到 i-1各个位置的最长升序子序列 + 1 的最大值if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
  3. dp数组初始化:每一个 i,对应的dp[i](即最长上升子序列)起始大小至少都是是1
  4. 确定遍历顺序:j 是根据 i 来的,i 要从头开始,j在i的内层
  5. 打印推导dp数组:
    在这里插入图片描述

26.最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
贪心不解释😏

class Solution {
    public int findLengthOfLCIS(int[] nums) {
        int res = 1;
        int count = 1;
        for (int i = 1; i < nums.length; i++) {
            if(nums[i] > nums[i - 1]){
                count++;
                res = Math.max(res,count);
            }else {
                count = 1;
            }
        }
        return res;
    }
}

在这里插入图片描述

动态规划😏:

class Solution {
    public int findLengthOfLCIS(int[] nums) {
        int[] dp = new int[nums.length];
        Arrays.fill(dp,1);
        int res = 1;
        for (int i = 1; i < dp.length; i++) {
            if(nums[i] > nums[i - 1]){
                dp[i] = dp[i - 1] + 1;
            }
            res = res > dp[i] ? res : dp[i];
        }
        return res;
    }
}

思路:

  1. 确定dp数组中的含义:dp[i] 代表当前下表最大连续值
  2. 确定递推公式:如果当前值比前一值大的条件下,当前索引(状态)处的最大连续值,比前一状态的最大连续值大1。即dp[i] = dp[i - 1] + 1;
  3. dp数组初始化:数组每一处的最大连续值都是1
  4. 确定遍历顺序:从前往后推
  5. 打印推导dp数组:
    在这里插入图片描述在这里插入图片描述

27. 最长重复子数组

哎牛批😏给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

class Solution {
    public int findLength(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length][nums2.length];
        for (int[] row : dp) {
            Arrays.fill(row,0);
        }
        int res = 0;
        for (int i = 0; i < nums2.length; i++) {
            if(nums1[0] == nums2[i]){
                dp[0][i] = 1;
                res = 1;
            }
        }
        for (int i = 0; i < nums1.length; i++) {
            if(nums2[0] == nums1[i]){
                dp[i][0] = 1;
                res = 1;
            }
        }


        for (int i = 1; i < nums1.length; i++) {
            for (int j = 1; j < nums2.length; j++) {
                if(nums1[i] == nums2[j])dp[i][j] = dp[i - 1][j - 1] + 1;
                res = res > dp[i][j] ? res : dp[i][j];
            }
        }
        return res;
    }
}

思路:

  1. 确定dp数组中的含义:dp[i][j] i 指的是nums1中的索引,j 指的是nums2中的索引,指当前索引下最长的子数组
  2. 确定递推公式:if(nums1[i] == nums2[j])dp[i][j] = dp[i - 1][j - 1] + 1;如果两数组当前索引指的是同一数,那么在其左上角的状态上加一
    在这里插入图片描述
  3. dp数组初始化:需要初始化第一列和第一行,如果有当前两索引指的数是相同的,就置为1,前提可以全部置为0
  4. 确定遍历顺序:从前到后即可
  5. 打印推导dp数组:
    在这里插入图片描述

28.最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int[][] dp = new int[text1.length() + 1][text2.length() + 1];

        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[0].length ; j++) {
                if(text1.charAt(i - 1) == text2.charAt(j - 1)){
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }else {
                    dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
                }
            }
        }
        return dp[text1.length()][text2.length()];
    }
}

思路:

  1. 确定dp数组中的含义:dp[i][j] i 指的是字符串1的字符索引,j 指的是字符串2的字符索引,那么整体就是表示到当前俩索引下的最长子序列
  2. 确定递推公式:如果当前两索引指的字符相等那就在其左上角的数据+1,即dp[i][j] = dp[i - 1][j - 1] + 1;;如果不相等当前状态就取上面或者左面的最大值即可,即dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
    在这里插入图片描述
  3. dp数组初始化:第一行第一列无需初始化,因为取不上字符
  4. 确定遍历顺序:从左上角推到右下角,自然是从前到后,起始索引都从1开始
  5. 打印推导dp数组:
    在这里插入图片描述

29.不相交的线

给两个数组,把相同数连接起来,连接的线不能相交

class Solution {
    public int maxUncrossedLines(int[] nums1, int[] nums2) {
        int[][] dp = new int[nums1.length + 1][nums2.length + 1];
        for (int i = 1; i <= nums1.length; i++) {
            for (int j = 1; j <= nums2.length; j++) {
                if(nums1[i - 1] == nums2[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }else {
                    dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
                }
            }
        }
        return dp[nums1.length][nums2.length];
    }
}

思路:其实就是求两个字符串的最长公共子序列的长度!

  1. 确定dp数组中的含义:dp[i][j]当前两索引下相同的数的最大个数
  2. 确定递推公式:相等的话取左上角的数 + 1,dp[i][j] = dp[i - 1][j - 1] + 1;不相等的话取上面或者左面最大的数dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
  3. dp数组初始化:不用管初始化
  4. 确定遍历顺序:从前到后
  5. 打印推导dp数组:以int[] cost = {2,5,1,2,5}; int[] cost1 = {10,5,2,1,5,2};为例在这里插入图片描述

30.最大子序和

class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int res = nums[0];
        for (int i = 1; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1] + nums[i],nums[i]);
            res = Math.max(res,dp[i]);
        }
        return res;
    }
}

思路:

  1. 确定dp数组中的含义:dp[i]:包括下标i之前的最大连续子序列和
  2. 确定递推公式:dp[i]只有两个方向可以推出来:
    dp[i - 1] + nums[i],即:nums[i]加入当前连续子序列和
    nums[i],即:从头开始计算当前连续子序列和
    dp[i] = Math.max(dp[i - 1] + nums[i],nums[i]);
  3. dp数组初始化:如果只有一个数和就是这个数,所以第一个就取 nums[0];
  4. 确定遍历顺序:从前向后推出来的
  5. 打印推导dp数组:以int[] cost1 = {-2,1,-3,4,-1,2,1,-5,4};为例
    在这里插入图片描述

31.判断子序列

给两个字符串,看短的字符串在长的字符串里有没有有序的都出现过

class Solution {
    public boolean isSubsequence(String s, String t) {
        int[][] dp = new int[s.length() + 1][t.length() + 1];
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 1; j <= t.length(); j++) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }else {
                    dp[i][j] = dp[i][j - 1];
                }
            }
        }
        
        if(dp[s.length()][t.length()] == s.length()){
            return true;
        }
        return false;
    }
}

思路:同28,最后判断结果是不是和短字符的长度相等即可

32. 不同的子序列

给一长一短字符串,判断长字符串含有多少次短字符串

class Solution {
    public int numDistinct(String s, String t) {
        int[][] dp = new int[s.length() + 1][t.length() + 1];
        for (int i = 0; i < s.length() + 1; i++) {
            dp[i][0] = 1;
        }
        
        for (int i = 1; i < s.length() + 1; i++) {
            for (int j = 1; j < t.length() + 1; j++) {
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
                }else{
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        
        return dp[s.length()][t.length()];
    }
}

思路:

  1. 确定dp数组中的含义:dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数
  2. 确定递推公式:①用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。②不用s[i - 1]来匹配,个数为dp[i - 1][j]
    s[i - 1]t[j - 1]相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
    s[i - 1]t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]
  3. dp数组初始化:dp[i][0] 表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
    那么dp[i][0]一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
    dp[0][j]:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
    那么dp[0][j]一定都是0,s如论如何也变成不了t。
    dp[0][0]应该是1,空字符串s,可以删除0个元素,变成空字符串t。
  4. 确定遍历顺序:从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j]都是根据左上方和正上方推出来的。所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

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

给两个字符串,问总共要删多少次才能使两个字符串相等

class Solution {
    public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        for (int i = 0; i <= word1.length(); i++)dp[i][0] = i;
        for (int i = 0; i <= word2.length(); i++)dp[0][i] = i;
        for (int i = 1; i <= word1.length(); i++) {
            for (int j = 1; j <= word2.length(); j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                }else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1] + 2,
                            Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}

思路:

  1. 确定dp数组中的含义:dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。就是说i索引遍历w1,j索引遍历w2,在当前索引下删除的次数
  2. 确定递推公式:
  • word1[i - 1]word2[j - 1]相同的时候,说明不需要删除,所以和上一状态一样,dp[i][j] = dp[i - 1][j - 1];
  • word1[i - 1]word2[j - 1]不相同的时候,可能需要w1删除,可能需要w2删除,可能都需要删除,那么取三种删除方式最小的那个,即dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
  1. dp数组初始化:dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。一个字符串本来是空的,要跟他一样,那就得把自己删完
  2. 确定遍历顺序:dp[i][j]都是根据左上方、正上方、正左方推出来的,所以遍历的时候一定是从上到下,从左到右

34.编辑距离

给两个字符,通过插入一个字符、删除一个字符、替换一个字符操作,使得两字符相等,需要操作的次数

class Solution {
    public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        for (int i = 0; i <= word1.length(); i++)dp[i][0] = i;
        for (int i = 0; i <= word2.length(); i++)dp[0][i] = i;
        for (int i = 1; i <= word1.length(); i++) {
            for (int j = 1; j <= word2.length(); j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    dp[i][j] = dp[i - 1][j - 1];
                }else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1] + 1,
                            Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}

思路:本题看着难,其实和上一题差不多,在递归公式处有所不同,如果当前两字符相等,就不需要操作,参照前一状态;如果不相等,可以进行三种操作:插删换,插入一个字符和删除的操作数一样,所以可以看作是dp[i - 1][j] + 1或者dp[i][j - 1] + 1而替换是需要改动i - 1、j - 1索引两个字符所以操作数得+2,且都是之前状态的,故递推公式是dp[i][j] = Math.min(dp[i - 1][j - 1],Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;

35.最长回文子串

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

class Solution {
    public int longestPalindromeSubseq(String s) {
        int len = s.length();
        int[][] dp = new int[len + 1][len + 1];
        for (int i = len - 1; i >= 0; i--) { // 从后往前遍历 保证情况不漏
            dp[i][i] = 1; // 初始化
            for (int j = i + 1; j < len; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i][j], dp[i][j - 1]));
                }
            }
        }
        return dp[0][len - 1];
    }
}

思路:

  1. 确定dp数组中的含义:dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度
  2. 确定递推公式:加入s[j]的回文子序列长度为dp[i + 1][j],加入s[i]的回文子序列长度为dp[i][j - 1],那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
    如果s[i]s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
  3. dp数组初始化:递推公式是计算不到 i 和j相同时候的情况。所以i == j时,一个字符的回文子序列长度就是1。
  4. 确定遍历顺序:从矩阵的角度来说,dp[i][j] 下一行的数据。 所以遍历i的时候一定要从下到上遍历,这样才能保证,下一行的数据是经过计算的。

动态规划大总结

  • 动态规划更多的就是使用状态转移容器来记录状态的变化
  • 所以求组合类问题的公式(求装满背包有几种方法,填满多大容积的包)一般公式都是:dp[j] += dp[j - nums[i]]。且组合问题的初始化一般都为dp[0] = 1;
  • 01背包问题中,物品的价值一般是数据的个数,物品的重量一般是数据的大小
  • 如果求组合数就是外层for循环遍历物品,内层for遍历背包,然后内层的起始索引就是每个物品的质量,去塞背包
    如果求排列数就是外层for遍历背包,内层for循环遍历物品。注意排列问题,在写递推公式时,注意不要指针越界
  • 求一维dp[i // j],取决于背包所在的循环索引是 i 还是 j
  • 定义dp数组时,dp一般表示的是当前数据的状态,所以为了不用考虑边界情况,dp维度的定义要比原始数据 + 1
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值