常见字符串问题


回文子串


最长回文子串

// 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;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值