LeetCode 字符串问题
@
28. 实现 strStr() KMP算法
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
-
算法思路:
- 遍历haystack每个起始位置,向后对比needle,算法复杂度O(m x n)
- 使用KMP算法,对needle建立最大前缀和辅助数组,算法复杂度O(m + n)
-
KMP算法解释:
- next[i] 表示以第 i 个字符为止的前 next[i] 个字符与字符串开头前 next[i] 个字符相等,即相同的最大前缀
- needle = "AABAAACD" next = [0 1 0 1 2 1 0 0]
- 假设当前对比第5个字符A,p = 5,其不相同,查询其前一个字符的最大前缀和为 next[p - 1],可知前面 2 个字符一定是AA,因此可以直接对比 needle 第 3 个字符,也就是第 next[p - 1] 个字符
- haystack = "AABAA'B'AAACD" needle = "AABAA'A'CD"
- 对比到两个字符串第 6 个字符('B' 'A')时发现不同,此时查看 needle 的 next 数组前一个字符的最大前缀长度,即 next[5] = 2,说明 haystack 第 6 个字符前面的 2 个字符和 needle 前 2 个字符相同,因此无需从头判断,直接从 needle 的第 3 个字符处继续与 haystack 第 6 个字符判断即可
class Solution { public: int strStr(string haystack, string needle) { int k = 0, m = haystack.length(), n = needle.length(); vector<int> next(n, 0); // 0 表示不存在相同的最大前缀 if (n == 0) return 0; calNext(needle, next); for (int i = 0; i < m; i++) { while(k > 0 && needle[k] != haystack[i]) { k = next[k - 1]; // 部分匹配,往前回溯 } if (needle[k] == haystack[i]) { ++k; } if (k == n) { return i - n + 1; } } return -1; } void calNext(const string& needle, vector<int>& next) { for (int j = 1, p = 0; j < needle.length(); j++) { while (p > 0 && needle[p] != needle[j]) { p = next[p - 1]; // 不相同,往前回溯 } if (needle[p] == needle[j]) { p++; // 相同,更新相同的最大前缀长度 } next[j] = p; } } };
76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。Link
- 滑动窗口、对 t 中字符统计个数
- 1、预先统计 t 中每个字符的个数
- 2、对 t 中的字符标记为 true
- 记录滑动窗口内的字符
- 1、对字符的个数减 1
- 如果字符个数减 1 大于等于 0,说明是 t 中的字符,此时令 cnt++
- 2、当 cnt 等于 t 的长度,说明当前窗口包含了 t 中的字符
- 记录此时的窗口大小、起始位置
- 左边界右移,当字符为 t 中的字符时,标志为 true,并且在个数为 0 的基础加 1 将大于 0,说明 t 中的字符移出了窗口,令 cnt--
- 3、检查窗口大小是否改变,改变则输出子字符串,否则说明未找到
- 1、对字符的个数减 1
class Solution { public: string minWindow(string s, string t) { vector<int> chars(128, 0); vector<bool> flag(128, false); // 统计 t 中的字符个数,并进行标记 for (int i = 0; i < t.size(); i++) { ++chars[t[i]]; flag[t[i]] = true; } int min_l = 0, l = 0, min_size = s.size() + 1, cnt = 0; for (int r = 0; r < s.size(); ++r) { // 实现移入滑动窗口内的字符个数减 1 操作 if (--chars[s[r]] >= 0) { ++cnt; } while (cnt == t.size()) { if (r - l + 1 < min_size) { min_l = l; min_size = r - l + 1; } // 将 t 中的字符移出窗口 if (flag[s[l]] && ++chars[s[l]] > 0) { --cnt; } ++l; } } return min_size > s.size() ? "" : s.substr(min_l, min_size); } };
- 滑动窗口解法 2
- 1、统计 t 中的字符个数
- 2、滑动窗口内字符判断
- 字符个数大于 0,说明是 t 的字符,此时 cnt--
- 移入窗口内的字符,其个数减 1
- 3、cnt 为 0,说明窗口内包含 t 中所有字符
- 滑动窗口左边界循环右移,直到遇到 t 的字符,即 ++need[s[l]] <= 0 不成立时
- 当 s[l] 为 t 的字符时:其值为 0,左加加后为 1
- 当 s[l] 不为 t 的字符时:其值小于 0,左加加后 小于等于 0
- 记录符合条件的窗口坐边界、窗口长度,并将 cnt++,说明 t 中字符移出窗口
- 滑动窗口左边界循环右移,直到遇到 t 的字符,即 ++need[s[l]] <= 0 不成立时
- 4、观察窗口长度是否变化,判断有无子字符串输出
class Solution { public: string minWindow(string s, string t) { int len = t.size(); if (s.size() < len) return ""; vector<int> need(128, 0); // 统计 t 中每个字符的个数 for (char &c : t) { need[c]++; } // 滑动窗口求最小的子串 int l = 0, r = 0, start = 0, size = INT_MAX; int cnt = len; while (r < s.size()) { char c = s[r]; // 只有 c 为 t 中的字符时,才有 if 条件成立,说明窗口内引入了一个有用字符 if (need[c] > 0) { cnt--; } // 将滑动窗口内字符的数量减 1 need[c]--; // 说明 t 中的所有字符均在滑动窗口内了 if (cnt == 0) { // 因为先前统计了 t 中字符的数量,滑动窗口内遇到字符将其减 1 // 只有 t 中的字符值为 0,故 ++ 操作后为 1 大于 0 while (l < r && ++need[s[l]] <= 0) { l++; } // 更新符合条件的子字符串长度 int cur = r - l + 1; if (cur < size) { size = cur; start = l; } // 将左边界右移,同时将 cnt++,因为一个有效字符移出了滑动窗口 l++; cnt++; } r++; // 右移右边界 } return size == INT_MAX ? "" : s.substr(start, size); } };
3. 无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。Link
- 滑动窗口统计字符、同一字符出现两次即计算
- 1、建立容器存储每个字符出现的个数
- 2、当字符个数超过 1 时,说明出现了重复字符
- 记录重复字符前的窗口长度,即为子串长度
- 移动窗口左边界,直到将重复字符移出
- |- -count[s[l]] <= 0 说明移出的为非重复字符,因为这些字符只出现了一次,减 1 后为 0
- 3、到达字符串结尾时再次更新最大子串长度
- 计算窗口大小的触发条件时出现了重复字符,但字符串遍历完也需要触发并计算窗口大小
class Solution { public: int lengthOfLongestSubstring(string s) { vector<int> count(128, 0); int l = 0, size = 0; int n = s.size(); for (int i = 0; i < s.size(); i++) { char c = s[i]; if (++count[c] > 1) { // 当前 i 指向的是重复字符,因此字符串长度为 i - l size = max(size, i - l); // 循环移动左边界,并将字符个数减 1 while (--count[s[l]] <= 0) { l++; } // l 指向重复字符,将其移出需要再加 1 l++; } } // 统计到达字符串末尾时的 子字符串长度 return max(size, n - l); } };
394. 字符串解码
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。Link
- 递归思想、遇左 '[' 便向下递归
- 1、使用数字存储编码次数、字符串存储结果
- 2、'[' 前必定是一个数字 “3[a2[bc]]”
- 向下递归、遇 ']' 则返回结果
- "a2[bc]"
- res = "a" -> num = 2 -> return bc -> "a" + 2 * "bc"
- 字符串根据次数、递归返回的结果得到当前的解码结果
- 向下递归、遇 ']' 则返回结果
- 3、遍历结束返回结果
- 索引为引用形式,防止重复计算
class Solution { public: string decodeString(string s) { int i = 0; return recur(s, i); } string recur(string& s, int& i) { string res = ""; int num = 0; int n = s.size(); while (i < n) { if (isdigit(s[i])) { num = num * 10 + (s[i] - '0'); }else if (isalpha(s[i])) { res += s[i]; }else if (s[i] == '[') { string temp; i++; // "3 -> [a2[bc]]" // "2 -> [bc]" temp = recur(s, i); while (num > 0) { res += temp; num--; } }else { return res; // 返回结果 } i++; } return res; } };
本文作者:GreyWang
本文链接:https://www.cnblogs.com/GreyWang/p/17124739.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步