1. question: 回文子串(中等)
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/palindromic-substrings
示例 1:
1 2 3
| 输入:s = "abc" 输出:3 解释:三个回文子串: "a", "b", "c"
|
示例 2:
1 2 3
| 输入:s = "aaa" 输出:6 解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
|
提示:
1 2
| 1 <= s.length <= 1000 s 由小写英文字母组成
|
2. answers
这道题最开始我自己以接近超时的方式做出来了。动态规划+双指针法判断回文串。
首先以首尾双指针法判断回文串,这没什么可说的,直接做就行。其次是动态规划,定义数组dp[i]表示字符串s[0:i]所能形成的回文串的数量,那么s[0:i+1]和前面有什么关系吗?答案是有的。
- 字符串s[0:i+1]所能形成的回文串一定是s[0:i]和s[i+1],以及s[x:i+1]所形成的回文串总和。
- 其中s[0:i]所形成的回文串数量已经有前面得到,s[i+1]就是一个字符,显然回文串数量为1。
- 那么s[x:i+1]实际上就是以s[i+1]为结尾的这i个字符串是否是回文串就行。
代码如下所示:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class Solution_0145 {
public boolean isSubstrings(String s) { int start = 0; int end = s.length() - 1;
while(start <= end) { if(s.charAt(start) != s.charAt(end)) return false;
start++; end--; }
return true; }
public int countSubstrings(String s) {
int[] dp = new int[s.length()];
dp[0] = 1;
for (int i = 1; i < s.length(); i++) {
dp[i] = dp[i-1] + 1; for (int j = 0; j < i; j++) { if(isSubstrings(s.substring(j, i + 1))) dp[i] += 1; }
}
return dp[s.length() - 1]; }
public static void main(String[] args) {
Solution_0145 s0145 = new Solution_0145();
String s = "aaa";
System.out.println(s0145.countSubstrings(s)); } }
|
上面的时间复杂度接近O(n^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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
public class Solution_0145_02 {
public int countSubstrings(String s) {
boolean[][] dp = new boolean[s.length()][s.length()]; int result = 0;
for (int i = 0; i < s.length(); i++) { for (int j = 0; j < s.length(); j++) { if(i == j) { dp[i][j] = true; result++; } } }
for (int i = s.length() - 2; i >= 0 ; i--) {
for (int j = i + 1; j < s.length(); j++) {
if(s.charAt(i) == s.charAt(j)) { if(j - i <= 1) { dp[i][j] = true; result++; } else { dp[i][j] = dp[i+1][j-1]; if(dp[i][j]) result++; } }
} }
return result; }
public static void main(String[] args) { System.out.println();
Solution_0145_02 s0145_02 = new Solution_0145_02();
String s = "aaa";
System.out.println(s0145_02.countSubstrings(s));
} }
|
除了上面的动态规划,其实可以采用中心扩散双指针法。代码如下所示:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
public class Solution_0145_03 {
public boolean isSubstrings(String s) { int start = 0; int end = s.length() - 1;
while(start <= end) { if(s.charAt(start) != s.charAt(end)) return false;
start++; end--; }
return true; }
public int countSubstrings(String s) {
if(s.length() == 1) return 1;
int subRange = 0; int result = 0;
for (int i = 0; i < s.length(); i++) {
subRange = Math.min(i, s.length() - 1 - i); while(subRange >= 0) { if(isSubstrings(s.substring(i-subRange, i+subRange + 1))) result++; subRange--; }
subRange = Math.min(i, s.length() - 1 - (i + 1)); while(subRange >= 0) { if(isSubstrings(s.substring(i-subRange, (i+1)+subRange + 1))) result++; subRange--; } }
return result; }
public static void main(String[] args) { System.out.println(); Solution_0145_03 s0145 = new Solution_0145_03();
String s = "ab";
System.out.println(s0145.countSubstrings(s)); } }
|
3. 备注
参考力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 (leetcode-cn.com),代码随想录 (programmercarl.com)。