中等
给你一个字符串 s
,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
算法思路推荐看代码随想录(代码随想录)
使用动态规划方法:
- 时间复杂度:O(n^2)
- 空间复杂度:O(n^2)
dp[i][j]表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如果是dp[i][j]为true,否则为false。
初始化:
所有dp[i][j]初始化为false
遍历顺序:
i为子串左下标,j为右下标。情况三是根据dp[i + 1][j - 1]是否为true,在对dp[i][j]进行赋值true的。dp[i + 1][j - 1] 在 dp[i][j]的左下角,为保障i+1能取到值,所以i从大到小遍历,而j为子串的右下标,j必须大于等于i。j从i开始从小到大遍历。遍历顺序如下:
class Solution {
public int countSubstrings(String s) {
char[] chars = s.toCharArray();
int len = chars.length;
int res = 0;
//布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串
//如果是dp[i][j]为true,否则为false
boolean[][] dp = new boolean[len][len];
for(int i=len-1;i>=0;i--){
for(int j=i;j<len;j++){
if(chars[i] == chars[j]){
//情况一:下标i 与 j相同,同一个字符例如a,当然是回文子串
//情况二:下标i 与 j相差为1,例如aa,也是回文子串
if(j-i<=1){
res++;
dp[i][j] = true;
//情况三:下标:i 与 j相差大于1的时候,
//例如cabac,此时s[i]与s[j]已经相同了,我们看i到j区间是不是回文子串就看aba是不是回文
}else if(dp[i+1][j-1]){
res++;
dp[i][j] = true;
}
}
}
}
return res;
}
}
使用中心扩散法:
这个方法的时间复杂度是 O(n^2),空间复杂度是 O(1)。
中心点即 left 指针和 right 指针初始化指向的地方,可能是一个也可能是两个
举例:
aba 有5个中心点,分别是 a、b、c、ab、ba
abba 有7个中心点,分别是 a、b、b、a、ab、bb、ba
class Solution {
public int countSubstrings(String s) {
//中心扩散法
int res = 0;
//遍历所有中心点
for(int center = 0; center < 2*s.length()-1;center++){
int left = center / 2;
int right = left + center%2;
//当左右下标指向的字符相同,左下标左移,右下标右移
while(left>=0 && right<s.length() && s.charAt(left) == s.charAt(right)){
res++;
left--;
right++;
}
}
return res;
}
}
中等
给你一个字符串 s
,找到 s
中最长的 回文子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
使用中心扩散法:
时间复杂度:O(n ^2),其中 n 是字符串的长度。长度为 1 和 2 的回文中心分别有 n 和 n−1 个,每个回文中心最多会向外扩展 O(n) 次。
空间复杂度:O(1)
class Solution {
public String longestPalindrome(String s) {
int left = 0, right = 0;
String res = "";
char[] ch = s.toCharArray();
for(int i=0;i<ch.length*2;i++){
left = i/2;
right = left+i%2;
while(left>=0 && right<ch.length && ch[left] == ch[right]){
if(right-left+1>res.length()){
res = s.substring(left, right+1);
}
left--;
right++;
}
}
return res;
}
}
中等
给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab" 输出:4 解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd" 输出:2 解释:一个可能的最长回文子序列为 "bb" 。
动态规划:
dp数组的含义:
dp[i][j]:字符串s在[i, j]范围内最长的回文子序列的长度为dp[i][j]。
递推公式:
如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子序列的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。
加入s[j]的回文子序列长度为dp[i + 1][j]。
加入s[i]的回文子序列长度为dp[i][j - 1]。
那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
举例:当i=0, j=2,即字符串cbb时,最长回文子序列为bb,即dp[1][2].
初始化:
首先要考虑当i 和j 相同的情况,从递推公式:dp[i][j] = dp[i + 1][j - 1] + 2; 可以看出 递推公式是计算不到 i 和j相同时候的情况。
所以需要手动初始化一下,当i与j相同,那么dp[i][j]一定是等于1的,即:一个字符的回文子序列长度就是1。
遍历顺序:从下到上,即i从大到小,并且j>i,所以j从i+1开始,从小到大
class Solution {
public int longestPalindromeSubseq(String s) {
char[] ch = s.toCharArray();
int[][] dp = new int[ch.length][ch.length];
for(int i=0;i<ch.length;i++){
dp[i][i] = 1;
}
for(int i=ch.length-1;i>=0;i--){
for(int j=i+1;j<ch.length;j++){
if(ch[i] == ch[j]){
dp[i][j] = dp[i+1][j-1]+2;
}else{
dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][ch.length-1];
}
}
简单
给定一个包含大写字母和小写字母的字符串 s
,返回 通过这些字母构造成的 最长的 回文串
的长度。
在构造过程中,请注意 区分大小写 。比如 "Aa"
不能当做一个回文字符串
思路:
对于每个字符 ch,假设它出现了 v 次,我们可以使用该字符 v / 2 * 2 次
举例:v=5,v/2=2.5,取int值即为2,所以 5 / 2 * 2=4
如果有任何一个字符 ch 的出现次数 v 为奇数(即 v % 2 == 1),那么可以将这个字符作为回文中心,注意只能最多有一个字符作为回文中心。
在代码中,我们用 ans 存储回文串的长度,由于在遍历字符时,ans 每次会增加 v / 2 * 2,因此 ans 一直为偶数。
但在发现了第一个出现次数为奇数的字符后,我们将 ans 增加 1,这样 ans 变为奇数,在后面发现其它出现奇数次的字符时,我们就不改变 ans 的值了。
class Solution {
public int longestPalindrome(String s) {
//字符的 ASCII 值的范围为 [0, 128)
int[] count = new int[128];
int length = s.length();
for (int i = 0; i < length; ++i) {
char c = s.charAt(i);
count[c]++;
}
int ans = 0;
for (int v: count) {
ans += v / 2 * 2;
if (v % 2 == 1 && ans % 2 == 0) {
ans++;
}
}
return ans;
}
}