LeetCode 面试题 16.18. 模式匹配

题目

面试题 16.18. 模式匹配

你有两个字符串,即 pattern 和 value。

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 仅包含小写字母。

思路

我们要判断:

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

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

思路核心是:

  1. 数出 pattern 里 'a' 和 'b' 的个数:countA、countB。

  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 :
    • 那就只有一种变量,lenA 由 m / countA 决定(能整除才可能)。
  5. 任意一方可以映射为空串 :
    • 题目给出的例子 4 明确说明: "a"="dogdog", "b"="" 是允许的。
    • 我们通过枚举 lenA、计算 lenB,自然能覆盖 lenA == 0 或 lenB == 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 开销取决于实现:
      • 理论上,一次 substr 是 O(子串长度) 的拷贝。
      • 遍历 pattern 时,总共切出的子串长度之和刚好等于 m(因为我们把 value 从头到尾分块用掉),所以单次枚举的总 substring 拷贝成本是 O(m)。
    • 因此:每个 (lenA, lenB) 的检查成本约为 O(m) (或者,你也可以认为是 O(n + m),但其中 n ≤ 1000,同量级)。
  4. 总时间:

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

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

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

空间复杂度分析

  • 额外变量:
    • subA、subB、若干整型和循环变量。
  • subA / subB 最长可能到 m(整个 value)。
  • 所以额外空间复杂度 为 O(m)(或者说 O(|value|))。
  • 不存在递归或大型辅助数组,空间占用很小
相关推荐
小郎君6581 小时前
Vue 多标签页的加载态设计:为什么我没有用 Suspense 实现 PageLoading
vue.js·算法
JJJennie7771 小时前
MAI Gateway私有化部署全攻略:大模型网关如何接入?附架构图
算法·gateway·ai网关·魔芋ai
東隅已逝,桑榆非晚1 小时前
算法3.双指针法-快乐数
c++·笔记·学习·算法
专注仿真2 小时前
卷积神经网络(CNN)算法详解
人工智能·python·算法·代码·大模型训练
FII工业富联科技服务2 小时前
多智能体如何进入制造现场?从业务协同到数字孪生验证
大数据·人工智能·算法·机器人·制造·数字孪生
≮傷£≯√2 小时前
httpserver_QNetWorkClient
算法
Evand J2 小时前
【PMSM例程11】永磁同步电机(PMSM)基于UKF的转速与转子位置估计。附下载链接
算法·matlab·卡尔曼滤波·永磁同步电机·ukf·pmsm·位置估计
货拉拉技术2 小时前
记忆即智能:重构对话式AI Agent认知基座的四层渐进式记忆与自进化系统
人工智能·算法
杜 硕2 小时前
从零开始的C++生活——第六讲——02
c++·算法·生活
小超人诞生日记2 小时前
C++ STL 反向迭代器:从源码框架到适配器实现
c++·算法