字符串
回文
回文是正着读、反着读都一样的字符串。
125-验证回文串(双指针或双向队列)
双指针:
class Solution {
public:
bool isPalindrome(string s) {
int n = s.size();
int i = 0;
int j = n - 1;
while (i < j) {
while (i < j && !isalnum(s[i])) ++i;
while (i < j && !isalnum(s[j])) --j;
if (tolower(s[i]) != tolower(s[j])) return false;
++i;
--j;
}
return true;
}
};
双向队列:
class Solution {
public:
bool isPalindrome(string s) {
int N = s.size();
deque<char> dq;
for (const char &c : s) {
if (isalnum(c)) {
dq.push_back(tolower(c));
}
}
while (dq.size() >= 2) {
if (dq.front() != dq.back())
return false;
dq.pop_front();
dq.pop_back();
}
return true;
}
};
5-最长回文子串(中心扩散法)
给定一个字符串,找到其中最长的回文子串。
对于回文子串问题,中心扩散法是更优的解法。长度为 N 的字符串 s[0...n-1] 一共有 2N-1 个中心扩散点。
class Solution {
public:
void expand(const string &s, int i, int j) {
if (i > j) return;
while (i >= 0 && j < s.size() && s[i] == s[j]) {
--i;
++j;
}
++i;
--j;
if (hi - lo < j - i) {
lo = i;
hi = j;
}
}
string longestPalindrome(const string &s) {
for (int i = 0; i < s.size(); ++i) {
expand(s, i, i);
expand(s, i, i + 1);
}
return s.substr(lo, hi - lo + 1);
}
private:
int lo = 0;
int hi = 0;
};
647-回文子串(中心扩散法)
给定一个字符串,计算这个字符串中有多少个回文子串。
对于回文子串问题,中心扩散法是更优的解法。
class Solution {
public:
void expand(const string &s, int i, int j) {
if (i > j) return;
while (i >= 0 && j < s.size() && s[i] == s[j]) {
++res;
--i;
++j;
}
}
int countSubstrings(const string &s) {
for (int i = 0; i < s.size(); ++i) {
expand(s, i, i);
expand(s, i, i + 1);
}
return res;
}
private:
int res = 0;
};
516-最长回文子序列(动态规划)
回文问题要抓住一个基本特征,就是中间向两边扩散。假设有字符串 s,我们如果已知 s[i...j] 的最长回文子序列的长度,那么当 i、j 分别向左右扩散时,我们就可以利用 dp[i][j] 的结果进行扩展,因此本题可以用动态规划求解。
假设字符串为 "abcbd",长度为 5,我们画一个 5x5 的表格,dp[i][j] 表示 s[i...j] 中的最长回文子序列的长度。很显然,这个表格只有当列数 > 行数时才有意义。
一、最优子结构:dp[i][j] 表示 s[i...j] 中的最长回文子序列的长度。
二、状态转移方程
- 如果 i == j,dp[i][j] = 1
- 如果 s[i] == s[j],则 dp[i][j] = dp[i+1][j-1] + 2
- 如果 s[i] != s[j],dp[i][j] = max(dp[i+1][j], dp[i][j-1])
dp[0][n-1]
表示 s[0..<n]
中的最长回文子序列长度,因此题解在表格的右上方,遍历方向是从下至上、从左至右。
class Solution {
public:
int longestPalindromeSubseq(const string &s) {
int n = s.size();
vector<vector<int>> dp(n, vector<int>(n));
for (int i = n - 1; i >= 0; --i) {
for (int j = i; j < n; ++j) {
if (i == j) {
dp[i][j] = 1;
} else 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][n-1];
}
};
子串问题(滑动窗口 + 哈希表)
3-无重复字符的最长子串
给定一个字符串,找出其中不含有重复字符的最长子串的长度。
子串问题的特征是,给定一个字符串,找到符合某种条件的子串。通用的解决办法是滑动窗口 + 哈希表。
需要一个哈希表记录每个字符出现的位置。
i 只能右移,不能左移,当发现了重复字符,i = max(i, 重复字符的位置 + 1),为什么?因为左移的话,刚才已经排除掉的其它重复字符又会包括进来。
class Solution {
public:
int lengthOfLongestSubstring(const string &s) {
int i = 0;
int j = 0;
int res = 0;
unordered_map<char, int> um;
while (j < s.size()) {
if (um.find(s[j]) != um.end()) {
i = max(i, um[s[j]] + 1); // 使 i...j 不包括重复字符
}
um[s[j]] = j;
res = max(res, j - i + 1);
++j;
}
return res;
}
};