【双指针】【DP】LeetCode 5. 最长回文子串
题目链接
思路1:中心扩散法
遍历字符串 s
,对于每个字符都以它为中心向两边扩散,测得最长回文子串。
注意:在扩散的过程中要分回文串长度奇偶进行扩散,因为长度为偶数的回文串中心是两个字母
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(1)\)
代码1
class Solution {
public String longestPalindrome(String s) {
int[] result = new int[2];
for(int i = 0; i < s.length() - 1; i++){
int[] temp = spread(s, i, i);
if((result[1] - result[0] + 1) < (temp[1] - temp[0] + 1)){
result = temp;
}
temp = spread(s, i, i + 1);
if((result[1] - result[0] + 1) < (temp[1] - temp[0] + 1)){
result = temp;
}
}
return s.substring(result[0], result[1] + 1);
}
int[] spread(String s, int left, int right){
while(left >= 0 && right < s.length()){
if(s.charAt(left) != s.charAt(right)){
break;
}
left--;
right++;
}
if(left < 0 || right == s.length()){
left++;
right--;
}
return s.charAt(left) == s.charAt(right) ? new int[]{left, right} : new int[]{left + 1, right - 1};
}
}
思路2:DP
求回文串的过程本质上就是从前往后和从后往前同时遍历字符串的过程,所以看起来只有一个字符串,其实是对两个字符串进行操作。
根据这个想法,我们可以使用二维数组 \(dp[i][j]\) 表示 \(s[i : j]\) 是否是回文串。
接下来思考如何把大问题化简为小问题,也就是让 \(dp[i][j]\) 与 \(dp[i \pm 1][j \pm 1]\) 联系在一起,根据这个想法我们自然想到对字符串的首尾两个字符进行操作。
如果一个字符串是回文串,那么它首尾两端的字符是相同的,同时它中间的子串也是回文串,这样就把这个字符串本身的问题转化成了它中间子串的问题。
根据这个思路我们可以得到状态转移方程
\[dp[i][j] = ((s[i] == s[j]) \ \&\& \ (j - i \leq 2 \ || \ dp[i + 1][j - 1]))
\]
其中 \(j - i \leq 2\) 表示长度如果小于等于2,则只需要判断两个字符是否相同即可。
代码2
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
// dp[i][j] 表示 s[i : j] 是否是回文串
boolean[][] dp = new boolean[n + 3][n + 3];
// 初始化对角线元素为 true
for(int i = 0; i < n; i++){
dp[i][i] = true;
}
// dp[i][j] = (s[i] == s[j]) && (j - i <= 2 || dp[i + 1][j - 1])
for(int j = 1; j < n; j++){
for(int i = 0; i < j; i++){
dp[i][j] = (s.charAt(i) == s.charAt(j)) && (j - i <= 2 || dp[i + 1][j - 1]);
}
}
int length = 1;
int[] result = new int[]{0, 0};
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
if(dp[i][j] && length < j - i + 1){
length = Math.max(length, j - i + 1);
result[0] = i;
result[1] = j;
}
}
}
return s.substring(result[0], result[1] + 1);
}
}
时间复杂度:\(O(n^2)\)
空间复杂度:\(O(n^2)\)