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、检查窗口大小是否改变,改变则输出子字符串,否则说明未找到
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 中字符移出窗口
  • 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;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值