描述
给定一个包含大写字母和小写字母的字符串 s ,返回 通过这些字母构造成的 最长的回文串 。
在构造过程中,请注意 区分大小写 。比如 “Aa” 不能当做一个回文字符串。
示例
输入: s = “abccccdd” 输出: 7 解释: 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
题解
1. 贪心 + 哈希表
哈希表记录每个字母出现的个数,如果是双数,答案就加本身,如果是单数,答案就加本身减一,并且可以放一个字母在字符串正中间,所以最后答案加一。
- 时间复杂度:
O
(
N
)
O(N)
O(N),其中 N 为字符串 s 的长度。我们需要遍历每个字符一次。
- 空间复杂度:
O
(
S
)
O(S)
O(S),其中 S 为字符集大小。题目中保证了给定的字符串 s 只包含大小写字母,因此我们也可以使用哈希映射(HashMap)来存储每个字符出现的次数,例如 Python 和 C++ 的代码。如果使用哈希映射,最多只会存储 52 个(即小写字母与大写字母的数量之和)键值对。
class Solution {
public:
int longestPalindrome(string str) {
int ans = 0, flag = 0;
unordered_map<char, int> hash;
for(char ch : str){
++hash[ch];
}
for(auto pairs : hash){
if(flag == 0 && pairs.second % 2 != 0){
flag = 1;
}
ans += pairs.second % 2 == 0? pairs.second : pairs.second-1;
}
ans += flag;
return ans;
}
};
给你一个字符串 s,找到 s 中最长的回文子串。 – 输入:s = “babad” 输出:“bab” 解释:“aba” 同样是符合题意的答案。
动态规划思路与647. 回文子串 ●●类似;
- dp[i][j] 表示 [i, j] 范围内的子串是否为回文串
- 遍历过程有三种情况,当长度为最大时更新返回字符串 ans;
1)只有一个字符,属于回文串 2)s[i] != s[j],非回文串 3)s[i] == s[j],判断中间部分 [i+1, j-1] 是否为回文串(只有两个字符时单独讨论) - dp 初始化为 false
- dp[i][j] 可能要根据 dp[i+1, j-1] 进行判断,因此外层起始位置 i 从后往前遍历,内层终止位置 j 从前往后遍历
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2) 空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
class Solution {
public:
string longestPalindrome(string s) {
int len = s.length();
vector<vector<bool>> dp(len, vector<bool>(len, false));
int maxL = 1;
string ans(1, s[0]);
for(int i = len-1; i >= 0; --i){
dp[i][i] = true;
for(int j = i+1; j < len; ++j){
if(s[i] == s[j]){
if(j - i == 1){
dp[i][j] = true;
if(maxL < 2){
maxL = 2;
ans = string(s.begin() + i, s.begin() + j + 1);
}
}else if(dp[i+1][j-1]){
dp[i][j] = true;
if(maxL < j - i + 1){
maxL = j - i + 1;
ans = string(s.begin() + i, s.begin() + j + 1);
}
}
}
}
}
return ans;
}
};
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。 回文字符串 是正着读和倒过来读一样的字符串。 子字符串 是字符串中的由连续字符组成的一个序列。 具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 – 输入:s = “aaa” 输出:6 解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”
1. 暴力遍历
两层 for 循环,遍历区间起始位置和终止位置,然后判断这个区间是不是回文。
时间复杂度:
O
(
n
3
)
O(n^3)
O(n3)
class Solution {
public:
bool isValid(string s, int start, int end){
for(int i = 0; i <= (end - start) / 2; ++i){
if(s[start + i] != s[end-i]) return false;
}
return true;
}
int countSubstrings(string s) {
int len = s.length();
int ans = 0;
for(int i = 0; i < len; ++i){
++ans;
for(int j = i+1; j < len; ++j){
if(isValid(s, i, j)) ++ans;
}
}
return ans;
}
};
2. DP
- dp[i][j] 表示 [i, j] 范围内的子串是否为回文串
- 遍历过程有三种情况:
1)只有一个字符,属于回文串 2)s[i] != s[j],非回文串 3)s[i] == s[j],判断中间部分 [i+1, j-1] 是否为回文串(只有两个字符时单独讨论) - dp 初始化为 false
- dp[i][j] 可能要根据 dp[i+1, j-1] 进行判断,因此外层起始位置 i 从后往前遍历,内层终止位置 j 从前往后遍历
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2) 空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
class Solution {
public:
int countSubstrings(string s) {
int len = s.length();
int ans = 0;
vector<vector<bool>> dp(len, vector<bool>(len, false));
for(int i = len-1; i >= 0; --i){
dp[i][i] = true;
++ans;
for(int j = i+1; j < len; ++j){
if(s[i] == s[j]){
if(j - i == 1){
dp[i][j] = true;
++ans;
}else if(dp[i+1][j-1]){
dp[i][j] = true;
++ans;
}
}
}
}
return ans;
}
};
3. 双指针(中心扩展)
枚举所有的中心点,然后用两个指针分别向左右两边拓展,当两个指针指向的元素相同的时候就拓展,否则停止拓展。
对一个字符串 ababa ,选择最中间的 a 作为中心点,往两边扩散,第一次扩散发现 left 指向的是 b ,right 指向的也是 b ,所以是回文串,继续扩散,同理 ababa 也是回文串。
如何有序地枚举所有可能的回文中心,我们需要考虑回文长度是奇数和回文长度是偶数的两种情况。如果回文长度是奇数,那么回文中心是一个字符;如果回文长度是偶数,那么中心是两个字符;所以最终的中心点有 2 * len - 1 个,分别是 len 个单字符和 len - 1 个双字符。
- 时间复杂度:
O
(
n
2
)
O(n^2)
O(n2),枚举回文中心的是
O
(
n
)
O(n)
O(n) 的,对于每个回文中心拓展的次数也是
O
(
n
)
O(n)
O(n)。
- 空间复杂度:
O
(
1
)
O(1)
O(1)
单个字符和两个字符单独计算:
class Solution {
public:
int countSubstrings(string s) {
int ans = 0;
for(int i = 0; i < s.length(); ++i){
ans += centerCount(s, i, i);
ans += centerCount(s, i, i+1);
}
return ans;
}
int centerCount(string s, int left, int right){
int ans = 0;
while(left >= 0 && right < s.length() && s[left] == s[right]){
--left;
++right;
++ans;
}
return ans;
}
};
字符串中心合并计算:
遍历2 * len - 1 个中心点,left = i / 2 ;right = left + i %2 ;
class Solution {
public:
int countSubstrings(string s) {
int ans = 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[left] == s[right]){
--left;
++right;
++ans;
}
}
return ans;
}
};
4. Manacher 算法
描述
给定一个仅包含小写字母的字符串,求它的最长回文子串的长度。
所谓回文串,指左右对称的字符串。 所谓子串,指一个字符串删掉其部分前缀和后缀(也可以不删)的字符串
数据范围:字符串长度
1
≤
s
≤
350
1\le s\le 350
1≤s≤350
进阶:时间复杂度:O(n)\O(n) ,空间复杂度:O(n)\O(n)
示例
输入: cdabbacc 输出: 4 解释: abba为最长的回文子串
题解
ACM 模式(中心扩散 & 动态规划)
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
int from_center(string &str, int start, int end){
if(str[start] != str[end]){
return 1;
}
int ans = end - start + 1;
int l = start - 1, r = end + 1;
while(l >= 0 && r < str.length()){
if(str[l--] != str[r++]) break;
ans += 2;
}
return ans;
}
int main(){
string str;
while(cin >> str){
int ans = 1, n = str.length();
vector<vector<bool>> dp(n, vector<bool>(n, false));
for(int i = n-1; i >= 0; --i){
dp[i][i] = true;
for(int j = i+1; j < n; ++j){
if(str[i] != str[j]) break;
int len = j-i+1;
if(len == 2){
dp[i][j] = true;
ans = max(ans, len);
}else if(dp[i+1][j-1]){
dp[i][j] = true;
ans = max(ans, len);
}
}
}
cout << ans << endl;
}
return 0;
}
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。 子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。 – 输入:s = “bbbab” 输出:4 解释:一个可能的最长回文子序列为 “bbbb” 。
回文子串是要连续的,回文子序列可不是连续的!
dp[i][j] 表示 [i, j] 范围内的子串中最长回文子序列数- 遍历过程有两种情况:
1)s[i] == s[j],则中间部分 [i+1, j-1] 回文子序列长度 + 2 dp[i][j] = dp[i+1][j-1] + 2; 2)s[i] != s[j],选择 去首 或 去尾 的子串中最长的回文子序列长度。 dp[i][j] = max(dp[i+1][j], dp[i][j-1]); - dp 初始化为 0,dp[i][i] 为 1
- dp[i][j] 可能要根据 dp[i+1, j-1] 进行判断,因此外层起始位置 i 从后往前遍历,内层终止位置 j 从前往后遍历
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2) 空间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
class Solution {
public:
int longestPalindromeSubseq(string s) {
int len = s.length();
vector<vector<int>> dp(len, vector<int>(len, 0));
for(int i = len - 1; i >= 0; --i){
dp[i][i] = 1;
for(int j = i+1; j < len; ++j){
if(s[i] == s[j]){
dp[i][j] = dp[i+1][j-1] + 2;
}else{
dp[i][j] = max(dp[i+1][j], dp[i][j-1]);
}
}
}
return dp[0][len-1];
}
};
|