题目要求 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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"
翻译过来就是说:在一个字符串中寻找最长的子字符串,该字符串是回数(即从左往右和从右往左读的结果是相同的)。该字符串的最大长度为1000
思路一:头指针+尾指针 遍历字符串,找到当前头指针的元素下一次出现时的下标,并且判断该子字符串是否是回数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public String longestPalindrome (String s) { StringBuilder result = new StringBuilder (); int resultLength = 0 ; StringBuilder temp = new StringBuilder (); for (int i = 0 ; i<s.length()-resultLength ; i++){ for (int j = s.lastIndexOf(s.charAt(i)) ; j>=i+resultLength ; j = s.substring(0 , j).lastIndexOf(s.charAt(i))){ temp = new StringBuilder (s.substring(i, j+1 )); if (temp.toString().equals(temp.reverse().toString())){ result = temp; resultLength = temp.length(); break ; } } } return result.toString(); }
在该方法中,已经对遍历进行了优化,尽可能减少了无效遍历,例如当长度一定小于当前结果的最大长度时,跳出当前循环并进入下一个遍历。但是仍然有很多无效的遍历,因此该答案最后还是超时的。
思路二:指针+最大长度 现在我们从回数的特点入手。 假若一个字符串是一个回数,那么该字符串内部一定还存在更多的回数。例如,abbbcbbba是一个回数,那么bbbcbbb一定是一个回数,那么bcb也是回数,最后到b。同理,bccb是一个回数,那么cc也是一个回数。因此可以看出,假设当前一个字符串是回数,那么加上两侧的字符可能还是回数。假设当前一个字符串不是回数,那么加上右侧的字符可能构成一个回数。因此,假设当前得到的回数的最大长度为n,我们可以判断n+1或者n+2是不是回数。 为什么这么判断呢?下面给出证明。 我们假设有一个字符串xxxxxxxxabaxxxxxxs,其中x代表任意字符。 假设此时指针指向s,而已知最大回数子字符串的长度为3。我们只需要判断xxxs以及xxxxs是不是回数。无需判断xxs乃至更近是因为它们的长度必然无法超过当前的最大长度。而无需判断xxxxxs乃至更远是因为假如xxxxxs是回数,那么xxxx一定是回数,则当前的最大长度为4而不是3,与题设不符。所以只需判断两种情况。 这里就充分利用了回数的性质,省去了很多无效的遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public String longestPalindrome2 (String s) { StringBuilder result = new StringBuilder (); int curLength = 0 ; for (int i = 0 ; i<s.length() ; i++){ if (isPalindromic(s, i-curLength-1 , i)){ result = new StringBuilder (s.substring(i-curLength-1 , i+1 )); curLength += 2 ; }else if (isPalindromic(s, i-curLength, i)){ result = new StringBuilder (s.substring(i-curLength, i+1 )); curLength += 1 ; } } return result.toString(); } public boolean isPalindromic (String s, int start, int end) { if (start<0 ){ return false ; } while (start<end){ if (s.charAt(start++)!=s.charAt(end--)) return false ; } return true ; }
思路三:由中间至两边找回数 思路三就像是思路一的彻底反转。思路一优先寻找回数的边缘,而思路三则直接从中间开始寻找,直至找到最远的边缘。 正如上面所说,回数的中间可能是一个单值,如aba中的b;也可能是双值,如abba中的bb。 我们可以对当前下标上的单值双值都进行尝试 以下是我在leetcode上找到的一个实现(88%)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Solution {private int lo, maxLen;public String longestPalindrome (String s) { int len = s.length(); if (len < 2 ) return s; for (int i = 0 ; i < len-1 ; i++) { extendPalindrome(s, i, i); extendPalindrome(s, i, i+1 ); } return s.substring(lo, lo + maxLen); } private void extendPalindrome (String s, int j, int k) { while (j >= 0 && k < s.length() && s.charAt(j) == s.charAt(k)) { j--; k++; } if (maxLen < k - j - 1 ) { lo = j + 1 ; maxLen = k - j - 1 ; } }}