题目链接:https://leetcode-cn.com/problems/palindromic-substrings/description/
这题基于动态规划的思想来解答的话,实现思想参考自 :http://www.bkjia.com/Cyy/988261.html
借助一个辅助二维数组 dp[i][j]
,其中 i
和 j
一起作用,表示字符串 s 中下标从 i
到 j
的子串的回文子串的个数,且对于矩阵 dp
,只考虑包含对角线的右上部分(即 j >= i
的情况),因为 dp[i][j]
和 dp[j][i]
在意义上是一样的(因此,考虑左下部分也是一样的,只是具体的代码逻辑也要做相应的调整)。
于是对于 dp[i][j]
就会有如下公式(特殊情况暂不考虑):
dp[i][j] = dp[i+1][j] + dp[i][j-1] - dp[i+1][j-1];
子串 s.sub(i,j)
,可以看作是子串 s.sub(i+1,j-1)
和两个边缘字符 s(i)
与 s(j)
的组合:
对于 dp[i+1][j]
,表示子串 s.sub(i+1,j-1)
与左边的字符 s(i)
组合的新的字符串的回文子串个数,其值是以 dp[i+1][j-1]
为基础的;对于 dp[i][j-1]
同理。
但是,需要注意,因为 dp[i+1][j]
和 dp[i][j-1]
都包含了一次 dp[i+1][j-1]
的值,重复了一次,因此需要减去一次。
根据上述公式,可以知道大范围的字符串的 dp 值(即 dp[i][j]
)需要根据小范围的字符串的 dp 值(dp[i+1][j]、dp[i][j-1]、dp[i+1][j-1]
)来计算,如果是以矩阵右上角部分去计算,则 dp 矩阵的计算顺序如下图:
画圈的为对应的 dp[i][j]
的值,折线表示计算的顺序。最终的结果即为 dp[0][s.length() - 1]
的值,即上图的 dp[0][4]
。
然后就是要考虑到特殊的情况了:
(1)当 j == i
时,即单个字符,dp[i][j]
为 1;
(2)当 j - i == 1
时,即子串长度为 2(s(i)
与 s(j)
两两相邻),此时 dp[i][j]
至少为 2,而且当 s(i) == s(j)
时,就为 3。
(3)当 j - i > 1
时,此时如果 s(i) == s(j)
且 s.sub(i+1,j-1)
为回文串,则还需要在上述公式的基础上 +1
,因为此时子串 s.sub(i,j)
也是一个回文串,该情况也需要考虑到。(这里需要特别注意,不能仅仅是判断 s(i)
与 s(j)
是否相等而不考虑 s.sub(i+1,j-1)
是否为回文串,因为 “s.sub(i+1,j-1)
为回文串“ 是前提条件,举例测试数据 "fdsklf"
)
public class PalindromicSubstrings {
public static int countSubstrings(String s) {
if (s == null) return 0;
int len = s.length();
int[][] dp = new int[len][len];
for (int i = 0; i < len; i++) {
dp[i][i] = 1;
}
//用一个 Map 保存判断过的是否回文的子串,提高效率
Map map = new HashMap<>();
for (int i = len - 2; i >= 0; i--) {
for (int j = i + 1; j < len; j++) {
if (j - i == 1) {
dp[i][j] = 2;
if (s.charAt(i)==s.charAt(j)) dp[i][j]++;
} else {
dp[i][j]=dp[i+1][j]+dp[i][j-1]-dp[i+1][j-1];
//注意 String 的 substring() 函数的结束边界值的特殊性
if (s.charAt(i) == s.charAt(j) && isPalindromic(s.substring(i + 1, j ),map)) {
dp[i][j] ++;
}
}
}
}
return dp[0][len - 1];
}
/**
* 判断是否为回文字符串
*/
private static boolean isPalindromic(String s, Map map) {
if (map.containsKey(s)) return map.get(s);
int t = s.length() /2;
int p1 = t-1;
int p2;
if (s.length()%2==0) {
p2 = t;
} else {
p2 = t+1;
}
while (p1>=0&&p2if (s.charAt(p1--)!=s.charAt(p2++)) {
map.put(s, false);
return false;
}
}
map.put(s, true);
return true;
}
public static void main(String[] args) {
// System.out.println(countSubstrings("fdsklf"));
System.out.println(countSubstrings("longtimenosee"));
}
}
参考自我朋友的想法
大致思想也与上面的类型,只不过这里辅助数组 map[i][j]
是用来判断从 i
到 j
的子串是不是回文串
public static int countSubstrings(String s) {
int length = s.length();
boolean[][] map = new boolean[length][length];
//单个字符也算
int num = length;
// 单个字符
for (int i = 0; i < length; i++) {
map[i][i] = true;
}
//从第0个字符开始,两个两两相邻的字符组成的字符串
//如果两个字符相等,则为回文串
for (int j = 0; j + 1 < length; j++) {
if (s.charAt(j + 1) == s.charAt(j)) {
map[j][j + 1] = true;
num++;
}
}
//从第0个字符开始,三个及以上连续的字符组成的字符串
//如果 s.sub(j,j+i) 除去两边的字符之后的子串(即 s.sub(j+1,j+i-1))为回文串且除去的两个字符相等(即 s(j)==s(j+1))
for (int i = 2; i < length; i++) {
for (int j = 0; j + i < length; j++) {
if (map[j + 1][j + i - 1] && s.charAt(j + i) == s.charAt(j)) {
map[j][j + i] = true;
num++;
}
}
}
return num;
}
参考自 https://leetcode-cn.com/submissions/detail/1031526/ 7ms 用时的范例:
public static int countSubstrings2(String s) {
int result = 0;
if ( s == null){
return result;
}
int length = s.length();
for(int i =0; i < length;i++){
result += count(s,i,i);//以 s(i) 为中心向两边扩散
result += count(s,i,i+1); //以 s(i) 与 s(i+1) 为中心向两边扩散
}
return result;
}
public static int count(String s,int beg,int end){
int count = 0;
while( beg >=0 && end < s.length()&& s.charAt(beg) == s.charAt(end)){
count++;
beg--;
end++;
}
return count;
}