最长回文子串

162 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣题目链接

问题描述

给你一个字符串 s,找到 s 中最长的回文子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

提示:

  • 1 <= s.length <= 1000
  • s 仅由数字和英文字母组成

剖析

暴力破解

首先解释下回文,回文就是对称的字符串,即正过来返回来都相等。看到这道题想了下,最简单的就是暴力破解,两层for循环从前到后扩大截取子字符串进行判断是否对称,在这个过程中记录当前最长的子字符串和最长的长度,如果后面没有更长的就返回当前的。

至于判断字符串是否对称:设字符串长度为l,i为字符串的字符下标,那么只需要判断i下标下的字符是否和l-i-1下标的字符是否相等即可。

代码如下:

public static boolean isPalindromic(String s) {
    for (int i = 0; i < s.length() / 2; i++) {
        if (s.charAt(i) != s.charAt(s.length() - i - 1)) {
            return false;
        }
    }
    return true;
}

// 暴力解法
public static String longestPalindrome(String s) {
    //最终返回的字符串
    String result = "";
    int max = 0;
    for (int i = 0; i < s.length(); i++) {
        for (int j = i + 1; j <= s.length(); j++) {
            String subS = s.substring(i, j);
            if (isPalindromic(subS) && subS.length() > max) {
                max = Math.max(max, subS.length());
                result = subS;
            }
        }
    }
    return result;
}

动态规划

动态规划的核心思想就是:从内到外归纳总结再归纳总结,后项再前项的基础上进行。对于数学来说就是写转移方程了,最内层的项是一个特殊情况。

所以针对于这道题,最内层的项就是当有一个字符的时候为回文,大于等于2就是需要最外层的左右两边到最内层的左右两边相等。

我们用 P(i,j)表示字符串 s 的第 i 到 j 个字母组成的串是否为回文,如果为回文就是true,那么它的转移方程就是:P(i,j)=P(i+1,j−1)∧(Si==Sj),Si、Sj就是最外层左右两边的字符,特殊情况就是P(i,i)=true。

代码如下:

代码

 public static String longestPalindrome(String s) {
           if (s.length() < 2) {
            return s;
        }

        //最长回文字串的左边下标
        int begin = 0;
        //当前最长回文字串长度
        int max = 1;
        boolean[][] dp = new boolean[s.length()][s.length()];
        //特殊情况赋值
        for (int i = 0; i < s.length(); i++) {
            dp[i][i] = true;
        }

        //特殊情况已经处理,所以从2开始
        for (int l = 2; l <= s.length(); l++) {
            for (int i = 0; i < s.length(); i++) {
                int j = l + i - 1;
                //到达了右边界
                if (j >= s.length()) {
                    break;
                }

                if (s.charAt(i) != s.charAt(j)) {
                    dp[i][j] = false;
                } else {
                    //已经经过了if判断所以i下标和j下标的字符必定相等,当长度为2时dp[i][j]必定为true。
                    if (l == 2) {
                        dp[i][j] = true;
                    } else {
                        dp[i][j] = dp[i + 1][j - 1];
                    }
                }

                //是回文的话就判断长度是否大于,大于就进行替换
                if (dp[i][j] && (j - i + 1) > max) {
                    begin = i;
                    max = (j - i + 1);
                }
            }
        }

        return s.substring(begin, begin + max);
    }