Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目:给定一个字符串s,请统计并返回这个字符串中回文子串的个数。
解题思路
一个字符串的所有回文子串的个数,那么我们是否可以先获取字符串的所有子串,之后挨个判断是否为回文子串即可,可得代码如下:
public int countSubStrings(String s){
int count = 0;
for(int i=0;i<s.length();i++){
StringBuilder sb = new StringBuilder();
for(int j=i;j<s.length();j++){
sb.append(s.charAt(j));
StringBuilder st = new StringBuilder(sb.toString());
if(sb.toString().contentEquals(st.reverse())) count++;
}
}
return count;
}
时间复杂度为,方法是对的,下次别用了,哈哈哈哈。
换个思路,我们直接来看一个回文子串abba,可以看到0索引元素等于3索引,之后我们再看1索引元素等于2索引,经过这两步可判断0到3的字符串为一个回文串,那么根据这个思路:
我们可以首先双重for循环遍历所有可能的子串,而对于索引i到索引j的子串,首先判断i和j位置元素是否相等,不相等则必定不可能是回文串,相等则看i和j中间有几个元素,有一个则也是回文串,如果大于一个则再看i+1和j-1的子串是否为回文串,依次类推即可。
上述过程显然可以用动态规划解决,因为在计算dp[i][j](指由i到j组成的字符串)时,dp[i+1][j-1]必然已经计算完毕了,而要保证这点,则for循环的外层循环控制的是结束点,也即j,内层控制的是i,可得代码如下:
public int countSubStrings2(String s){
int count = 0, len = s.length();
boolean[][] dp = new boolean[len][len];
for(int i=0;i<len;i++){
for(int j=0;j<=i;j++){
if(s.charAt(i) == s.charAt(j)){
if(i-j<3){
dp[j][i] = true;
}else {
dp[j][i] = dp[j+1][i-1];
}
}else {
dp[j][i] = false;
}
}
}
for(int i=0;i<len;i++) {
for (int j = 0; j < len; j++) {
if (dp[i][j]){count++;
System.out.println(i+""+j);}
}
}
return count;
}
该方法时间复杂度为。看了题解,发现还有一种比较巧妙的思路,中心扩散法。
该方法首先判断所有可能的回文中心点,对于一个长度为n的字符串,其回文中心点有2*n-1个,其中单字符回文中心n个,双字符回文中心n-1个,这比较容易理解。但回文中心的左边界和右边界不好确定,由分析可知,单字符回文中心左边界为0,右边界也为0。而双字符的回文中心点其右边界应该是1。由这一规律,可知左边界索引为i/2下取整,右边界则i/2 + i%2。最终可得代码:
public int countSubStrings3(String s){
int count = 0;
for(int i=0;i<2*s.length()-1;i++){
int left = i/2;
int right = left + i%2;
while(left>=0&&right<s.length()&&s.charAt(left)==s.charAt(right)){
left--;
right++;
count++;
}
}
return count;
}
时间复杂度也是。