动态规划

确定dp数组以及下标的含义

确定递归公式

dp数组如何初始化

确定遍历顺序

举例推导dp数组

斐波那契数(与爬楼梯思路一样)

public int fib(int n) {
        //dp是第i下标的斐波那契数
        int[] dp=new int[n+1];
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        dp[0]=0;
        
        dp[1]=1;
        for (int i = 2; i < dp.length; i++) {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }

 

使用最小花费爬楼梯

public int minCostClimbingStairs(int[] cost) {
        int[] dp=new int[cost.length+1];
        dp[0]=0;
        dp[1]=0;

        for(int i=2;i<dp.length;i++){
            dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[dp.length-1];
    }

 

不同路径

使用二维dp数组,初始化第一行为1,第一列也为1.

public int uniquePaths(int m, int n) {
        int[][] dp=new int[m][n];
        for(int i=0;i<m;i++){
            dp[i][0]=1;
        }
        for(int j=0;j<n;j++){
            dp[0][j]=1;
        }

        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }

 

路径设置障碍物

初始化中,如果第一行中有障碍物,那么该行中障碍物后面的位置都不可到达

      同时第一列中如果有障碍物,那么该列障碍物后面的位置都不可达。

public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int[][] dp=new int[obstacleGrid.length][obstacleGrid[0].length];
       
        for(int i=0;i<dp.length;i++){
            if(obstacleGrid[i][0]==1){
                while(i<dp.length){
                    dp[i][0]=0;
                    i++;
                }
            }else{
                dp[i][0]=1;
            }
        }

        for(int j=0;j<dp[0].length;j++){
            if(obstacleGrid[0][j]==1){
                 while(j<dp[0].length){
                    dp[0][j]=0;
                    j++;
                }

            }else{
                dp[0][j]=1;
            }
        }

        for(int i=1;i<dp.length;i++){
            for(int j=1;j<dp[0].length;j++){
                if(obstacleGrid[i][j]==1){
                    dp[i][j]=0;
                }else{
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }
            }
        }
        return dp[dp.length-1][dp[0].length-1];
    }

 

整数拆分

dp公式总共考虑到三个情况,分别是拆分成两个整数的最大值,拆分成三个及以上的整数最大值,还有和它自己的当前值进行比较。

public int integerBreak(int n) {
        int[] dp=new int[n+1];
        dp[0]=0;
        dp[1]=0;
        dp[2]=1;
        for(int i=3;i<=n;i++){
            for(int j=2;j<i;j++){
                int temp=Math.max(dp[i-j]*j,(i-j)*j);
                dp[i]=Math.max(temp,dp[i]);
            }
        }
        return dp[n];
    }

 

不同的二叉搜索树

空树也是一种二叉搜索树,所以初始化dp[0]=1

不同的二叉搜索树个数为左子树个数对应的搜索树数量*右子树对应的搜索树数量,将不同的情况相加

因为这个不看具体的值,只是看子搜索树的形状。

public int numTrees(int n) {
        int[] dp=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            for(int j=0;j<i;j++){
                dp[i]=dp[i]+dp[j]*dp[i-j-1];
            }
        }
        return dp[n];
    }

 

0-1背包问题

重点:dp[i][j]表示从第0~i个物品中任选,装入背包重量为j时,最大的价值总和

dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]),其中j-weight[i]需要判断是否<0

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner scanner=new Scanner(System.in);
        int M=scanner.nextInt();
        int N=scanner.nextInt();
        int[] weight=new int[M];
        for (int i = 0; i < M; i++) {
            weight[i]=scanner.nextInt();
        }

        int[] value=new int[M];
        for (int i = 0; i < M; i++) {
            value[i]=scanner.nextInt();
        }

        int[][] dp=new int[M][N+1];
        //第一行初始化
        for (int j = 0; j < N + 1; j++) {
            if (weight[0]<=j)
            dp[0][j]=value[0];
        }

        for (int i = 1; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                if (j-weight[i]>=0){
                    dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]);
                }else {
                    dp[i][j]=dp[i-1][j];
                }

            }

        }

        System.out.println(dp[M-1][N]);
    }
}

 

0-1背包使用滚动数组

重点:dp[j]的含义:是背包重量为j时的最大价值总和

注意里层循环的截止条件,不是>0,而是>=weight[i],而且是从后往前遍历,否则会重复添加(先循环物品,再循环重量)

// 创建一个动态规划数组 dp,初始值为 0
        int[] dp = new int[N + 1];

        // 外层循环遍历每个类型的研究材料
        for (int i = 0; i < M; i++) {
            // 内层循环从 N 空间逐渐减少到当前研究材料所占空间
            for (int j = N; j >= costs[i]; j--) {
                // 考虑当前研究材料选择和不选择的情况,选择最大值
                dp[j] = Math.max(dp[j], dp[j - costs[i]] + values[i]);
            }
        }

        // 输出 dp[N],即在给定 N 行李空间可以携带的研究材料的最大价值
        System.out.println(dp[N]);

        scanner.close();

 

目标和 (装满背包有几种方法?)

一维数组是在上一行结果的基础上进行下一行的计算,因此遍历数组必须在外面

dp[j]:表示当背包重量是j时,装满的方式有几种

dp[j]=dp[j]+dp[j-nums[i]],不装最后一个物品和装最后一个物品的装满背包总和

public int findTargetSumWays(int[] nums, int target) {
        int sum =Arrays.stream(nums).sum();//
        int bagSize=(sum+target)/2;//得到target时的最大背包重量
        if (sum< Math.abs(target) || (sum+target)%2==1){
           return 0;
        }

        int[] dp=new int[bagSize+1];
        dp[0]=1;
        for (int i = 0; i < nums.length; i++) {
            for (int j = bagSize; j >=nums[i] ; j--) {
                dp[j]=dp[j]+dp[j-nums[i]];
            }
        }
        return dp[bagSize];
    }


1和0  当背包容量为指定值时最多能装几个物品?

int dp[][]=new int[m+1][n+1];
        for (int i = 0; i < strs.length; i++) {
            int zeroC=0,oneC=0;
            for (int j = 0; j < strs[i].length(); j++) {
                if (strs[i].charAt(j)=='0'){
                    zeroC++;
                }else {
                    oneC++;
                }
            }
            //遍历维度是必须
            for (int j = m; j >=zeroC; j--) {
                for (int k=n;  k>=oneC; k--) {
                    dp[j][k]=Math.max(dp[j][k],dp[j-zeroC][k-oneC]+1);
                }
            }

        }
        return dp[m][n]; 

 

 

完全背包理论(物品可以无限次拿取)

和0-1背包的不同的是dp的递推公式,因为是无限次所以dp[i][j-weight[i],0-1背包中是dp[i-1][j-weight[i]]

import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner scanner=new Scanner(System.in);
        int n=scanner.nextInt();//材料种类
        int v=scanner.nextInt();//行李承担的总重量

        int[] weight=new int[n];
        int[] value=new int[n];
        for(int i=0;i<n;i++){
            weight[i]=scanner.nextInt();
            value[i]=scanner.nextInt();
        }

        int[][] dp=new int[n][v+1];
        for(int j=weight[0];j<=v;j++){
            dp[0][j]=dp[0][j-weight[0]]+value[0];
        }

        for(int i=1;i<n;i++){
            for(int j=0;j<=v;j++){
                if(j<weight[i]){
                    dp[i][j]=dp[i-1][j];
                }else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-weight[i]]+value[i]);
                }
                
            }
        }
        System.out.println(dp[n-1][v]);
        scanner.close();
    }
}

 

 总结:二维数组解决背包问题,物品和背包重量循环谁在外面都可以,且遍历顺序都是从前往后,因为要靠之前的结果得到后面的结果!!

 

零钱兑换II(完全背包的装满背包多少种方法-组合问题)

二维dp数组

public int change(int amount, int[] coins) {
        int[][] dp=new int[coins.length][amount+1];
        for(int j=0;j<=amount;j++){
            if(j%coins[0]==0){
                dp[0][j]=1;
            }
        }

        for(int i=1;i<dp.length;i++){
            for(int j=0;j<=amount;j++){
                if(j<coins[i]){
                    dp[i][j]=dp[i-1][j];
                }else{
                    dp[i][j]=dp[i-1][j]+dp[i][j-coins[i]];
                }
            }
        }
        return dp[coins.length-1][amount];
    }

一维dp数组

public int change(int amount, int[] coins) {
        int[] dp=new int[amount+1];
        dp[0]=1;

        for(int i=0;i<coins.length;i++){
            for(int j=coins[i];j<=amount;j++){
                dp[j]+=dp[j-coins[i]];
            }
        }
        return dp[amount];
    }

组合总和(规定不同顺序组合也算不同组合实质为排列问题)

public int combinationSum4(int[] nums, int target) {
        int[] dp=new int[target+1];
        dp[0]=1;

        for(int j=0;j<=target;j++){
            for(int i=0;i<nums.length;i++){
                if(j>=nums[i]){
                    dp[j]=dp[j]+dp[j-nums[i]];
                }
            }
        }
        return dp[target];
    }

 

总结:组合和排列问题在于物品和背包的内外顺序,组合问题是物品在外层循环,排列问题是背包在外层循环。

 

零钱兑换(背包装满最少个数)

除了dp[0]设置为0之外,其它初始值设置为最大值,只有当dp[j-coins[i]]不为最大值时,才有比较的意义。

public int coinChange(int[] coins, int amount) {
         int[] dp=new int[amount+1];
        
        for(int j=1;j<=amount;j++){
            dp[j]=Integer.MAX_VALUE;
        }
        dp[0]=0;
        for (int i = 0; i < coins.length; i++) {
            for (int j = coins[i]; j <= amount; j++) {
                if (dp[j-coins[i]]!=Integer.MAX_VALUE){
                    dp[j]=Math.min(dp[j],dp[j-coins[i]]+1);
                }
            }
        }
        if ( dp[amount]==Integer.MAX_VALUE){
            return -1;
        }
        return dp[amount];
    }

 

单词拆分(字符串的装满问题)⭐

首先,dp[j]的含义是当字符串的长度为j时,是否可以被字典填满

递推公式:当dp[j-str.length()]为true且子串和字典中的一个单词相同时,dp[j]=true;

遍历顺序:因为是排列问题,所以必须是先遍历背包再遍历物品。

本题字符串相当于背包,用字符串组去填拆分得到字符串。

public boolean wordBreak(String s, List<String> wordDict) {
        boolean[] dp=new boolean[s.length()+1];
        dp[0]=true;

        //当字符串在第j个位置时,是否能被字典完全拆分
        for (int j = 1; j <=s.length() ; j++) {
            //
            for (int i = 0; i < wordDict.size(); i++) {
                String str=wordDict.get(i);
                if (j>=str.length() && dp[j-str.length()] && s.substring(j-str.length(),j).equals(str)){
                    dp[j]=true;
                }
            }
        }
        return dp[s.length()];
    }

 

打家劫舍

先自己写递推过程,然后确定dp[j]的含义

dp[j]表示到了第j家偷盗的最高金额;

面临偷与不偷两个问题,取最大值,则递推公式为max(dp[j-2]+nums[j],dp[j-1]);

public int rob(int[] nums) {
        int[] dp=new int[nums.length];
        
        for(int i=0;i<nums.length;i++){
            if(i==0){
                dp[0]=nums[0];
            }else if(i==1){
                dp[1]=Math.max(nums[1],dp[0]);
            }else{
                dp[i]=Math.max(dp[i-2]+nums[i],dp[i-1]);
            }
        }

        return dp[nums.length-1];
    }

 

打家劫舍II(第一家和最后一家不能一起打劫)

分成两个部分求最大值,分别是第0家到倒数第二家,第1家到倒数第一家。

其中,每个部分的dp需要重新分配内存,且index++和赋值不能写在一行,会出错,dp的下标用index,不用i;

 public int rob(int[] nums) {
        if(nums.length==1){
            return nums[0];
        }
        return Math.max(robAction(nums,0,nums.length-1),robAction(nums,1,nums.length));
    }
    public int robAction(int[] nums,int start,int end){
        int[] dp=new int[end-start];
        int index=0;
        for(int i=start;i<end;i++){
            if(i==start){
                dp[index++]=nums[start];
            }else if(i==start+1){
                dp[index++]=Math.max(dp[0],nums[i]);
            }else{
                dp[index]=Math.max(dp[index-2]+nums[i],dp[index-1]);
                index++;
            }
        }
        return dp[index-1];
        
    }

打家劫舍III(二叉树形式)

dp的长度为2,dp[0]表示不偷该节点的最大金额,dp[1]表示偷该节点的最大金额;

需要使用递归,因为当前节点的值由后面的值决定

public int rob(TreeNode root) {
        int[] res = robAction(root);
        return Math.max(res[0],res[1]);
    }
    public int[] robAction(TreeNode root){
        int[] res=new int[2];
        if (root==null){
            return res;
        }

        int[] left=robAction(root.left);
        int[] right=robAction(root.right);
        //不偷当前节点
        int leftMax=Math.max(left[0],left[1]);
        int rightMax=Math.max(right[0],right[1]);
        res[0]=leftMax+rightMax;
        //偷当前节点
        res[1]=left[0]+right[0]+root.val;
        return res;
    }

 

股票买卖的最佳时机(一次买卖次数)

双层for循环会运行超时

使用dp[i][0]表示第i天持有股票的时的最大金额,dp[i][1]表示第i天不持有股票的最大金额

dp[i][0]从前i-1天持有股票的最大金额和第i天 持有股票的最大金额中选最大值;

dp[i][1]前i-1天不持有股票的最大金额和第i天卖出股票的最大金额中选最大值。

public int maxProfit(int[] prices) {
        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]);//第i天持有股票
            dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }

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

 

股票买卖II(多次买卖)

将dp[i][0]的表达式进行改变,第i天买入该股票时的最大金额变为dp[i-1][1]-prices[i],原先是-prices[i]

 public int maxProfit(int[] prices) {
        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]);//第i天持有股票
            dp[i][1]=Math.max(dp[i-1][1],dp[i-1][0]+prices[i]);
        }

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

 

 

股票(最多买卖两次)

使用4个状态,分别是第一次/第二次持有股票/不持有股票。

public int maxProfit(int[] prices) {
        int[][] dp=new int[prices.length][4];

        dp[0][0]=-prices[0];
        dp[0][1]=0;
        dp[0][2]=-prices[0];
        dp[0][3]=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][1],dp[i-1][0]+prices[i]);
            dp[i][2]=Math.max(dp[i-1][2],dp[i-1][1]-prices[i]);
            dp[i][3]=Math.max(dp[i-1][3],dp[i-1][2]+prices[i]);
        }
        return dp[prices.length-1][3];
    }

 

 

股票(最多买卖K次)

public int maxProfit(int k, int[] prices) {
        int[][] dp=new int[prices.length][2*k];
        //初始化
        for(int i=0;i<2*k;i++){
            if(i%2==0){
                dp[0][i]=-prices[0];
            }else{
                dp[0][i]=0;
            }
        }

        for(int i=1;i<prices.length;i++){//第i天
            for(int j=0;j<2*k;j++){//第i天的状态
            if(j==0){
                dp[i][j]=Math.max(dp[i-1][j],-prices[i]);
            }else if(j%2==1){//不持有状态
                dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-1]+prices[i]);
            }else{//持有状态
                dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-1]-prices[i]);
            }
            
            }
        }
        return dp[prices.length-1][2*k-1];
    }

 

股票(多次买卖+冷冻期)

对于不合法状态,直接带入公式得到初始合理值。

dp[i][0]:第i天持有股票时的最大金额

dp[i][1]:第i天保持不持股(冷冻期之后)的最大金额

dp[i][2]:第i天卖出股时的最大金额

dp[i][3]:第i天为冷冻期的最大金额。

public int maxProfit(int[] prices) {
        int[][] dp=new int[prices.length][4];
        //初始化
        dp[0][0]=-prices[0];
        dp[0][1]=0;
        dp[0][2]=0;
        dp[0][3]=0;

        for(int i=1;i<prices.length;i++){//第i天
        int temp=Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
            dp[i][0]=Math.max(temp,dp[i-1][3]-prices[i]);
            dp[i][1]=Math.max(dp[i-1][1],dp[i-1][3]);
            dp[i][2]=dp[i-1][0]+prices[i];
            dp[i][3]=dp[i-1][2];
        }
        int temp=Math.max(dp[prices.length-1][1],dp[prices.length-1][2]);
        return Math.max(temp,dp[prices.length-1][3]);
    }

 

最长递增子序列

使用双层for循环,因为不能单纯根据dp[i-1]推断出结果

dp[i]表示以nums[i]为结尾数组的最长严格递增子序列的长度。

最后结果需要遍历dp数组,得到最大值。

public int lengthOfLIS(int[] nums) {
        int[] dp=new int[nums.length];
        Arrays.fill(dp,1);
    
        for(int i=1;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 temp=1;
        for(int i:dp){
            temp=Math.max(i,temp);
        }
        return temp;
    }

 

最长连续递增子序列

区别在于没有双层for循环

public int findLengthOfLCIS(int[] nums) {
        int[] dp=new int[nums.length];
        Arrays.fill(dp,1);

        for(int i=1;i<nums.length;i++){
            if(nums[i]>nums[i-1]){
                dp[i]=dp[i-1]+1;
            }
        }

        int temp=1;
        for(int i:dp){
            temp=Math.max(i,temp);
        }
        return temp;
    }

 

最长重复子数组

dp[i][j]表示以nums1[i]结尾的A和nums2[j]结尾的B,最长重复子数组(两个末尾数字必须相同,否则结果为0)

public int findLength(int[] nums1, int[] nums2) {
        int[][] dp=new int[nums1.length][nums2.length];
        int max=0;

        for (int i = 0; i < nums1.length; i++) {
            if (nums1[i]==nums2[0]){
                dp[i][0]=1;
                max=1;
            }
        }
        for (int i = 0; i < nums2.length; i++) {
            if (nums1[0]==nums2[i]){
                dp[0][i]=1;
                max=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;
                    max=Math.max(dp[i][j],max);
                }
            }
        }
        return max;
    }

 

最长公共子序列(不连续)

该题就和上面不一样,后面的结果只会大于等于前面的结果

public int longiestCommonSubsequence(String text1, String text2) {
        int[][] dp = new int[text1.length() + 1][text2.length() + 1]; // 先对dp数组做初始化操作
        for (int i = 1 ; i <= text1.length() ; i++) {
            char char1 = text1.charAt(i - 1);
            for (int j = 1; j <= text2.length(); j++) {
                char char2 = text2.charAt(j - 1);
                if (char1 == char2) { // 开始列出状态转移方程
                    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()];
    }

 

判断子序列

dp[i][j]表示以下标i-1结尾的字符串s,和以下标j-1结尾的字符串t,当s完全在t中时t的长度

当子序列是ab 主序列是ah时,也判定为0

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];
                }
            }
        }
        return dp[s.length()][t.length()]==s.length();
    }

总结:关于不相等时,dp[i][j]=max(dp[i-1][j],dp[i][j-1])和dp[i][j]=dp[i][j-1]的区别在于,要不要求连续,两个都不连续时使用max,当有同一个要求连续时,使用dp[i][j]=dp[i][j-1]。

 

不同的子序列⭐

    b a e g g
  1 1 1 1 1 1
b 0 1 1 1 1 1
a 0 0 1 1 1 1
g 0 0 0 0 1 2

dp[i][j]分为两个部分,分别是使用t[j-1]和不使用t[j-1],当使用t[j-1]时dp[i][j]=dp[i-1][j-1],不使用时,dp[i][j]=dp[i][j-1]。

public int numDistinct(String s, String t) {
        int[][] dp=new int[t.length()+1][s.length()+1];
        for(int j=0;j<=s.length();j++){
            dp[0][j]=1;
        }

            for(int i=1;i<=t.length();i++){
                char t1=t.charAt(i-1);
                for(int j=1;j<=s.length();j++){
                    char s1=s.charAt(j-1);
                    if(t1==s1){
                        dp[i][j]=dp[i-1][j-1]+dp[i][j-1];
                    }else{
                        dp[i][j]=dp[i][j-1];
                    }
                }
            }
            return dp[t.length()][s.length()];
        
    }

 

两个字符串的删除操作

dp数组如下图所示:

    e a t
  0 1 2 3
s 1 2 3 4
e 2 1 2 3
a 3 3 2 1
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 j=0;j<=word2.length();j++){
            dp[0][j]=j;
        }

        for(int i=1;i<=word1.length();i++){
            char w1=word1.charAt(i-1);
            for(int j=1;j<=word2.length();j++){
                char w2=word2.charAt(j-1);
                if(w1==w2){
                    dp[i][j]=dp[i-1][j-1];
                }else{
                    dp[i][j]=Math.min(dp[i-1][j]+1,dp[i][j-1]+1);
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }

 

编辑距离(比上题多了一个删除操作)

递归表达式不相等情况多了个dp[i-1][j-1]+1

    e x e c u t i o n
  0 1 2 3 4 5 6 7 8 9
i 1 1 2 3 4 5 6 6 7 8
n 2 2 2 3 4 5 6 7 8 8
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 j=0;j<=word2.length();j++){
            dp[0][j]=j;
        }

        for(int i=1;i<=word1.length();i++){
            char w1=word1.charAt(i-1);
            for(int j=1;j<=word2.length();j++){
                char w2=word2.charAt(j-1);
                if(w1==w2){
                    dp[i][j]=dp[i-1][j-1];
                }else{
                    int temp=Math.min(dp[i-1][j]+1,dp[i][j-1]+1);
                    dp[i][j]=Math.min(temp,dp[i-1][j-1]+1);
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }

 

回文子串

dp[i][j]表示以i开始j结尾的字符串是否为回文子串,true/fasle

根据dp表格得,初始化先将对角线原始初始化为true(也可以在遍历的时候直接判断),然后遍历顺序是从中间到右边,从下面到上面

  c b a b c
c T f f f T(3)
b   T f T(2) f
a     T(1) f f
b       T f
c         T
public int countSubstrings(String s) {
        int result=0;
        boolean[][] dp=new boolean[s.length()][s.length()];
        
        for(int i=s.length()-1;i>=0;i--){
            char c1=s.charAt(i);
            for(int j=i;j<s.length();j++){
                char c2=s.charAt(j);
                if(c1==c2){
                    if(j-i<=1){
                        dp[i][j]=true;
                    }else{
                        dp[i][j]=dp[i+1][j-1];
                    }
                    if(dp[i][j]){
                        result++;
                    }
                }
            }
        }
        return result;
    }

 

最长回文子序列

dp[i][j]表示以i开始,j结束的最长的回文子序列个数

对于aab当开头和末尾不相等时,从aa/ab中取最长回文子序列值

对于abaa,当开头和末尾相等时,比较aba/ba+2取最大值

  a a b a a
a 1 2 2 3 5
a   1 1 3 3
b     1 1 2
a       1 2
a         1
public int longestPalindromeSubseq(String s) {
        int[][] dp=new int[s.length()][s.length()];
        int max=1;
        for(int i=s.length()-1;i>=0;i--){
            char s1=s.charAt(i);
            for(int j=i;j<s.length();j++){
                char s2=s.charAt(j);
                if(s1==s2){
                    if(i==j){
                        dp[i][j]=1;
                    }else{
                        dp[i][j]=Math.max(dp[i][j-1],dp[i+1][j-1]+2);
                    }
                }else{
                    dp[i][j]=Math.max(dp[i+1][j],dp[i][j-1]);
                }
                max=Math.max(max,dp[i][j]);
            }
        }
        return max;
    }

 

posted @ 2025-04-03 15:14  Dyj07  阅读(18)  评论(0)    收藏  举报