最长回文子串
给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
方法一:暴力匹配法
思路分析:
- 根据回文子串的定义,美剧所有长度大于等于2的长度,依次判断他们是否是回文
- 只针对当前得到的最长回文子串长度的子串进行"回文验证".
- 在记录最长回文子串的时候,可以只记录“当前子串的起始位置”和“子串长度”,不必做截取。
package com.nie.Leec.exercise;
public class lee_005 {
public static void main(String[] args) {
String str = "babad";
lee_005 a = new lee_005();
System.out.println(a.longestPalindrome(str));
}
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
//先将字符串转换成数组
char[] charArray = s.toCharArray();
for (int i = 0; i < len - 1; i++) { //第一个数子,当开始比如 babad中表示b 一直往后遍历一直到结束
for (int j = i + 1; j < len; j++) {//在一次循环中,babad 第一个遍历到b 第二个循环就有其后紧邻的开始遍历 即b
//j-i+1>maxLen判断是否比保存的长度长,,如果长,,进行判断是否是回文
if (j - i + 1 > maxLen && validPalidromic(charArray, i, j)) {
maxLen = j - i + 1;//目前最长的回文的长度
begin = i;//回文开始的位置
}
}
}
return s.substring(begin, maxLen + begin);
}
private boolean validPalidromic(char[] charArray, int left, int right) {
while (left < right) {
if (charArray[left] != charArray[right]) { //进行截取的
return false;
} else {
left++;
right--;
}
}
return true;
}
}
复杂度分析:
- 时间复杂度:O(N^3),这里 NN是字符串的长度,枚举字符串的左边界、右边界,然后继续验证子串是否是回文子串,这三种操作都与 NN相关;
- 空间复杂度:O(1),只使用到常数个临时变量,与字符串长度无关。
方法二:动态规划
主要思想:
主要思想:一旦在一个回文串的两端,对称的加上相同的元素,那么新形成的字符串仍然是一个回文串
方法:设定两个下标,从左到右扫描,一旦i和j下标重回,那么i下标移动的头部,从头开始扫描,j下标向后移动一个元素
当i下标和j下标指向的元素相同的时候,判断除去这两个相同元素剩下的字符串是否是回文串,如果是,那么最长回文串就是当前形成的新串,如果不是则继续扫描.
分布思路
- 先考虑题目的特解,本题的特解要考虑字符串为空串和字符串为1的情况,这两种情况都符合题意,所以直接返回字符串s。
- 声明两个变量maxLen为最大长度,begin为最大长度的回文的开始位置
- 二维数组dp[][],dp[i][j]为下标i到j的位置上的字符为回文 ,初始化的dp[i][i],因为单个字符串必然为回文
- 两重循环,j从第二个字符(下标为1)一直遍历到字符串末尾,同时保证 i从0遍历到j-1(i<j)。
- dp[i][j]为回文的判定条件第一个就是c[i] == c[j] ,符合这一条件的继续判定,不符合的直接置为false
- j-i<3,说明 j-i+1<4, 表明i到j没有4个字符,i到j中间只有0个或者1个字符,上一步已经判定c[i] == c[j],所以dp[i][j]必为回文,置为true。
- 边界条件为 j-i>=3,即j-i+1>=4,i到j至少有4个字符。这种情况下dp[i][j]要为回文,dp[i+1][j-1]必须为回文,即dp[i][j] = dp[i + 1][j - 1] 同正同错。
- 当dp[i][j]为回文且 i到j的字符串个数大于当前维护的最大长度,即 j-i+1>maxLen时候,需要把maxLen这个最大长度改为i到j的个数(j-i+1),开始位置begin改为i。
public class Solution {
public String longestPalindrome(String s) {
// 特判
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i, j] 是否是回文串
boolean[][] dp = new boolean[len][len];
char[] charArray = s.toCharArray();
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
for (int j = 1; j < len; j++) {
for (int i = 0; i < j; i++) {
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
//j-i<3,说明 j-i+1<4, 表明i到j没有4个字符,i到j中间只有0个或者1个字符,
//上一步已经判定c[i] == c[j],所以dp[i][j]必为回文,置为true。
if (j - i < 3) {
dp[i][j] = true;
} else {
// j-i>=3,即j-i+1>=4,i到j至少有4个字符。这种情况下dp[i][j]要为回文,
//dp\[i+1]\[j-1]必须为回文,即dp\[i]\[j] = dp\[i + 1]\[j - 1] 同正同错。
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
复杂度分析:
- 时间复杂度:O(N^2);
- 空间复杂度:O(N2),二维数组,因此空间复杂度为O(N2);
总结:
- 用动态规划解题问题,有的时候并不是直接面向问题的
- 动态规划依然是(空间换取时间)的思想的体现,而且本身动态规划就是一种打表格,就是空间换取时间
- 动态规划的本质还是(暴力解法),因为需要枚举左右边界,有O(N2);
方法三:中心扩散法
文串一定是对称的,所以我们可以每次循环选择一个中心,进行左右扩展,判断左右字符是否相等即可。
- 奇数回文串的“中心”是一个具体的字符,例如:回文串 “aba” 的中心是字符 “b”;
- 偶数回文串的“中心”是位于中间的两个字符的“空隙”,例如:回文串串 “abba” 的中心是两个 “b” 中间的那个“空隙”。
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
String res = s.substring(0, 1);
// 中心位置枚举到 len - 2 即可
for (int i = 0; i < len - 1; i++) {
String oddStr = centerSpread(s, i, i);
String evenStr = centerSpread(s, i, i + 1);
String maxLenStr = oddStr.length() > evenStr.length() ? oddStr : evenStr;
if (maxLenStr.length() > maxLen) {
maxLen = maxLenStr.length();
res = maxLenStr;
}
}
return res;
}
private String centerSpread(String s, int left, int right) {
// left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数//左边
// right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
int len = s.length();
int i = left;
int j = right;
while (i >= 0 && j < len) {
if (s.charAt(i) == s.charAt(j)) {
i--;
j++;
} else {
break;
}
}
// 这里要小心,跳出 while 循环时,恰好满足 s.charAt(i) != s.charAt(j),因此不能取 i,不能取 j
return s.substring(i + 1, j);
}
}
复杂度分析
- 时间复杂度:O(N2),枚举"中心位置"时间复杂度O(N),由中心位置扩展到回文O(N),所以为O(N2)
- 空间复杂度O(1),只使用到常数个临时变量,与字符串长度无关。