5. 最长回文子串 Longest Palindromic Substring
Given a string s
, return the longest palindromic substring in s
.
Input: s = "babad"
Output: "bab"
Note: "aba" is also a valid answer.
方法一:
暴力法,双层循环,验证所以字串是否是回文,找到最长的。
时间复杂度O(n^3)
方法二:中心扩展算法
回文是由中心往外扩展的,对于n维数组,有2n-1个中心
因为一种回文是奇数长度,只有一个中心点,数组有n个中心
一种回文是偶数长,有两个中心点,数组有n-1个中心。
public String longestPalindrome(String s){ int maxLen = 0; String dest = ""; for(int i = 0; i < s.length(); i++){ int len1 = maxLenth(s,i,i); int len2 = maxLenth(s, i, i + 1); int len = Math.max(len1,len2); if (len > maxLen ){ dest = s.substring(i-(len-1)/2,i+len/2+1); maxLen = len; } } return dest; } private int maxLenth(String s, int i, int j){ int L = i, R = j; while(L >=0 && R < s.length() && s.charAt(L)==s.charAt(R)){ L--; R++; } return R - L -1; }
时间复杂度为O(n^2)
方法三:动态规划
对于一个子串而言,如果它是回文串,并且长度大于 2,那么将它首尾的两个字母去除之后,它仍然是个回文串。
状态转移方程为:
P(i,j)=P(i+1,j−1)∧(Si==Sj)
P(i,j)P(i,j) 表示字符串 ss 的第 ii 到 jj 个字母组成的串是否为回文字串。
public String longestPalindromeDP(String s){ int n = s.length(); boolean[][] dp = new boolean[n][n]; String dst = ""; for( int l = 0; l < s.length(); ++l){ for ( int i = 0; i + l < s.length(); ++i){ int j = i + l; if (l == 0){ dp[i][j] = true; }else if(l == 1){ dp[i][j] = (s.charAt(i) == s.charAt(j)); }else{ dp[i][j] = (s.charAt(i) == s.charAt(j) )&& dp[i + 1][j - 1]; } if (dp[i][j] && (l + 1 > dst.length())){ dst = s.substring(i, i + l + 1); } } } return dst; }
时间复杂度为O(n^2)
方法四:Manacher 算法
对于长度为奇数的回文字符串,在中心扩展算法的过程中,我们能够得出每个位置的臂长。那么当我们要得出以下一个位置 i 的臂长时,可以利用之前得到的信息
具体来说,如果位置 j (在i前面)的臂长为 length,并且有 j + length > i:
当在位置 i 开始进行中心拓展时,我们可以先找到 i 关于 j 的对称点 2 * j - i。那么如果点 2 * j - i 的臂长等于 n,我们就可以知道,点 i 的臂长至少为 min(j + length - i, n)。那么我们就可以直接跳过 i 到 i + min(j + length - i, n) 这部分,从 i + min(j + length - i, n) + 1 开始拓展。
我们只需要在中心扩展法的过程中记录右臂在最右边的回文字符串,将其中心作为 j,在计算过程中就能最大限度地避免重复计算。
对于回文长度为偶数的情况:我们向字符串的头尾以及每两个字符中间添加一个特殊字符 #,比如字符串 aaba 处理后会变成 #a#a#b#a#。那么原先长度为偶数的回文字符串 aa 会变成长度为奇数的回文字符串 #a#a#,而长度为奇数的回文字符串 aba 会变成长度仍然为奇数的回文字符串 #a#b#a#,所以我们就不需要再考虑长度为偶数的回文字符串
public String longestPalindromeManacher(String s){ int len = s.length(); if (len < 2) { return s; } int start = 0, end = -1; s = addBoundaries(s, '#'); List<Integer> arm_len = new ArrayList<Integer>(); int right = -1, j = -1; for (int i = 0; i < s.length(); ++i) { int cur_arm_len; if (right >= i) { int i_sym = j * 2 - i; int min_arm_len = Math.min(arm_len.get(i_sym), right - i); cur_arm_len = maxLenth(s, i - min_arm_len, i + min_arm_len)/2; } else { cur_arm_len = maxLenth(s, i, i)/2; } arm_len.add(cur_arm_len); if (i + cur_arm_len > right) { j = i; right = i + cur_arm_len; } if (cur_arm_len * 2 + 1 > end - start) { start = i - cur_arm_len; end = i + cur_arm_len; } } StringBuffer ans = new StringBuffer(); for (int i = start; i <= end; ++i) { if (s.charAt(i) != '#') { ans.append(s.charAt(i)); } } return ans.toString(); } private String addBoundaries(String s, char divide) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < s.length(); i++) { stringBuilder.append(divide); stringBuilder.append(s.charAt(i)); } stringBuilder.append(divide); return stringBuilder.toString(); } private int maxLenth(String s, int i, int j){ int L = i, R = j; while(L >=0 && R < s.length() && s.charAt(L)==s.charAt(R)){ L--; R++; } return R - L -1; }
时间复杂度为O(n)
参考链接:
https://leetcode-cn.com/problems/longest-palindromic-substring
https://leetcode.com/problems/longest-palindromic-substring