• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无信不立

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【数据结构和算法】 求出字符串中的最长回文串

https://blog.csdn.net/tiankong_12345/article/details/102018257

零、题目

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

回文串概念:从左侧读串,和从右侧读串,是一样的。

例如:aba,  abba, a ,  bb, bbb

 

一、暴力求解算法

step1:先求指定字符串所有的子串

step2:根据每个字串判断是否为回文串

时间复杂度:两层 for 循环 O(n²),for 循环里边判断是否为回文 O(n),所以时间复杂度为 O(n³)。

空间复杂度:O(1),常数个变量。

/**
     * 穷举一个字符串所有的子字符串
     * 时间复杂度O(N^2)
     *
     * @param string
     * @return
     */
    public static Set<String> queryAllSubStrings(String string) {
        //字符串为空则返回空集合
        if (StringUtils.isBlank(string)) {
            return Collections.emptySet();
        }
        //字串长度为1则返回只有本身的元素集合
        if (string.length() == 1) {
            Set<String> result = new HashSet<>();
            result.add(string);
            return result;
        }
        //进行加工
        Set<String> result = new HashSet<>();
        StringBuilder builder = null;
        //外层循环控制字串的开始下标
        for (int i = 0; i < string.length(); i++) {
            builder = new StringBuilder();
            for (int j = i; j < string.length(); j++) {
                //内存循环控制字串结束的下标
                builder.append(string.charAt(j));
                result.add(builder.toString());
            }
        }
        return result;

    }


    /**
     * 判断一个字符串窜是否是回文串
     * 求数组中指定下标 i 在数组中对称位置的下标的公式
     * j = length - i -1
     *
     * @param str
     * @return
     */
    public static boolean isPalindromeString(String str) {
        //空的字串不是回文串
        if (StringUtils.isBlank(str)) {
            return false;
        }
        //一个字符串本身是回文串
        if (str.length() == 1) {
            return true;
        }
        //判定超过两个(包括)长度的字符串是否是回文串
        int length = str.length();
        for (int i = 0; i < length / 2; i++) {
            //如果一个字符串的对称位置的字符均相等,则该字符串为回文串
            if (str.charAt(i) != str.charAt(length - i - 1)) {
                return false;
            }
        }
        return true;
    }
View Code

二、动态规划算法

暴力解法时间复杂度太高,我们可以考虑,去掉一些暴力解法中重复的判断。

时间复杂度:两层循环 O(n²)。

空间复杂度:用二维数组 PP保存每个子串的情况 O(n²)。

代码实现如下

/**
     * 基于动态规划的思路求解回文串
     * <p>
     * <p>
     * 【整体思想】
     * (1)把字符串中所有子字符串是否为回文串的结果记录在一个二维数组中
     * (2)从长度为1的字串,逐步增长,向长度为n的字串进行求解(基于滑动窗口的思想求解)
     * (3)在求解长度n是否是回文串,会参考长度为 n-1 的串是否为回文串的结果(这就是动态规划的思想关键所在)
     * (4)回文串的求解公式为: p[i][j] =  (length ==1 || length==2 || p[i+1][j-1])  &&   char[i] == char[j]
     * 其中 length 表示当前求解回文串的字符串的长度
     * i,j 表示当前求解的字符串的初始字符和结束字符的下标
     * <p>
     * <p>
     * 求出指定字符串中的最长回文串
     *
     * @param str
     * @return
     */
    public static String maxPalindromeSearch(String str) {
        int length = str.length();
        int maxLength = 0;
        String maxPalindrome = null;
        //声明一个二维数组记录str每个字串是否为回文串的结果
        Boolean[][] result = new Boolean[length][length];

        //strLen表示子字符串的长度( 从1->length 长度逐步求解各个长度的子字符串的是否回文的结果 )
        for (int strLen = 1; strLen <= length; strLen++) {

            for (int startIndex = 0; startIndex < length - 1; startIndex++) {
                //求出当前字串的结尾下标
                int endIndex = startIndex + strLen - 1;

                if (endIndex > length - 1) {
                    //子字符串的结尾下标超过了父字符串的最大下标,需要停止扫描
                    continue;
                }

                //求当前字串是否为回文串,利用了动态规划的求解结果
                //长度为1=>默认为回文串
                //长度为2=> 看首位字符是否相等
                //长度>2 => p[i+1][j-1])  &&   char[i] == char[j]
                result[startIndex][endIndex] = (strLen == 1 || strLen == 2 || result[startIndex + 1][endIndex - 1]) && str.charAt(startIndex) == str.charAt(endIndex);

                if (result[startIndex][endIndex] && strLen > maxLength) {
                    //当前字串为回文串,且大于当前扫描的最大长度
                    //赋值最长回文串
                    maxPalindrome = str.substring(startIndex, endIndex + 1);
                    //赋值最长回文串的长度
                    maxLength = strLen;
                }

            }

        }

        return maxPalindrome;

    }
View Code

 

三、中心扩展算法

我们知道回文串一定是对称的,所以我们可以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。

 

由于存在奇数的字符串和偶数的字符串,所以我们需要从一个字符开始扩展,或者从两个字符之间开始扩展,所以总共有

n + (n-1)个中心

 /**
     * 中心扩展算法求解回文串
     *
     * @param str
     * @return
     */
    public static String loopCenterString(String str) {
        if (str == null || str.length() < 1) {
            return "";
        }
        //声明回文串的开始下标和结束下标
        int startIndex = 0;
        int endIndex = 0;

        //遍历字符串每一个元素,让每一个元素作为中心点,向外扩展尝试找到最大回文串
        for (int i = 0; i < str.length(); i++) {
            //按当前字符下标作为奇数中心点向外扩展
            int qLen = scanStrByCenterIndex(str, i, i);
            //按当前字符下标作为偶数中心点向外扩展
            int oLen = scanStrByCenterIndex(str, i, i + 1);

            //当前循环的回文串最长长度
            int currentMaxLen = Math.max(qLen, oLen);

            //如果当前中点点求得的回文串长度,大于已求出的回文串长度,则更新当前最长的回文串的开始下标和结束下标
            if(currentMaxLen > (endIndex-startIndex+1)){
                startIndex = i - (currentMaxLen-1)/2;
                endIndex = i + currentMaxLen/2;
            }

        }

        //返回最终的最长回文串结果
        return str.substring(startIndex,endIndex+1);

    }

    /**
     * 以该中心点开始向外扩展进行扫描
     *
     * @param str
     * @param scanLeftIndex
     * @param scanRightIndex
     * @return
     */
    private static int scanStrByCenterIndex(String str, int scanLeftIndex, int scanRightIndex) {
        int L = scanLeftIndex;
        int R = scanRightIndex;

        while (L >= 0 && R < str.length() && str.charAt(L) == str.charAt(R)) {
            L--;
            R++;
        }
        //多循环了一次,要减掉
        return (R - 1) - (L + 1) + 1;
    }
View Code

 

posted on 2023-04-01 17:24  无信不立  阅读(62)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3