字符串类题目是面试一大考点
这类题目算法一般不太复杂,但灵活多变,实现细节较多,容易出错
在实际应用中也比较普遍
属于熟能生巧的一系列题目
无论是Online assessment还是Onsite面试,挂在这类题目上就太可惜了
字符串基础知识
遍历字符串
字符串比较
709.转换成小写字母
//https://leetcode.cn/problems/to-lower-case/ class Solution { public: string toLowerCase(string s) { for (char& ch: s) { ch = tolower(ch); } return s; } };
class Solution { public: string toLowerCase(string s) { for (char& ch: s) { if (ch >= 65 && ch <= 90) { ch |= 32; } } return s; } };
58.最后一个单词的长度
//https://leetcode.cn/problems/length-of-last-word/ class Solution { public: int lengthOfLastWord(string s) { int index = s.size() - 1; while (s[index] == ' ') { index--; } int wordLength = 0; while (index >= 0 && s[index] != ' ') { wordLength++; index--; } return wordLength; } };
771.宝石与石头
//https://leetcode.cn/problems/jewels-and-stones/ class Solution { public: int numJewelsInStones(string jewels, string stones) { int jewelsCount = 0; int jewelsLength = jewels.length(), stonesLength = stones.length(); for (int i = 0; i < stonesLength; i++) { char stone = stones[i]; for (int j = 0; j < jewelsLength; j++) { char jewel = jewels[j]; if (stone == jewel) { jewelsCount++; break; } } } return jewelsCount; } };
class Solution { public: int numJewelsInStones(string jewels, string stones) { int jewelsCount = 0; unordered_set<char> jewelsSet; int jewelsLength = jewels.length(), stonesLength = stones.length(); for (int i = 0; i < jewelsLength; i++) { char jewel = jewels[i]; jewelsSet.insert(jewel); } for (int i = 0; i < stonesLength; i++) { char stone = stones[i]; if (jewelsSet.count(stone)) { jewelsCount++; } } return jewelsCount; } };
387.字符串中的第一个唯一字符
https://leetcode.cn/problems/first-unique-character-in-a-string/ class Solution { public: int firstUniqChar(string s) { unordered_map<int, int> frequency; for (char ch: s) { ++frequency[ch]; } for (int i = 0; i < s.size(); ++i) { if (frequency[s[i]] == 1) { return i; } } return -1; } };
class Solution { public: int firstUniqChar(string s) { unordered_map<int, int> position; int n = s.size(); for (int i = 0; i < n; ++i) { if (position.count(s[i])) { position[s[i]] = -1; } else { position[s[i]] = i; } } int first = n; for (auto [_, pos]: position) { if (pos != -1 && pos < first) { first = pos; } } if (first == n) { first = -1; } return first; } };
class Solution { public: int firstUniqChar(string s) { unordered_map<char, int> position; queue<pair<char, int>> q; int n = s.size(); for (int i = 0; i < n; ++i) { if (!position.count(s[i])) { position[s[i]] = i; q.emplace(s[i], i); } else { position[s[i]] = -1; while (!q.empty() && position[q.front().first] == -1) { q.pop(); } } } return q.empty() ? -1 : q.front().second; } };
14.最长公共前缀
//https://leetcode.cn/problems/longest-common-prefix/description/ class Solution { public: string longestCommonPrefix(vector<string>& strs) { if (!strs.size()) { return ""; } string prefix = strs[0]; int count = strs.size(); for (int i = 1; i < count; ++i) { prefix = longestCommonPrefix(prefix, strs[i]); if (!prefix.size()) { break; } } return prefix; } string longestCommonPrefix(const string& str1, const string& str2) { int length = min(str1.size(), str2.size()); int index = 0; while (index < length && str1[index] == str2[index]) { ++index; } return str1.substr(0, index); } };
class Solution { public: string longestCommonPrefix(vector<string>& strs) { if (!strs.size()) { return ""; } int length = strs[0].size(); int count = strs.size(); for (int i = 0; i < length; ++i) { char c = strs[0][i]; for (int j = 1; j < count; ++j) { if (i == strs[j].size() || strs[j][i] != c) { return strs[0].substr(0, i); } } } return strs[0]; } };
class Solution { public: string longestCommonPrefix(vector<string>& strs) { if (!strs.size()) { return ""; } else { return longestCommonPrefix(strs, 0, strs.size() - 1); } } string longestCommonPrefix(const vector<string>& strs, int start, int end) { if (start == end) { return strs[start]; } else { int mid = (start + end) / 2; string lcpLeft = longestCommonPrefix(strs, start, mid); string lcpRight = longestCommonPrefix(strs, mid + 1, end); return commonPrefix(lcpLeft, lcpRight); } } string commonPrefix(const string& lcpLeft, const string& lcpRight) { int minLength = min(lcpLeft.size(), lcpRight.size()); for (int i = 0; i < minLength; ++i) { if (lcpLeft[i] != lcpRight[i]) { return lcpLeft.substr(0, i); } } return lcpLeft.substr(0, minLength); } };
class Solution { public: string longestCommonPrefix(vector<string>& strs) { if (!strs.size()) { return ""; } int minLength = min_element(strs.begin(), strs.end(), [](const string& s, const string& t) {return s.size() < t.size();})->size(); int low = 0, high = minLength; while (low < high) { int mid = (high - low + 1) / 2 + low; if (isCommonPrefix(strs, mid)) { low = mid; } else { high = mid - 1; } } return strs[0].substr(0, low); } bool isCommonPrefix(const vector<string>& strs, int length) { string str0 = strs[0].substr(0, length); int count = strs.size(); for (int i = 1; i < count; ++i) { string str = strs[i]; for (int j = 0; j < length; ++j) { if (str0[j] != str[j]) { return false; } } } return true; } };
344.反转字符串
//https://leetcode.cn/problems/reverse-string/ class Solution { public: void reverseString(vector<char>& s) { int n = s.size(); for (int left = 0, right = n - 1; left < right; ++left, --right) { swap(s[left], s[right]); } } };
541.反转字符串Ⅱ
//https://leetcode.cn/problems/reverse-string-ii/ class Solution { public: string reverseStr(string s, int k) { int n = s.length(); for (int i = 0; i < n; i += 2 * k) { reverse(s.begin() + i, s.begin() + min(i + k, n)); } return s; } };
151.反转字符串中的单词
//https://leetcode.cn/problems/reverse-words-in-a-string/ class Solution { public String reverseWords(String s) { // 除去开头和末尾的空白字符 s = s.trim(); // 正则匹配连续的空白字符作为分隔符分割 List<String> wordList = Arrays.asList(s.split("\\s+")); Collections.reverse(wordList); return String.join(" ", wordList); } }
class Solution { public: string reverseWords(string s) { // 反转整个字符串 reverse(s.begin(), s.end()); int n = s.size(); int idx = 0; for (int start = 0; start < n; ++start) { if (s[start] != ' ') { // 填一个空白字符然后将idx移动到下一个单词的开头位置 if (idx != 0) s[idx++] = ' '; // 循环遍历至单词的末尾 int end = start; while (end < n && s[end] != ' ') s[idx++] = s[end++]; // 反转整个单词 reverse(s.begin() + idx - (end - start), s.begin() + idx); // 更新start,去找下一个单词 start = end; } } s.erase(s.begin() + idx, s.end()); return s; } };
class Solution { public: string reverseWords(string s) { int left = 0, right = s.size() - 1; // 去掉字符串开头的空白字符 while (left <= right && s[left] == ' ') ++left; // 去掉字符串末尾的空白字符 while (left <= right && s[right] == ' ') --right; deque<string> d; string word; while (left <= right) { char c = s[left]; if (word.size() && c == ' ') { // 将单词 push 到队列的头部 d.push_front(move(word)); word = ""; } else if (c != ' ') { word += c; } ++left; } d.push_front(move(word)); string ans; while (!d.empty()) { ans += d.front(); d.pop_front(); if (!d.empty()) ans += ' '; } return ans; } };
557.反转字符串中的单词Ⅲ
//https://leetcode.cn/problems/reverse-words-in-a-string-iii/ class Solution { public: string reverseWords(string s) { string ret; int length = s.length(); int i = 0; while (i < length) { int start = i; while (i < length && s[i] != ' ') { i++; } for (int p = start; p < i; p++) { ret.push_back(s[start + i - 1 - p]); } while (i < length && s[i] == ' ') { i++; ret.push_back(' '); } } return ret; } };
class Solution { public: string reverseWords(string s) { int length = s.length(); int i = 0; while (i < length) { int start = i; while (i < length && s[i] != ' ') { i++; } int left = start, right = i - 1; while (left < right) { swap(s[left], s[right]); left++; right--; } while (i < length && s[i] == ' ') { i++; } } return s; } };
917.仅仅反转字母
//https://leetcode.cn/problems/reverse-only-letters/ class Solution { public: string reverseOnlyLetters(string s) { int n = s.size(); int left = 0, right = n - 1; while (true) { while (left < right && !isalpha(s[left])) { // 判断左边是否扫描到字母 left++; } while (right > left && !isalpha(s[right])) { // 判断右边是否扫描到字母 right--; } if (left >= right) { break; } swap(s[left], s[right]); left++; right--; } return s; } };
8.字符串转换整数(atoi)
//https://leetcode.cn/problems/string-to-integer-atoi/ class Solution { public: int myAtoi(string s) { int index = 0; while( index < s.size() && isspace(s[index])) index++; int sign = 1; if( index < s.size() && s[index] == '-' || s[index] == '+') { sign = s[index] == '-' ? -1 : 1; index++; } long long val = 0; while(index < s.size() && isdigit(s[index])) { if( val > (2147483647 - (s[index] - '0')) / 10) { if (sign == 1) return 2147483647; else return -2147483648; } val = val * 10 + s[index] - '0'; index++; } return sign * val; } };
class Automaton { string state = "start"; unordered_map<string, vector<string>> table = { {"start", {"start", "signed", "in_number", "end"}}, {"signed", {"end", "end", "in_number", "end"}}, {"in_number", {"end", "end", "in_number", "end"}}, {"end", {"end", "end", "end", "end"}} }; int get_col(char c) { if (isspace(c)) return 0; if (c == '+' or c == '-') return 1; if (isdigit(c)) return 2; return 3; } public: int sign = 1; long long ans = 0; void get(char c) { state = table[state][get_col(c)]; if (state == "in_number") { ans = ans * 10 + c - '0'; ans = sign == 1 ? min(ans, (long long)INT_MAX) : min(ans, -(long long)INT_MIN); } else if (state == "signed") sign = c == '+' ? 1 : -1; } }; class Solution { public: int myAtoi(string str) { Automaton automaton; for (char c : str) automaton.get(c); return automaton.sign * automaton.ans; } };
Rabin-Karp字符串哈希算法
https://blog.csdn.net/qq_46118239/article/details/126594215
典型的字符串处理:子串、回文、同构
125.验证回文串
//https://leetcode.cn/problems/valid-palindrome/ class Solution { public: bool isPalindrome(string s) { string sgood; for (char ch: s) { if (isalnum(ch)) { sgood += tolower(ch); } } string sgood_rev(sgood.rbegin(), sgood.rend()); return sgood == sgood_rev; } };
class Solution { public: bool isPalindrome(string s) { string sgood; for (char ch: s) { if (isalnum(ch)) { sgood += tolower(ch); } } int n = sgood.size(); int left = 0, right = n - 1; while (left < right) { if (sgood[left] != sgood[right]) { return false; } ++left; --right; } return true; } };
class Solution { public: bool isPalindrome(string s) { int n = s.size(); int left = 0, right = n - 1; while (left < right) { while (left < right && !isalnum(s[left])) { ++left; } while (left < right && !isalnum(s[right])) { --right; } if (left < right) { if (tolower(s[left]) != tolower(s[right])) { return false; } ++left; --right; } } return true; } };
680.验证回文串Ⅱ
//https://leetcode.cn/problems/valid-palindrome-ii/ class Solution { public: bool checkPalindrome(const string& s, int low, int high) { for (int i = low, j = high; i < j; ++i, --j) { if (s[i] != s[j]) { return false; } } return true; } bool validPalindrome(string s) { int low = 0, high = s.size() - 1; while (low < high) { char c1 = s[low], c2 = s[high]; if (c1 == c2) { ++low; --high; } else { return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high); } } return true; } };
5.最长回文子串
#include <iostream> #include <string> #include <vector> using namespace std; class Solution { //https://leetcode.cn/problems/longest-palindromic-substring/ public: string longestPalindrome(string s) { int n = s.size(); if (n < 2) { return s; } int maxLen = 1; int begin = 0; // dp[i][j] 表示 s[i..j] 是否是回文串 vector<vector<int>> dp(n, vector<int>(n)); // 初始化:所有长度为 1 的子串都是回文串 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++) { // 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得 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]; } } // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置 if (dp[i][j] && j - i + 1 > maxLen) { maxLen = j - i + 1; begin = i; } } } return s.substr(begin, maxLen); } };
class Solution { public: pair<int, int> expandAroundCenter(const string& s, int left, int right) { while (left >= 0 && right < s.size() && s[left] == s[right]) { --left; ++right; } return {left + 1, right - 1}; } string longestPalindrome(string s) { int start = 0, end = 0; for (int i = 0; i < s.size(); ++i) { auto [left1, right1] = expandAroundCenter(s, i, i); auto [left2, right2] = expandAroundCenter(s, i, i + 1); if (right1 - left1 > end - start) { start = left1; end = right1; } if (right2 - left2 > end - start) { start = left2; end = right2; } } return s.substr(start, end - start + 1); } };
class Solution { public: int expand(const string& s, int left, int right) { while (left >= 0 && right < s.size() && s[left] == s[right]) { --left; ++right; } return (right - left - 2) / 2; } string longestPalindrome(string s) { int start = 0, end = -1; string t = "#"; for (char c: s) { t += c; t += '#'; } t += '#'; s = t; vector<int> arm_len; int right = -1, j = -1; for (int i = 0; i < s.size(); ++i) { int cur_arm_len; if (right >= i) { int i_sym = j * 2 - i; int min_arm_len = min(arm_len[i_sym], right - i); cur_arm_len = expand(s, i - min_arm_len, i + min_arm_len); } else { cur_arm_len = expand(s, i, i); } arm_len.push_back(cur_arm_len); if (i + cur_arm_len > right) { j = i; right = i + cur_arm_len; } if (cur_arm_len * 2 + 1 > end - start) { start = i - cur_arm_len; end = i + cur_arm_len; } } string ans; for (int i = start; i <= end; ++i) { if (s[i] != '#') { ans += s[i]; } } return ans; } };
205.同构字符串
//https://leetcode.cn/problems/isomorphic-strings/ class Solution { public: bool isIsomorphic(string s, string t) { unordered_map<char, char> s2t; unordered_map<char, char> t2s; int len = s.length(); for (int i = 0; i < len; ++i) { char x = s[i], y = t[i]; if ((s2t.count(x) && s2t[x] != y) || (t2s.count(y) && t2s[y] != x)) { return false; } s2t[x] = y; t2s[y] = x; } return true; } };
242.有效的字母异位词
//https://leetcode.cn/problems/valid-anagram/ class Solution { public: bool isAnagram(string s, string t) { if (s.length() != t.length()) { return false; } sort(s.begin(), s.end()); sort(t.begin(), t.end()); return s == t; } };
class Solution { public: bool isAnagram(string s, string t) { if (s.length() != t.length()) { return false; } vector<int> table(26, 0); for (auto& ch: s) { table[ch - 'a']++; } for (auto& ch: t) { table[ch - 'a']--; if (table[ch - 'a'] < 0) { return false; } } return true; } };
49.字母异位词分组
//https://leetcode.cn/problems/group-anagrams/ class Solution { public: vector<vector<string>> groupAnagrams(vector<string>& strs) { for(string& s : strs) { string copy = s; sort(copy.begin(), copy.end()); groups[copy].push_back(s); } vector<vector<string>> ans; for(const pair<string, vector<string>> & group : groups) { ans.push_back(group.second); } return ans; } private: unordered_map<string, vector<string>> groups; };
class Solution { public: vector<vector<string>> groupAnagrams(vector<string>& strs) { unordered_map<string, vector<string>> mp; for (string& str: strs) { string key = str; sort(key.begin(), key.end()); mp[key].emplace_back(str); } vector<vector<string>> ans; for (auto it = mp.begin(); it != mp.end(); ++it) { ans.emplace_back(it->second); } return ans; } };
class Solution { public: vector<vector<string>> groupAnagrams(vector<string>& strs) { // 自定义对 array<int, 26> 类型的哈希函数 auto arrayHash = [fn = hash<int>{}] (const array<int, 26>& arr) -> size_t { return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) { return (acc << 1) ^ fn(num); }); }; unordered_map<array<int, 26>, vector<string>, decltype(arrayHash)> mp(0, arrayHash); for (string& str: strs) { array<int, 26> counts{}; int length = str.length(); for (int i = 0; i < length; ++i) { counts[str[i] - 'a'] ++; } mp[counts].emplace_back(str); } vector<vector<string>> ans; for (auto it = mp.begin(); it != mp.end(); ++it) { ans.emplace_back(it->second); } return ans; } };
438.找到字符串中所有字母异位词
//https://leetcode.cn/problems/find-all-anagrams-in-a-string/ class Solution { public: vector<int> findAnagrams(string s, string p) { int sLen = s.size(), pLen = p.size(); if (sLen < pLen) { return vector<int>(); } vector<int> ans; vector<int> sCount(26); vector<int> pCount(26); for (int i = 0; i < pLen; ++i) { ++sCount[s[i] - 'a']; ++pCount[p[i] - 'a']; } if (sCount == pCount) { ans.emplace_back(0); } for (int i = 0; i < sLen - pLen; ++i) { --sCount[s[i] - 'a']; ++sCount[s[i + pLen] - 'a']; if (sCount == pCount) { ans.emplace_back(i + 1); } } return ans; } };
class Solution { public: vector<int> findAnagrams(string s, string p) { int sLen = s.size(), pLen = p.size(); if (sLen < pLen) { return vector<int>(); } vector<int> ans; vector<int> count(26); for (int i = 0; i < pLen; ++i) { ++count[s[i] - 'a']; --count[p[i] - 'a']; } int differ = 0; for (int j = 0; j < 26; ++j) { if (count[j] != 0) { ++differ; } } if (differ == 0) { ans.emplace_back(0); } for (int i = 0; i < sLen - pLen; ++i) { if (count[s[i] - 'a'] == 1) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从不同变得相同 --differ; } else if (count[s[i] - 'a'] == 0) { // 窗口中字母 s[i] 的数量与字符串 p 中的数量从相同变得不同 ++differ; } --count[s[i] - 'a']; if (count[s[i + pLen] - 'a'] == -1) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从不同变得相同 --differ; } else if (count[s[i + pLen] - 'a'] == 0) { // 窗口中字母 s[i+pLen] 的数量与字符串 p 中的数量从相同变得不同 ++differ; } ++count[s[i + pLen] - 'a']; if (differ == 0) { ans.emplace_back(i + 1); } } return ans; } };
10.i正则表达式匹配
//https://leetcode.cn/problems/regular-expression-matching/ class Solution { public: bool isMatch(string s, string p) { int m = s.size(); int n = p.size(); auto matches = [&](int i, int j) { if (i == 0) { return false; } if (p[j - 1] == '.') { return true; } return s[i - 1] == p[j - 1]; }; vector<vector<int>> f(m + 1, vector<int>(n + 1)); f[0][0] = true; for (int i = 0; i <= m; ++i) { for (int j = 1; j <= n; ++j) { if (p[j - 1] == '*') { f[i][j] |= f[i][j - 2]; if (matches(i, j - 1)) { f[i][j] |= f[i - 1][j]; } } else { if (matches(i, j)) { f[i][j] |= f[i - 1][j - 1]; } } } } return f[m][n]; } };
44.通配符匹配
//https://leetcode.cn/problems/wildcard-matching/ class Solution { public: 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] = true; for (int i = 1; i <= n; ++i) { if (p[i - 1] == '*') { dp[0][i] = true; } 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]; } };
class Solution { public: bool isMatch(string s, string p) { auto allStars = [](const string& str, int left, int right) { for (int i = left; i < right; ++i) { if (str[i] != '*') { return false; } } return true; }; auto charMatch = [](char u, char v) { return u == v || v == '?'; }; while (s.size() && p.size() && p.back() != '*') { if (charMatch(s.back(), p.back())) { s.pop_back(); p.pop_back(); } else { return false; } } if (p.empty()) { return s.empty(); } int sIndex = 0, pIndex = 0; int sRecord = -1, pRecord = -1; while (sIndex < s.size() && pIndex < p.size()) { if (p[pIndex] == '*') { ++pIndex; sRecord = sIndex; pRecord = pIndex; } else if (charMatch(s[sIndex], p[pIndex])) { ++sIndex; ++pIndex; } else if (sRecord != -1 && sRecord + 1 < s.size()) { ++sRecord; sIndex = sRecord; pIndex = pRecord; } else { return false; } } return allStars(p, pIndex, p.size()); } };
115.不同的子序列
//https://leetcode.cn/problems/distinct-subsequences/ class Solution { public: int numDistinct(string s, string t) { int m = s.length(), n = t.length(); if (m < n) { return 0; } vector<vector<unsigned long long>> dp(m + 1, vector<unsigned long long>(n + 1)); for (int i = 0; i <= m; i++) { dp[i][n] = 1; } for (int i = m - 1; i >= 0; i--) { char sChar = s.at(i); for (int j = n - 1; j >= 0; j--) { char tChar = t.at(j); if (sChar == tChar) { dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j]; } else { dp[i][j] = dp[i + 1][j]; } } } return dp[0][0]; } };
字符串模式匹配
字符串匹配
Rabin-Karp 字符串匹配
KMP模式匹配算法
时间复杂度?
看似是两重循环
在上面代码的while循环中,j的值不断减小
减小次数≤每层for循环开始时j的值– while循环结束时j的值
而在每层for循环中,j的值至多增加1
因为j始终非负,所以在整个计算过程中,j减小的幅度总和不会超过j增加的幅度总和
故j的总变化次数至多为2(N+M)
整个算法的时间复杂度为O(N+M)
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习