- 题目来源: https://leetcode.com/problems/longest-palindromic-substring/description/
- 题目: Longest Palindromic Substring
- 题目描述:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Examples:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Examples:
Input: “cbbd”
Output: “bb”
题目很简单,就是检索这个字符串的最长回文字符串并返回这个字符串,什么是回文,就是比如说”bab”反过来还是”bab”这个就是回文字符串,我是这么理解的,从题意也可以得到这个方面的内容,然后我给出我的思考的第一个方向的解答方案:
var longestPalindrome = function(s) {
var palindromicStr = '';
for(var i = 0; i< s.length; i++) {
for(var j = s.length; i < j; j--) {
var str = s.substring(i, j);
var palindromic = str.split('').reverse().join('');
if(str === palindromic) {
str.length > palindromicStr.length && (palindromicStr = str);
}
}
}
return palindromicStr;
};
最暴力的当然就是轮询遍历了,上去暴力穷举,将所有的回文都处理一遍,然后我就用了两个循环用substring这样前后两边一起循环,然后欣喜的提交我的代码,发现简单的字符串过了,一旦字符串长度达到999,直接超时挂了。所以这个方法走不通需要优化,想了一想我的这个方法的思路就是从每一个字符串开头,从后往回一个个减少字符串在去查询,这样由大到小,在由大到小的一个过程,性能确实很糊涂。
然后经过整整半天的思考,我还是没有想到更好的方法,翻了答案,看到答案中的第二个方法就是我的做法,但是这个方法是不可被接受的,然后我就开始研究第一个方法,第一个方法是先将这个字符串反转过后,求这两个字符串的最长公共子串,在来判断这个公共子串,如果不是,在继续往下计算,然后又到了计算“公共子串”的做法,这个LCS的计算方法又变成了动态规划的问题,深入研究了一天终于完成了这个公共子串的写法,上代码:
function longestPalindrome(s) {
var str1 = s,
str2 = s.split('').reverse().join('');
var len1 = str1.length,
len2 = str2.length;
var arr = new Array(len1);
var max = [];
for(var i = 0; i < len1; i++) {
for(var j = 0; j < len2; j++) {
if(j === 0) arr[i] = [];
if(str1.charAt(i) === str2.charAt(j)){
if(i ===0 || j ===0) {
arr[i][j] = 1;
} else {
arr[i][j] = arr[i-1][j-1] + 1;
}
} else {
arr[i][j] = 0;
}
if(arr[i][j] >= 2) {
max.push({
maxLen: arr[i][j],
maxPos: i
})
}
}
}
max = max.sort((a,b) => b.maxLen - a.maxLen);
for(var x = 0; x < max.length; x++) {
var str = str1.substr(max[x].maxPos - max[x].maxLen + 1, max[x].maxLen);
var Palindrome = str.split('').reverse().join('');
if(str === Palindrome) {
return str;
}
}
return '';
}
上面的方法已经可以实现这个答案的求解了,但是性能依旧爆炸,时间上依旧是超时,最终我还是看了其他答主的答案,找到了这个方案:
var longestPalindrome = function(s) {
var max = '';
for (var i = 0; i < s.length; i++) {
for (var j = 0; j < 2; j++) {
var left = i;
var right = i + j;
while (s[left] && s[left] === s[right]) {
left--;
right++;
}
if ((right - left - 1) > max.length) {
max = s.substring(left + 1, right);
}
}
}
return max;
};
上面这个答案从头开始遍历这个字符串,然后从这个字符的两边开始慢慢往外辐射找到结果,这个答案精简而迅速,确实没有想到这个想法。
动态规划
动态规划这个解法我也是想不到,并且研究了别人的思路三天左右,终于想明白了。具体思路就是假设要判断i到j是否是回文,就需要判断i+1到j-1是否是回文,那要判断这个就又有i+2到j-2之间是否是回文,所以当你判断到这一个是否是回文的时候只需要判断范围内的上一个字符串是否是回文即可,这样就可以省去了很多重复的计算,性能非常快。所以就有代码如下:
function longestPalindrome(s) {
if(s.length == 0)
{
return "";
}
if(s.length == 1)
{
return s;
}
dp = new Array();
var i,j;
for( i = 0; i < s.length; i++)
{
dp[i] = [];
for( j = 0; j < s.length; j++)
{
if(i >= j) {
dp[i][j] = true; //当i == j 的时候,只有一个字符的字符串; 当 i > j 认为是空串,也是回文
} else {
dp[i][j] = false; //其他情况都初始化成不是回文
}
}
}
console.log(dp)
var k;
var maxLen = 1;
var rf = 0, rt = 0;
for( k = 1; k < s.length; k++)
{
for( i = 0; k + i < s.length; i++)
{
j = i + k;
if(s.charAt(i) != s.charAt(j)) //对字符串 s[i....j] 如果 s[i] != s[j] 那么不是回文
{
dp[i][j] = false;
}
else //如果s[i] == s[j] 回文性质由 s[i+1][j-1] 决定
{
dp[i][j] = dp[i+1][j-1];
if(dp[i][j])
{
if(k + 1 > maxLen)
{
maxLen = k + 1;
rf = i;
rt = j;
}
}
}
}
}
return s.substring(rf, rt+1);
}
上面的代码引用于这里,前半部分的循环用于定义,i == j的情景就是只有一个字符的情景,那本身就一定等于本身,所以这个需要在最开始定义好,每一个回文判断就可以通过i+1到j-1一直辐射到最开始的i == j的情况就能判断出这个是否是最长回文字符串了,性能上比我自己的思路上是快速得非常多的,之所以研究了三天就是前半部分初始化的部分一直没有想明白,刚开始从LCS那种解法过来一直还沉浸在矩阵的方案里面没有想明白,所以画了好几个矩阵然后发现这个动态规划的这个方案,用矩阵想就一直想不明白,后来才理清楚了思路,这一点就比较重要,这个应该用普通的线图来解决这样的思维。
这样动态规划的思路就清楚了。