算法分析与设计,第二周博客
Longest Palindromic Substring
Description:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example:
Input: "cbbd" Output: "bb"
本题的题意是要找出一个字符串中最长的回文子串。在此之前,需要理解两个概念:
- 回文:指的是一个字符串,从头读到尾和从尾读到头的结果是一样的。
- 子串:指的是一个字符串从某一处开始一直连续到某一处结尾,注意需要与子序列区别开来。
判断一个字符串是否是回文的代码如下:
boolean isPalindrome(String s) {
int n = s.length();
for (int i = 0; i < n/2; ++i) {
if (s.charAt(i) != s.charAt(n-i-1))
return false;
}
return true;
}
这个算法的时间复杂度为O(n)。
再次回到这个问题上来,要解决这个问题,最简单的一个方法就是找出这个字符串的所有子串,一个个的判断其是否为回文,并找出属于回文中的最长的那一个。实现的代码如下:
public String longestPalindrome(String s) {
String res = "";
int start = 0, end = 0;
int n = s.length();
for (int i = 0; i < n; ++i) {
for (int j = i+1; j <= n; ++j) {
String cur = s.substring(i, j);
if (isPalindrome(cur) && cur.length() > res.length())
res = cur;
}
}
return res;
}
这个算法的时间复杂度为O(n^3),理所当然的超时了...
O(n^3)的时间复杂度太高了,需要找到一个更加好的算法。
再次回到回文这个点上来。我们用P(i, j)来代表字符串s的从下标i到下标j的子串是否为回文,也就是:
P(i, j) = true 如果s.substring(i, j+1)是回文。
观察回文,可以发现以下两个性质,设字符串s是回文,长度为n:
- s[0] == s[n-1]
- s.substring(1,n-1)也是回文
所有的回文都具备以上两个性质,而满足以上两个条件的字符串也必定是回文。也就是说,以上两个条件是一个字符串是回文的充分必要条件。
另外有以下两个简单的初始的性质:
- P(i, i) = true
- P(i, i+1) = s[i] == s[i+1]
有了这几个性质,就可以简化算法了:
P(i, j) = s[i] == s[j] && (i == j || i+1 == j || P(i+1, j-i) );
如果用一个二维数组将P存储起来,那么就只需要为这个表中的每个值赋值就可以得到结果了。这样的算法时间复杂度是O(n^2)。实现代码如下:
for (int i = n-1; i >= 0; --i) {
for (int j = i+1; j < n; ++j) {
if (s.charAt(i) == s.charAt(j) && (i == j || i+1 == j || p[i+1][j-1] == 1))
p[i][j] = 1;
}
}
在这个算法中,需要注意的是,在为p[i][j]赋值时,需要提前把p[i+1][j-1]的值确定,这也就要求需要从下往上开始赋值,所以第一个循环是从n-1到0,而不是从0到n-1。
得到了这个有关子串是否是回文的二维数组后,只需要在这个数组中把最长的那个子串找出来就可以了。代码如下:
int start = 0, end = 0;
for (int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
if (p[i][j] == 1 && j-i > end-start) {
end = j;
start = i;
}
}
}
return s.substring(start, end+1);
确定二维数组的值和找出最长子串的时间复杂度都是O(n^2),所以整个程序的复杂度也是O(n^2)。全部代码如下:
class Solution {
public String longestPalindrome(String s) {
int n = s.length();
int[][] p = new int[n][n];
for (int i = n-1; i >= 0; --i) {
for (int j = i; j < n; ++j) {
if (s.charAt(i) == s.charAt(j) && (i == j || i+1 == j || p[i+1][j-1] == 1))
p[i][j] = 1;
}
}
int start = 0, end = 0;
for (int i = 0; i < n; ++i) {
for (int j = i+1; j < n; ++j) {
if (p[i][j] == 1 && j-i > end-start) {
end = j;
start = i;
}
}
}
return s.substring(start, end+1);
}
}