动态规划:最长回文子串 & 最长回文子序列

动态规划:最长回文子串 & 最长回文子序列一、题目所谓回文字符串,就是一个字符串,从左到右读和从右到左读是完全一样的,比如“a”、“aba”、“abba”。对于一个字符串,其子串是指连续的一段子字符串,而子序列是可以非连续的一段子字符串。最长回文子串和最长回文子序列(LongestPalindromicSubsequence)是指任意一个字符串,它说包含的长度最长的回文子串和回文子序列。例如:字符串“ABCDDCEFA…

大家好,又见面了,我是你们的朋友全栈君。

一、题目

所谓回文字符串,就是一个字符串,从左到右读和从右到左读是完全一样的,比如 “a”、“aba”、“abba”。

对于一个字符串,其子串是指连续的一段子字符串,而子序列是可以非连续的一段子字符串。

最长回文子串最长回文子序列(Longest Palindromic Subsequence)是指任意一个字符串,它说包含的长度最长的回文子串和回文子序列。

例如:字符串 “ABCDDCEFA”,它的 最长回文子串 即 “CDDC”,最长回文子序列 即 “ACDDCA”。

二、最长回文子串

1. 思路

首先这类问题通过穷举的办法,判断是否是回文子串并再筛选出最长的,效率是很差的。我们使用 动态规划 的策略来求解它。首先我们从子问题入手,并将子问题的解保存起来,然后在求解后面的问题时,反复的利用子问题的解,可以极大的提示效率。

由于最长回文子串是要求连续的,所以我们可以假设 j 为子串的起始坐标,i 为子串的终点坐标,其中 ij 都是大于等于 0 并且小于字符串长度 length 的,且 j <= i,这样子串的长度就可以使用 i - j + 1 表示了。

我们从长度为 1 的子串依次遍历,长度为 1 的子串肯定是回文的,其长度就是 1;然后是长度为 2 的子串依次遍历,只要 str[i] 等于 str[j] ,它就是回文的,其长度为 2;接下来就好办了,长度大于 2 的子串,如果它要满足是回文子串的性质,就必须有 str[i] 等于 str[j] ,并且去掉两头的子串 str[j+1 ... i-1] 也一定是回文子串,所以我们使用一个数组来保存以 j 为子串起始坐标,i 为子串终点坐标的子串是否是回文的,由于我们是从子问题依次增大求解的,所以求解 [i ... j] 的问题时,比它规模更小的问题结果都是可以直接使用的了。

2. 代码

public class Main { 
   
  
    public static void main(String[] args) { 
   
        String s = "cabbaeeaf";
        System.out.println(getLPS(s));
    }
      
    public static String getLPS(String s) { 
   
        char[] chars = s.toCharArray();
        int length = chars.length;
        // 第一维参数表示起始位置坐标,第二维参数表示终点坐标
        // lps[j][i] 表示以 j 为起始坐标,i 为终点坐标是否为回文子串
        boolean[][] lps = new boolean[length][length];
        int maxLen = 1; // 记录最长回文子串最长长度
        int start = 0; // 记录最长回文子串起始位置
        for (int i = 0; i < length; i++) { 
   
            for (int j = 0; j <= i; j++) { 
   
                if (i - j < 2) { 
   
                    // 子字符串长度小于 2 的时候单独处理
                    lps[j][i] = chars[i] == chars[j];
                } else { 
   
                    // 如果 [i, j] 是回文子串,那么一定有 [j+1, i-1] 也是回子串
                    lps[j][i] = lps[j + 1][i - 1] && (chars[i] == chars[j]);
                }
                if (lps[j][i] && (i - j + 1) > maxLen) { 
   
                    // 如果 [i, j] 是回文子串,并且长度大于 max,则刷新最长回文子串
                    maxLen = i - j + 1;
                    start = j;
                }
            }
        }
        return s.substring(start, start + maxLen);
    }
      
}

三、最长回文子序列

1. 思路

子序列的问题将比子串更复杂,因为它是可以不连续的,这样如果穷举的话,问题规模将会变得非常大,我们依旧是选择使用 动态规划 来解决。

首先我们假设 str[0 ... n-1] 是给定的长度为 n 的字符串,我们使用 lps(0, n-1) 表示以 0 为起始坐标,长度为 n-1 的最长回文子序列的长度。那么我们需要从子问题开始入手,即我们一次遍历长度 1 到 n-1 的子串,并将子串包含的 最长回文子序列的长度 保存在 lps 的二维数组中。

遍历过程中,回文子序列的长度一定有如下性质:

  • 如果子串的第一个元素 str[j] 和最后一个元素 str[i+j] 相等,那么 lps[j, i+j] = lps[j+1, i+j-1] + 2,其中 lps[j+1, i+j-1] 表示去掉两头元素的最长子序列长度。
  • 如果两端的元素不相等,那么lps[j, i+j] = max(lps[j][i+j-1], lps[j+1][i+j]),这两个表示的分别是去掉末端元素的子串和去掉起始元素的子串。

2. 代码

public class Main { 
   
 
    public static void main(String[] args) { 
   
        String s = "cabbeaf";
        System.out.println(getLPS(s));
    }
     
    public static int getLPS(String s) { 
   
        char[] chars = s.toCharArray();
        int length = chars.length;
        // 第一维参数表示起始位置的坐标,第二维参数表示长度,使用 0 表示长度 1
        int[][] lps = new int[length][length];
        for (int i = 0; i < length; i++) { 
   
            lps[i][i] = 1; // 单个字符的最长回文子序列长度为1,特殊对待一下
        }
        // (i + 1) 表示当前循环的子字符串长度
        for (int i = 1; i < length; i++) { 
   
            // j 表示当前循环的字符串起始坐标
            for (int j = 0; i + j < length; j++) { 
   
                // 即当前循环的子字符串坐标为 [j, i + j]
                // 所以第一个字符是 chars[j],最后一个字符就是 chars[i + j]
                if (chars[j] == chars[i + j]) { 
   
                    lps[j][i + j] = lps[j + 1][i + j - 1] + 2;
                } else { 
   
                    lps[j][i + j] = Math.max(lps[j][i + j - 1], lps[j + 1][i + j]);
                }
            }
        }
        // 最大值一定在以0为起始点,长度为 length - 1 的位置
        return lps[0][length - 1];
    }
     
}

最后,这题只返回了最长回文子序列的长度,一般面试题中也只是要求返回长度即可。但是如果你也想知道最长回文子序列具体是啥,这可以额外添加一个变量记录最长回文子序列是哪些字符,例如维护一个键为 lps[j][i + j],值为 String 的 map。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/131000.html原文链接:https://javaforall.net

(0)
上一篇 2022年4月29日 下午12:20
下一篇 2022年4月29日 下午12:20


相关推荐

  • 在ThinkPHP中,if标签和比较标签对于变量的比较。

    在ThinkPHP中,if标签和比较标签对于变量的比较。

    2021年9月18日
    43
  • 原生css变量

    原生css变量CSS 自定义属性 通常称为 CSS 变量 类似于 JS 中声明的变量 可以复用 CSS 属性值 定义格式 变量名 值 变量两个开头 1 创建全局 CSS 变量 geek color primary root geek color primary fc6627 2 复用 list item active color var geek color primary tabs item active col

    2026年3月17日
    1
  • js算法初窥05(算法模式02-动态规划与贪心算法)

    在前面的文章中(js算法初窥02(排序算法02-归并、快速以及堆排)我们学习了如何用分治法来实现归并排序,那么动态规划跟分治法有点类似,但是分治法是把问题分解成互相独立的子问题,最后组合它们的结果,而

    2022年3月25日
    37
  • pycharm是java开发的吗_pycharmjupyter

    pycharm是java开发的吗_pycharmjupyterPycharm—编译器使用:虚拟环境与系统环境:就是包的区别。虚拟环境需要重新下包,但包不互相影响系统环境就是有下载过的所有包—通过切换本地—虚拟编译器即可切换环境本地:即python所在目录的python.exe程序–python最主要就是lib(第三方库群),python.exe编译器,pythonshell自带的IDLE,再加上个集成开发环境(pycharm)就齐了虚拟:v…

    2022年8月27日
    8
  • 使用flyio全局拦截ajax「建议收藏」

    使用flyio全局拦截ajax「建议收藏」1.场景如果我们在已经开发好的一个网站上优化,做ajax请求缓存,不确定他使用了什么ajax工具,应该怎么做。假如开发者使用的是axios,jquery等,我们可以在其中使用axios和jquery的拦截器做拦截。但是flyio提供了一个更简单的方法。2.使用1.安装flyionpminstallflyio–save2.使用如下使用,即可拦截全局的ajaximportflyfrom’flyio’;importEngineWrapperfrom’flyio/dis

    2025年10月7日
    6
  • Ubuntu下安装vscode_附近有安装etc的吗

    Ubuntu下安装vscode_附近有安装etc的吗目录一、安装VSCode1、VSCode下载2、安装VSCode二、配置C/C++环境1、配置g++环境1.1安装vim1.2安装g++2、安装汉化、C/C++插件3、建立工程4、更改lanuch.json文件5、更改task.json文件6、完美运行我的Ubuntu已经换源了,但是在浏览器中下载比较慢,可以在window中下载好直接拖到虚拟机中。如何在windows和虚拟机Ubuntu中拖拽文件参阅:两行代码实现Windows和…

    2026年1月16日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号