day03 每日三题 回文系列
516. 最长回文子序列
给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
示例 1:
输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。
示例 2:
输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。
提示:
1 <= s.length <= 1000
s
仅由小写英文字母组成
知识点:动态规划
public static int solution(String s) {
int len = s.length(); // 获取字符串长度
int[][] dp = new int[len+1][len+1]; // 创建一个二维数组,用于存储子字符串的回文长度
char[] aa = s.toCharArray(); // 将字符串转换为字符数组便于操作
for (int i = len-1; i >= 0; i--) { // 从字符串末尾开始遍历
dp[i][i] = 1; // 单个字符视为回文,回文长度为1
for (int j = i+1; j < len; j++) { // 遍历当前字符之后的字符
if (aa[i]==aa[j]) { // 如果当前字符和后面的字符相等
dp[i][j] = dp[i+1][j-1]+2; // 回文长度增加2,等于去掉首尾字符的回文长度再加2
}else {
dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]); // 如果不相等,则取去掉首字符或去掉尾字符的最大回文长度
}
}
}
return dp[0][len-1]; // 返回整个字符串的最长回文子序列的长度
}
647. 回文子串
给你一个字符串 s
,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
1 <= s.length <= 1000
s
由小写英文字母组成
知识点: 中心扩展、动态规划
作者思路:其实出发点很简单,先分类讨论,回文串分别含有奇数长度和偶数长度。举个例子,当我们对奇数长度的回文子串进行分析时,我们定义双指针,左边指针设定为left,初始值为-1,右边指针设定为right,初始值为1,当满足左指针大于0时,右指针在小于字符串长度时,并且俩指针对应的字符相等时,将返回结果+1,以此类推。同意,偶数指针同理,只是初始位置不一样。
示例代码:
class Solution {
public int countSubstrings(String s) {
int ans = 0;
int len = s.length(); // 获取字符串长度
for (int i = 0; i < len; i++) { // 遍历字符串中的每个字符
ans += helper(s, len, i, i) + helper(s, len, i - 1, i);
}
return ans;
}
public static int helper(String s, int len, int left, int right) {
int ans = 0; // 记录回文子串数量
char[] aa = s.toCharArray(); // 将字符串转换为字符数组便于操作
while (left >= 0 && right < len && aa[left] == aa[right]) { // 向两侧扩展,判断是否构成回文
left--;
right++;
ans++;
}
return ans;
}
}
具体而言,在countSubstrings
函数中,首先初始化回文子串的数量ans
为0,并获取字符串的长度len
。然后,通过遍历字符串中的每个字符,调用helper
函数来计算以当前字符为中心或以当前字符和前一个字符为中心的回文子串数量,并将结果累加到ans
中。helper
函数通过使用双指针从中心向两侧扩展的方式,判断字符是否相等,形成回文子串。具体而言,初始化左指针left
为当前位置,右指针right
为当前位置(以当前字符为中心)或前一个位置(以当前字符和前一个字符为中心)。然后,循环判断left
和right
的位置是否合法,并且对应的字符是否相等,如果满足条件,则将left
向左移动一位,right
向右移动一位,同时增加回文子串的数量ans
。最后,返回回文子串的数量ans
。
该代码的时间复杂度为O(n^2),其中n为字符串的长度。
5. 最长回文子串
给你一个字符串 s
,找到 s
中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
知识点:中心扩展、动态规划
思路:通过遍历字符串中的每个字符,以当前字符为中心和以当前字符和前一个字符为中心,分别向两侧扩展判断是否形成回文。
具体而言,在longestPalindrome
函数中,首先初始化结果字符串ans
为空,并获取字符串的长度len
,将字符串转换为字符数组aa
。然后,判断特殊情况,如果字符串长度为0或者1,直接返回原字符串。接下来,通过遍历字符串中的每个字符,分别以当前字符为中心和以当前字符和前一个字符为中心,进行回文判断。在每次判断时,使用双指针left
和right
从中心向两侧扩展,判断字符是否相等,如果相等,则继续扩展;如果不相等,则停止扩展。同时,每次扩展后,更新最长回文子串ans
,将当前回文子串的长度与之前记录的最长回文子串的长度进行比较,如果当前回文子串更长,则更新ans
。最后,返回最长回文子串ans
。
时间复杂度为O(n^2),其中n为字符串的长度。
class Solution {
public String longestPalindrome(String s) {
String ans = ""; // 存储最长回文子串
int len = s.length(); // 获取字符串长度
char[] aa = s.toCharArray(); // 将字符串转换为字符数组便于操作
if (len == 0 || len == 1) return s; // 如果字符串为空或只有一个字符,直接返回原字符串
for (int i = 0; i < len; i++) { // 遍历字符串中的每个字符
int right;
int left = right = i;
while (left >= 0 && right < len && aa[left] == aa[right]) { // 向两侧扩展,判断是否构成回文
left--;
right++;
}
if (ans.length() < right - (left + 1)) ans = s.substring(left + 1, right); // 更新最长回文子串
left = i - 1;
right = i;
while (left >= 0 && right < len && aa[left] == aa[right]) { // 向两侧扩展,判断是否构成回文
left--;
right++;
}
if (ans.length() < right - (left + 1)) ans = s.substring(left + 1, right); // 更新最长回文子串
}
return ans;
}
}