动态规划——回文系列总结

目录

一. 引入 

1.前后对比

2.反转整个字符串与原字符串进行对比。

3.中心扩散

4.动态规划

二. 扩展

1.变成回文的最少操作次数 

(1)题目

(2)思路 

(3)代码

2. 最长回文子序列

(1)题目

(2)思考过程

(3)代码


一. 引入 

谈到判断回文,我们可以想到:

1.前后对比

将第一个字符与最后一个字符位置进行比对,left++,right--,直至left>right结束,如果仍没有中途return false,那么它就是回文序列;

  public static boolean isPalindrome(String str) {
        int start = 0;
        int end = str.length() - 1;

        while (start < end) {
            if (str.charAt(start) != str.charAt(end)) {
                return false;
            }
            start++;
            end--;
        }

        return true;
    }

2.反转整个字符串与原字符串进行对比。

    public static boolean isPalindrome(String str) {
        String reversed = "";
        int length = str.length();
        for (int i = length - 1; i >= 0; i--) {
            reversed = reversed + str.charAt(i);
        }//springbuilder的reverse也行
        return str.equals(reversed);
    }

3.中心扩散

public static boolean isPalindrome(String str) {
        int length = str.length();
        for (int i = 0; i < length; i++) {
            if (!expandFromCenter(str, i, i)) // 奇数长度以当前字符为中心
                return false;
            if (!expandFromCenter(str, i, i + 1)) // 偶数长度以当前字符和下一个字符之间为中心
                return false;
        }
        return true;
    }

    private static boolean expandFromCenter(String str, int left, int right) {
        while (left >= 0 && right < str.length() && str.charAt(left) == str.charAt(right)) {
            left--;
            right++;
        }
        return right - left == 2; // 判断回文串长度是否为奇数
    }

4.动态规划

图解

代码

//将[i,j]的是否是回文串的信息填入dp表里里面,用的时候直接查就好. 
public void f(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        int ret = 0;

        for (int i = n - 1; i >= 0; i--) {
            for (int j = i; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;
                }
               
            }
        }
       
        
    }
dp[i][j]表示字符串[i,j]是否是回文子串;其中i<=j

✅s[i]!=s[j]——>false;

✅s[i]==s[j]:

   i==j        true

   i+1=j       true

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

其中dp[i][j]还依赖dp[i+1][j-1],所以i从下往上填,j从左往右填
if (s.charAt(i) == s.charAt(j)) {
     dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;
}

👻👻👻对于是否需要初始化?eg:3*3宫格
dp[i + 1][j - 1]涉及到越界的位置是(0,0),(2,2)
但是这两点都满足i=j,上面代码写法会直接特判这种情况为true;
所以不需初始化,这样写避免了越界情况

这种思路可用于求字符串中回文子串的个数,字符串中最长的回文串(记录每次满足的begin和len进行迭代

if (dp[i][j]&&j-i+1>len) {

          begin=i;

          len=j-i+1;

 },最后substring(i,i+len)即可),通常在一些中等题或者困难题最为一种优化而出现。

二. 扩展

1.变成回文的最少操作次数 

(1)题目

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让 s 成为回文串的 最少操作次数 。

示例 1:

输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。

示例 2:

输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm" 。

子串就是子数组

(2)思路 

  1.  状态表示:dp[ i ][ j ]表示让字符串s中 i 到 j 位置的子串变成回文穿的最少操作次数。
  2.  状态方程:
  3.  初始化:对应图看

💡当s[i]=s[j]时,dp[i+1][j-1]会越界,对应位置(0,0)和(2,2),但是这两点都在i=j的对角线上,已经进行过特判0。这点在引入的第四点已经提到过;

if (s.charAt(i) == s.charAt(j)) {
     dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;
}

👻👻👻对于是否需要初始化?eg:3*3宫格
dp[i + 1][j - 1]涉及到越界的位置是(0,0),(2,2)
但是这两点都满足i=j,上面代码写法会直接特判这种情况为true;
所以不需初始化,这样写避免了越界情况

优化:将j从i+1位置开始,也就满足题意,且不用考虑越界进行的特判;

关于为啥将 j 初始化成 i+1?

首先dp表未初始化时对应的值就是0,所以i=j, 这对角线就不用管了。并且不需要对越界进行特判,减小代码量,算是一种优化吧。

💡当s[i]!=s[j]时,首先就不会在对角线i=j上,只会存在于i=j+1,以及之后的,在下图就是对应的那三个黑点会涉及不相等的情况,会依赖下方的值dp[i+1][j]以及左方的值dp[i][j-1],即蓝色的箭头,很明显不会越界

4. 填表顺序:dp[i][j]依赖dp[i+1][j-1],dp[i][j+1],dp[i+1][j]。所以 i 从下往上,j 从左往右。

5. 返回值:

字符串 
———————————————————
0                  n-1
return dp[0][n-1];

(3)代码

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

2. 最长回文子序列

(1)题目

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

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。


示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

(2)思考过程

才开始的想法就是以i位置为结尾,即dp[i]表示以i位置为结尾的所有子序列中最长回文子序列的长度,i可以依赖...i-2,i-1,但是不能确定前面的某个使我们想要的满足回文关系的。所以用dp[i][j]表示在字符串[i,j]位置的所有子序列中最长回文子序列的长度。看到了子序列还去看了下子序列的引入题300. 最长递增子序列。300题是[j,i]表示其中0<=j<i,

写出状态转移方程,

考虑初始化,当s[i]=s[j],这种对i=j的情况进行特判就可以,就不会发生越界问题,也不用对dp表初始化,当s[i]!=s[j],(0,0)(2,2)位置危险,但是仍然属于i=j的范畴,然而i=j这样,只有一个字符,必然满足s[i]=s[j],所以越界考虑不成立

子序列的引入题300. 最长递增子序列。

​
class Solution {
    //dp[i]以i位置为结尾的所有子序列的最长递增子序列的长度;
    public int lengthOfLIS(int[] nums) {
        int n=nums.length;
        int[] dp=new int[n];
        for(int i=0;i<n;i++) dp[i]=1;
        int ret=1;//最差就是1
        for(int i=1;i<n;i++){
            //for(int j=i-1;j>=0;j--){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j])
                    dp[i]=Math.max(dp[j]+1,dp[i]);
            }
            ret = Math.max(dp[i],ret);
        }
        return ret;
    }
}
//**💡dp[i]以i位置为结尾的所有子序列的最长递增子序列的长度;**
// dp[i]长度为1;
// dp[i]长度大于1;
// ...   i-1 ,   i
// ...   i-2 ,   i
// ...   i-3 ,   i
// **💡0=<j<=i-1**MAX(dp[j]+1)
// ```
// **前提**nums[j]<nums[i]
// dp[j]+1~
// ```

(3)代码

class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        int[][] dp = new int[n][n];
        
        for (int i = n - 1; i >= 0; i--) {
            dp[i][i] = 1;//i=j的情况
            for (int j = i + 1; j < n; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;//合并i+1<=j的情况
                } else {
                    dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]);
                }
            }
        }
        
        return dp[0][n - 1];
    }
}

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

sqyaa.

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

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

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

打赏作者

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

抵扣说明:

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

余额充值