LeetCode解题 5:Longest Palindromic Substring(多解法:动态规划+中心扩散)
Problem 5: Longest Palindromic Substring [Medium]
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example 2:
Input: “cbbd”
Output: “bb”
来源:LeetCode
解题思路
1. DP
使用动态规划思想。维护一个长度为N的数组len,len[i]代表以 s i s_i si为结尾的最长回文子串的长度,遍历字符串计算len[i],每次计算出新的len[i]后更新一次maxLen和end。
具体思路:
- 首先将len[0],maxLen初始化为1,end初始化为0。
- 遍历字符串
s
s
s,计算len[i],计算方法为:
a. 假设len[i-1] = m,则 s i − m , s i − m + 1 , . . . , s i − 1 s_{i-m}, s_{i-m+1}, ..., s_{i-1} si−m,si−m+1,...,si−1为以 s i − 1 s_{i-1} si−1结尾的最长回文子串。
b. 若 s i − m − 1 = s i s_{i-m-1} = s_{i} si−m−1=si,则有len[i] = len[i-1] + 2。即,以 s i s_i si结尾的最长回文子串为 s i − m − 1 , s i − m , . . . , s i − 1 , s i s_{i-m-1}, s_{i-m}, ..., s_{i-1}, s_i si−m−1,si−m,...,si−1,si,比以 s i − 1 s_{i-1} si−1结尾的回文子串长度增加了2。
c. 若 s i − m − 1 ≠ s i s_{i-m-1} \neq s_{i} si−m−1=si或者 i − m − 1 < 0 i-m-1 < 0 i−m−1<0,则在 s i − m , . . . , s i s_{i-m}, ..., s_i si−m,...,si范围内用双指针寻找以 s i s_i si结尾的最长回文子串。(Solution DP中的函数findSym) - 不断更新maxLen和end,更新公式为:
{ m a x L e n , e n d } = { { m a x L e n , e n d } l e n [ i ] ≤ m a x L e n ; { l e n [ i ] , i } l e n [ i ] > m a x L e n . \{maxLen, end\} = \begin{cases} \{maxLen, end\} & len[i] \leq maxLen;\\ \{len[i], i\} & len[i] > maxLen. \end{cases} {maxLen,end}={{maxLen,end}{len[i],i}len[i]≤maxLen;len[i]>maxLen. - 最后返回子串 s e n d − m a x L e n + 1 , . . . , s e n d s_{end-maxLen+1}, ..., s_{end} send−maxLen+1,...,send。
最坏情况下每次计算len[i]都需要遍历i-1次,总时长为 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( n ) O(n) O(n)。
运行结果:
2. 中心扩散法
遍历字符串 s s s,分别以 s i s_i si和 s i − 1 / 2 s_{i-1/2} si−1/2为中心向左右扩展回文串,不断更新最大长度maxLen和子串起始位置start。
具体思路:
- 首先将maxLen初始化为1,start初始化为0。
- 遍历字符串
s
s
s,使用双指针法计算len1和len2,具体计算方法为:
a. len1 = 以 s i − 1 / 2 s_{i-1/2} si−1/2为中心的最长回文子串长度,令双指针起始位置为p = i-1,q = i,不断令p- -,q++,直到 s p ≠ s q s_p \neq s_q sp=sq。返回长度q-p+1一定为偶数。
b. len2 = 以 s i s_i si为中心的最长回文子串长度,令双指针起始位置为p = i-1,q = i+1,同样不断令p- -,q++,直到 s p ≠ s q s_p \neq s_q sp=sq。返回长度q-p+1一定为奇数。 - 将maxLen与len1、len2比较,不断更新maxLen和start。
- 最后返回子串 s s t a r t , . . . , s s t a r t + m a x L e n − 1 s_{start}, ..., s_{start + maxLen - 1} sstart,...,sstart+maxLen−1。
需要遍历 2 n − 1 2n-1 2n−1个位置,每个位置最多需要时长 min { i , n − i } \min\{i, n-i\} min{i,n−i},总时长大约为 n ( n − 2 ) 2 \frac{n(n-2)}{2} 2n(n−2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( 1 ) O(1) O(1)。
运行结果:
Solution (Java)
DP:
class Solution {
public String longestPalindrome(String s) {
int N = s.length();
if(N < 2) return s;
int[] len = new int[N];
int maxLen = 1;
int end = 0;
len[0] = 1;
for(int i = 1; i < N; i++){
int symmetry = i-1-len[i-1];
if(symmetry < 0 || s.charAt(symmetry) != s.charAt(i)){
len[i] = findSym(s, symmetry+1, i);
}
else{
len[i] = len[i-1] + 2;
}
if(len[i] > maxLen){
maxLen = len[i];
end = i;
}
}
return s.substring(end - maxLen + 1, end + 1);
}
private int findSym(String s, int start, int end){
for(int i = start; i < end; i++){
if(s.charAt(i) == s.charAt(end)){
int p = i+1;
int q = end-1;
while(p <= q && s.charAt(p) == s.charAt(q)){
p++;
q--;
}
if(p > q) return end - i + 1;
}
}
return 1;
}
}
中心扩散:
class Solution {
public String longestPalindrome(String s) {
int N = s.length();
if(N < 2) return s;
int maxLen = 1;
int start = 0;
int len1, len2;
for(int i = 1; i < N; i++){
len1 = calcLen(s, i - 1, i); // center: (i - 1/2)
len2 = calcLen(s, i - 1, i + 1); // center: i
if(len1 > maxLen){
maxLen = len1;
start = i - len1/2;
}
if(len2 > maxLen){
maxLen = len2;
start = i - len2/2;
}
}
return s.substring(start, start + maxLen);
}
private int calcLen(String s, int start1, int start2){
int N = s.length();
while(start1 >= 0 && start2 < N && s.charAt(start1) == s.charAt(start2)){
start1--;
start2++;
}
return start2 - start1 - 1;
}
}