目录
回文子串
最长回文子串
// DP O(n^2)
string longestPalindrome(string s) {
int n = s.size();
if (n < 2) return s;
int maxLen = 1, begin = 0;
vector<vector<int>> dp(n, vector<int>(n));
for (int i = 0; i < n; i++)
dp[i][i] = true;
for (int L = 2; L <= n; L++) {
for (int i = 0; i < n; i++) {
int j = L+i-1;
if (j >= n) break;
if (s[i] != s[j])
dp[i][j] = false;
else {
if (j-i < 3) dp[i][j] = true;
else dp[i][j] = dp[i+1][j-1];
}
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j-i+1;
begin = i;
}
}
}
return s.substr(begin, maxLen);
}
// Manacher O(n)
int expand(string s, int left, int right) {
while(left >= 0 && right < s.size() && s[left] == s[right]) {
--left; ++right;
}
return (right-left-2) / 2;
}
string manacher(string s) {
if(s.empty() || s.size() < 2) return s;
string tmp = "#";
for(char c : s) {
tmp += c;
tmp += "#";
}
tmp += "#";
int n = tmp.size(), right = 0, center = 0, start = 0, maxLen = 0;
vector<int> p(n, 0);
for(int i = 0; i < n; ++i) {
if(right >= i) {
int minArmLen = min(right-i, p[2*center-i]);
p[i] = expand(tmp, i-minArmLen, i+minArmLen);
}
else p[i] = expand(tmp, i, i);
if(i + p[i] > right) {
center = i;
right = i + p[i];
}
if(p[i] > maxLen) {
maxLen = p[i];
start = (i-p[i]) / 2;
}
}
return s.substr(start, maxLen);
}
string longestPalindrome(string s) {
return manacher(s);
}
字符串匹配
字符串匹配kmp
void getnext(string t, vector<int> &nxt) {
for(int i = 1; i < t.length(); ++i) {
int j = nxt[i];
while(j && t[i] != t[j])
j = nxt[j];
nxt[i+1] = (t[i]==t[j]) ? j+1 : 0;
}
}
// 返回子串t在s串中出现的次数,若仅判断是否包含,在最后if里返回即可
int kmp(string s, string t) {
vector<int> nxt(t.length()+1);
getnext(t, nxt);
int cnt = 0, j = 0;
for(int i = 0; i < s.length(); ++i) {
while(j && s[i] != t[j])
j = nxt[j];
if (s[i] == t[j])
++j;
if (j == t.length()) {
++cnt;
j = nxt[j];
}
}
return cnt;
}
// 判断由几段相同的子串连接而成
int getLoopKnot(string s, vector<int> &nxt) {
int len = s.length();
int x = len-nxt[len];
if (len % x == 0) return len/x;
return 1;
}
通配符匹配
‘?’ 可以匹配任何单个字符
‘*’ 可以匹配任意字符串(包括空字符串)
bool isMatch(string s, string p) {
int m = s.size();
int n = p.size();
vector<vector<int>> dp(m+1, vector<int>(n+1));
dp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
if (p[i-1] == '*')
dp[0][i] = 1;
else break;
}
for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (p[j-1] == '*') {
dp[i][j] = dp[i][j-1] | dp[i-1][j];
}
else if (p[j-1] == '?' || s[i-1] == p[j-1]) {
dp[i][j] = dp[i-1][j-1];
}
}
}
return dp[m][n];
}
正则表达式匹配
根据正则表达式匹配原则,判断p串能否表示成s串
‘.’ 匹配任意单个字符
‘*’ 匹配零个或多个前面的那一个元素
bool isMatch(string s, string p) {
int n = s.length(), m = p.length();
// dp[i][j] 表示s串前i个字符与p串前j个字符是否匹配
vector<vector<int>> dp(n+1, vector<int>(m+1));
dp[0][0] = 1;
for (int i = 0; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
if (p[j-1] == '*') {
// *前面的字符一个都不要
dp[i][j] |= dp[i][j-2];
// 如果*前面的字符要,则p[j-2]和s[i-1]必须匹配
// 匹配: 两字符相同 或 p[j-2]为.
if (i && (p[j-2] == s[i-1] || p[j-2] == '.'))
dp[i][j] |= dp[i-1][j];
}
else {
if (i && (p[j-1] == '.' || s[i-1] == p[j-1]))
dp[i][j] |= dp[i-1][j-1];
}
}
}
return dp[n][m];
}
杂题
最长公共子串
题意:给定两个字符串,问其中最长的公共子串
int longestCommonSubsequence(string text1, string text2) {
int n = text1.length(), m = text2.length();
vector<vector<int>> dp(n+1, vector<int>(m+1));
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (text1[i] == text2[j]) dp[i+1][j+1] = dp[i][j]+1;
else dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);
}
}
return dp[n][m];
}
// 滚动数组 空间优化
int longestCommonSubsequence(string text1, string text2) {
int n = text1.length(), m = text2.length();
vector<vector<int>> dp(2, vector<int>(m+1));
for (int i = 1; i <= n; i++) {
int r = i % 2;
for (int j = 1; j <= m; j++) {
if (text1[i-1] == text2[j-1]) dp[r][j] = dp[1-r][j-1] + 1;
else dp[r][j] = max(dp[1-r][j], dp[r][j-1]);
}
}
return dp[n%2][m];
}
括号是否有效
给出一串由括号组成的字符串,判断是否有效
bool isValid(string s) {
stack<char> a;
for (int i = 0; i < s.length(); ++i) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{')
a.push(s[i]);
else {
if (a.empty()) return false;
if (s[i] == ')' && a.top() == '(') a.pop();
else if (s[i] == ']' && a.top() == '[') a.pop();
else if (s[i] == '}' && a.top() == '{') a.pop();
else return false;
}
}
return a.empty();
}
最长有效括号
给出一串只有‘( ’ 和 ‘ )’组成的字符串,求其中最长的有效括号的长度
// DP
int longestValidParentheses(string s) {
int ret = 0, n = s.length();
vector<int> dp(n, 0);
for (int i = 1; i < n; i++) {
if (s[i] == '(')
continue;
if (s[i-1] == '(')
dp[i] = (i >= 2 ? dp[i-2] : 0) + 2;
else if (i-dp[i-1] > 0 && s[i-dp[i-1]-1] == '(')
dp[i] = dp[i-1] + ((i-dp[i-1]) >= 2 ? dp[i-dp[i-1]-2] : 0) + 2;
ret = max(ret, dp[i]);
}
return ret;
}
// 栈
int longestValidParentheses(string s) {
int maxans = 0;
stack<int> stk;
stk.push(-1);
for (int i = 0; i < s.length(); i++) {
if (s[i] == '(')
stk.push(i);
else {
stk.pop();
if (stk.empty()) stk.push(i);
else maxans = max(maxans, i - stk.top());
}
}
return maxans;
}
// 前后缀处理
int longestValidParentheses(string s) {
int left = 0, right = 0, maxlength = 0;
for (int i = 0; i < s.length(); ++i) {
if (s[i] == '(') left++;
else right++;
if (left == right)
maxlength = max(maxlength, right<<1);
else if (right > left)
left = right = 0;
}
left = right = 0;
for (int i = (int)s.length()-1; i >= 0; --i) {
if (s[i] == '(') left++;
else right++;
if (left == right)
maxlength = max(maxlength, left<<1);
else if (left > right)
left = right = 0;
}
return maxlength;
}
串联所有单词的子串
给出一堆长度相同的单词和一个长串,找出所有包含所有单词的子串的下标,下标无顺序要求,单词的顺序也无要求
// O(n*m)
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
if(words.empty()) return ret;
int word_size = words[0].size();
int word_num = words.size();
unordered_map<string, int> m1, m2;
for(int i = 0; i < word_num; ++i)
m1[words[i]]++;
for (int i = 0; i+word_size*word_num <= s.size(); ++i) {
int j = i;
for (; j < i+word_size*word_num; j += word_size) {
string temp_str = s.substr(j, word_size);
if (!m1.count(temp_str))
break;
else {
m2[temp_str]++;
if(m1[temp_str] < m2[temp_str])
break;
}
}
if (j == i+word_size*word_num)
ret.push_back(i);
m2.clear();
}
return ret;
}
// O(n)
vector<int> findSubstring(string s, vector<string>& words) {
vector<int> ret;
if(words.empty()) return ret;
unordered_map<string, int> m1, m2;
for (string &word : words)
m1[word]++;
int wordlen = words[0].size();
int wordnum = words.size();
for (int k = 0; k < wordlen; k++) {
int i = k, j = k;
while (i+wordnum*wordlen <= s.size()) {
while (j < i+wordnum*wordlen) {
string temp = s.substr(j, wordlen);
++m2[temp];
j += wordlen;
if (!m1.count(temp)) {
i = j;
m2.clear();
break;
}
else if (m2[temp] > m1[temp]) {
while (m2[temp] > m1[temp]) {
--m2[s.substr(i, wordlen)];
i += wordlen;
}
break;
}
}
if (j == i+wordlen*wordnum) {
ret.push_back(i);
m2[s.substr(i, wordlen)]--;
i += wordlen;
}
}
m2.clear();
}
return ret;
}
最长重复子串
题意:给定一个字符串s,求s中的最长重复子串,只要该子串在s中出现2次及以上即可认定为重复子串(可有部分重叠);
传送门
输入:s = “banana”
输出:“ana”
typedef long long ll;
typedef pair<ll, ll> pll;
ll pow(ll a, ll m, int mod) {
ll ret = 1;
while (m > 0) {
if (m % 2 == 1) {
ret = ret * a % mod;
if (ret < 0) ret += mod;
}
a = a * a % mod;
if (a < 0) a += mod;
m >>= 1;
}
return ret;
}
// 双hash
int check(const vector<int> &arr, int m) {
srand((unsigned)time(NULL));
static int a1 = random()%75 + 26;
static int a2 = random()%75 + 26;
static int mod1 = random()%(INT_MAX - (int)1e9+6) + 1e9+6;
static int mod2 = random()%(INT_MAX - (int)1e9+6) + 1e9+6;
ll aL1 = pow(a1, m, mod1);
ll aL2 = pow(a2, m, mod2);
ll h1 = 0, h2 = 0;
for (int i = 0; i < m; ++i) {
h1 = (h1 * a1 % mod1 + arr[i]) % mod1;
if (h1 < 0) h1 += mod1;
h2 = (h2 * a2 % mod2 + arr[i]) % mod2;
if (h2 < 0) h2 += mod2;
}
set<pll> vis;
vis.insert({h1, h2});
for (int start = 1; start <= arr.size() - m; ++start) {
h1 = (h1 * a1 % mod1 - arr[start - 1] * aL1 % mod1 + arr[start + m - 1]) % mod1;
if (h1 < 0) h1 += mod1;
h2 = (h2 * a2 % mod2 - arr[start - 1] * aL2 % mod2 + arr[start + m - 1]) % mod2;
if (h2 < 0) h2 += mod2;
if (vis.count({h1, h2})) {
return start;
}
vis.insert({h1, h2});
}
return -1;
}
string longestDupSubstring(string s) {
int n = s.size();
vector<int> arr(n);
for (int i = 0; i < n; ++i) {
arr[i] = s[i] - 'a';
}
int l = 1, r = n - 1;
int length = 0, start = -1;
while (l <= r) {
int m = l + (r - l + 1) / 2;
int idx = check(arr, m);
if (idx != -1) {
l = m + 1;
length = m;
start = idx;
}
else r = m - 1;
}
return start != -1 ? s.substr(start, length) : "";
}
字符串全排列
题意:给定一个字符串,返回所有全排列
传送门
vector<string> permutation(string s) {
int n = s.size();
vector<string> ret;
vector<int> vis(n);
sort(s.begin(), s.end());
string tmp;
function<void(int,int)> backtrack = [&](int i, int n) {
if (i == n) {
ret.push_back(tmp);
return;
}
for (int j = 0; j < n; j++) {
if (vis[j] || (j > 0 && !vis[j-1] && s[j-1] == s[j]))
continue;
vis[j] = true;
tmp.push_back(s[j]);
backtrack(i+1, n);
tmp.pop_back();
vis[j] = false;
}
};
backtrack(0, n);
return ret;
}
最小覆盖子串
题意:给定两个字符串s,t,求s的最小全包含t的子串;
传送门
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
题解:滑动窗口,窗口大小正常递增,用两个hash表判断是否包含,包含则缩小左边界并mark答案;
string minWindow(string s, string t) {
unordered_map<char, int> m1, m2;
function<bool()> inside = [&](){
for (auto &x : m1) {
if (x.second > m2[x.first])
return false;
}
return true;
};
for (char &c : t) m1[c] += 1;
int l = 0, r = -1, start = 0, len = INT_MAX;
while (++r < s.length()) {
m2[s[r]] += 1;
while (inside() && m2[s[l]] > m1[s[l]]) {
m2[s[l++]] -= 1;
}
if (inside()) {
if (r-l+1 < len) {
len = r-l+1;
start = l;
}
m2[s[l++]] -= 1;
}
}
return len==INT_MAX ? "" : s.substr(start, len);
}
最长无重复子串
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> m;
int ret = 0, len = 0;
for (int i = 0; i < s.length(); ++i) {
if (!m.count(s[i])) {
m[s[i]] = i;
++len;
}
else {
if (i-m[s[i]] > len) ++len;
else len = i-m[s[i]];
m[s[i]] = i;
}
ret = max(ret, len);
}
return ret;
}
字符串的排列
给定一个字符串,输出这个字符串的全部排列组合,不能有重复的字符串。
vector<string> permutation(string s) {
int n = s.size();
vector<string> ret;
vector<int> vis(n);
sort(s.begin(), s.end());
string tmp;
function<void(int,int)> backtrack = [&](int i, int n) {
if (i == n) {
ret.push_back(tmp);
return;
}
for (int j = 0; j < n; j++) {
if (vis[j] || (j > 0 && !vis[j-1] && s[j-1] == s[j]))
continue;
vis[j] = true;
tmp.push_back(s[j]);
backtrack(i+1, n);
tmp.pop_back();
vis[j] = false;
}
};
backtrack(0, n);
return ret;
}