LeetCode 面试题 16.18. 模式匹配

题目

面试题 16.18. 模式匹配

你有两个字符串,即 patternvalue

pattern 字符串由字母 "a""b" 组成,用于描述字符串中的模式。例如,字符串 "catcatgocatgo" 匹配模式 "aabab"(其中 "cat""a""go""b"),该字符串也匹配像 "a""ab""b" 这样的模式。但需注意 "a""b" 不能同时表示相同的字符串。编写一个方法判断 value 字符串是否匹配 pattern 字符串。

示例 1:

复制代码
输入: pattern = "abba", value = "dogcatcatdog"
输出: true

示例 2:

复制代码
输入: pattern = "abba", value = "dogcatcatfish"
输出: false

示例 3:

复制代码
输入: pattern = "aaaa", value = "dogcatcatdog"
输出: false

示例 4:

复制代码
输入: pattern = "abba", value = "dogdogdogdog"
输出: true
解释: "a"="dogdog",b="",反之也符合规则

提示:

  • 1 <= len(pattern) <= 1000
  • 0 <= len(value) <= 1000
  • 你可以假设 pattern 只包含字母 "a""b"value 仅包含小写字母。

思路

我们要判断:

能不能给 ab 分别对应两个(可以为空的)字符串,使得:

  • 把 pattern 中每个 'a' 替换成字符串 A
  • 把 pattern 中每个 'b' 替换成字符串 B
  • 恰好拼出 value
  • 且 A 和 B 不能是同一个字符串(包括不能都等于相同的非空串,一般也不允许都为 "",除非其中一个完全没出现)

思路核心是:

  1. 数出 pattern 里 'a''b' 的个数:countAcountB

  2. 枚举一种字符的长度(比如 lenA),因为总长度是固定的 value.size()

    lenA * countA + lenB * countB == value.size()

    在给定 lenA 的情况下,lenB 就是一个唯一的解(如果能整除的话)。

  3. 对每个可行的 (lenA, lenB),按照 pattern 的顺序从 value 里切子串,检查:

    • 所有 'a' 对应的子串是否一致(同一个映射)
    • 所有 'b' 对应的子串是否一致
    • 'a' 对应的串和 'b' 对应的串不相等
  4. 如果存在任何一组 (lenA, lenB) 满足上述条件,就返回 true,否则 false。

注意特殊情况:

  1. 交换 a / b 保证 pattern 以 a 开头
    • 减少分类情况,否则你可能得分别讨论 pattern 以 a 或 b 开头的不同情况。
  2. pattern 为空:只有当 value 也为空时返回 true。
  3. value 为空
    • pattern 必须只包含同一种字符(全 a 或全 b),否则需要 a 和 b 都映射成 "",导致冲突。
  4. pattern 全是 a
    • 那就只有一种变量,lenAm / countA 决定(能整除才可能)。
  5. 任意一方可以映射为空串
    • 题目给出的例子 4 明确说明: "a"="dogdog", "b"="" 是允许的。
    • 我们通过枚举 lenA、计算 lenB,自然能覆盖 lenA == 0lenB == 0 的情况。

代码

cpp 复制代码
class Solution {
public:
    bool patternMatching(string pattern, string value) {
        // 特判 pattern 为空或 value 为空的情况
        if(pattern.size() == 0) return value.size() == 0;
        if(value.size() == 0) {
            char first = pattern[0];
            for(auto c : pattern) {
                if(c != first)  return false;
            }
            return true;
        }

        // 简化处理,保证 pattern 以 a 开头
        if(pattern[0] == 'b') {
            for(auto &c : pattern) {
                c = (c == 'a' ? 'b' : 'a');
            }
        }
        
        // 统计 a 和 b 的数量
        int cntA = 0, cntB = 0;
        for(auto &c : pattern) {
            if(c == 'a') ++ cntA;
            else ++ cntB;
        }

        // 枚举 a 的长度
        int n = pattern.size(), m = value.size();
        for(int lenA = 0; lenA * cntA <= m; lenA ++ ) {
            // 判断 lenB 否合法: cntA * lenA + cntB * lenB == m
            int sizeB = m - cntA * lenA;
            int lenB = 0;
            if(cntB == 0) {
                if(sizeB != 0) continue;
            }
            else {
                if(sizeB % cntB != 0) continue;
                lenB = sizeB / cntB;
            }

            // 尝试构造 a 和 b
            string subA, subB;
            bool hasA = false, hasB = false;
            bool ok = true;
            int pos = 0;
            for(auto c : pattern) {
                // 注意在这里我们不用担心 substr(pos, lenA/lenB) 越界
                // 因为通过前面的计算我们保证 cntA*lenA+cntB*lenB==value.size()
                if(c == 'a') {
                    string cur = value.substr(pos, lenA);
                    if(!hasA) { subA = cur; hasA = true; }
                    else if(subA != cur) { ok = false; break; }
                    pos += lenA;
                }
                else {
                    string cur = value.substr(pos, lenB);
                    if(!hasB) { subB = cur; hasB = true; }
                    else if(subB != cur) { ok = false; break; }
                    pos += lenB;
                }
            }
            if(ok && subA != subB)  return true;
        }
        return false;
    }
};

时间复杂度分析

记:

  • n = pattern.length()
  • m = value.length()
  1. 统计 countA/countB

    • 一次遍历 pattern → 时间 O(n)
  2. 枚举 lenA

    • lenA 从 0 到 m / countA(极端情况下 countA = 1,则接近 m),所以约 O(m) 次枚举。
  3. 每一次枚举 (lenA, lenB) 后:

    • 要遍历一遍 pattern(长度为 n),同时在 value 上用 substr 切子串。
    • pattern 长度固定为 n,但 substr 开销取决于实现:
      • 理论上,一次 substrO(子串长度) 的拷贝。
      • 遍历 pattern 时,总共切出的子串长度之和刚好等于 m(因为我们把 value 从头到尾分块用掉),所以单次枚举的总 substring 拷贝成本是 O(m)
    • 因此:每个 (lenA, lenB) 的检查成本约为 O(m) (或者,你也可以认为是 O(n + m),但其中 n ≤ 1000,同量级)。
  4. 总时间:

    • 枚举次数大约 O(m),每次检查 O(m) → 总时间 O(m^2)
    • 由于 m ≤ 1000m^2 = 10^6,再乘上常数完全可以接受。

在很多讲解里,会粗略写成:

  • 时间复杂度O(n * m)O(m^2),这两种说法都能接受(因为 nm 同数量级上界为 1000),严格一点按上面的分析是 O(m^2)

空间复杂度分析

  • 额外变量:
    • subAsubB、若干整型和循环变量。
  • subA / subB 最长可能到 m(整个 value)。
  • 所以额外空间复杂度O(m)(或者说 O(|value|))。
  • 不存在递归或大型辅助数组,空间占用很小
相关推荐
uuuuuuu1 小时前
数组中的排序问题
算法
Stream1 小时前
加密与签名技术之密钥派生与密码学随机数
后端·算法
Stream1 小时前
加密与签名技术之哈希算法
后端·算法
少许极端1 小时前
算法奇妙屋(十五)-BFS解决边权为1的最短路径问题
数据结构·算法·bfs·宽度优先·队列·图解算法·边权为1的最短路径问题
c骑着乌龟追兔子2 小时前
Day 27 常见的降维算法
人工智能·算法·机器学习
hetao17338372 小时前
2025-12-02~03 hetao1733837的刷题记录
c++·算法
田里的水稻2 小时前
math_旋转变换
算法·几何学
ada7_2 小时前
LeetCode(python)——94.二叉
python·算法·leetcode·链表·职场和发展
AI视觉网奇2 小时前
躯体驱动 算法学习笔记
人工智能·算法