跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Javajava算法

LeetCode 290. 单词规律:多种解法与性能对比

详细解析了 LeetCode 290 单词规律问题的四种 Java 解法。涵盖双向哈希表、首次位置映射、纯数组优化及无分割遍历策略。通过对比时间复杂度、空间复杂度及内存消耗,提供了不同场景下的最优实现方案,确保字符与单词间建立严格的一一对应关系。

日志猎手发布于 2026/3/23更新于 2026/10/83.8K 浏览

LeetCode 290. 单词规律

给定一种规律 pattern 和一个字符串 s,判断 s 是否遵循相同的规律。

这里的 遵循 指完全匹配,例如,pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向的一一对应关系。

示例

示例 1: 输入:pattern = "abba", s = "dog cat cat dog" 输出:true

示例 2: 输入:pattern = "abba", s = "dog cat cat fish" 输出:false

示例 3: 输入:pattern = "aaaa", s = "dog cat cat dog" 输出:false

提示

  • 1 <= pattern.length <= 300
  • pattern 只包含小写英文字母
  • 1 <= s.length <= 3000
  • s 只包含小写英文字母和空格 ' '
  • s 不包含 前导或尾随空格
  • s 中每个单词都由单个空格分隔

解法一:双向哈希表法

解题思路

核心方法:双向哈希表映射法,通过两个 HashMap 分别维护 pattern 字符→s 单词 和 s 单词→pattern 字符 的双向唯一映射关系,确保'相同字符映射唯一单词'且'不同字符不映射到同一单词',逻辑严谨且能完全满足'一一对应'的核心约束。

核心逻辑拆解

判断单词规律的核心是'字符与单词的双向唯一绑定':

  1. 前置校验:将 s 按空格分割为单词数组,若数组长度与 pattern 长度不一致,直接返回 false(字符数和单词数不匹配,无法一一对应);
  2. 正向约束:pattern 中的同一个字符,必须始终映射到 s 中的同一个单词(比如 pattern 的 'a' 不能既映射到 'dog' 又映射到 'cat');
  3. 反向约束:s 中的同一个单词,只能被 pattern 中的一个字符映射(比如 pattern 的 'a' 和 'b' 不能同时映射到 'dog');
  4. 双 Map 维护映射:
    • map:key 为 pattern 的字符,value 为 s 的单词(字符→单词);
    • map2:key 为 s 的单词,value 为 pattern 的字符(单词→字符);
  5. 遍历验证:
    • 若字符已在 map 中,检查映射的单词是否与当前单词一致,不一致则返回 false;
    • 若字符不在 map 中,检查当前单词是否已被其他字符映射(在 map2 中),若已映射则返回 false;
    • 若都满足,将字符→单词、单词→字符分别存入两个 Map;
  6. 遍历完成后返回 true。
性能说明
  • 时间复杂度:O(n)(n 为 pattern 长度,分割字符串 O(m) + 遍历 O(n),m 为 s 长度,整体仍为线性);
  • 空间复杂度:O(k)(k 为不同字符/单词的数量,最多等于 长度);
pattern
  • 优势:逻辑直观,双向 Map 能清晰体现'一一对应'的约束,新手易理解;
  • 内存损耗点:两个 HashMap 的存储开销 + 字符串分割生成的单词数组。
  • public boolean wordPattern(String pattern, String s) {
        String[] sArray = s.split(" ");
        if (pattern.length() != sArray.length) {
            return false;
        }
        boolean result = true;
        Map<Character, String> map = new HashMap<>();
        Map<String, Character> map2 = new HashMap<>();
        for (int i = 0; i < pattern.length(); i++) {
            char a = pattern.charAt(i);
            if (map.containsKey(a)) {
                if (!map.get(a).equals(sArray[i])) {
                    result = false;
                    break;
                }
            } else {
                if (map2.containsKey(sArray[i])) {
                    result = false;
                    break;
                } else {
                    map.put(a, sArray[i]);
                    map2.put(sArray[i], a);
                }
            }
        }
        return result;
    }
    

    解法二:首次位置映射法

    解题思路

    核心方法:首次出现位置统一映射法,将'字符→单词'的双向映射转化为'字符/单词的首次出现位置'比对,用数组记录字符的首次位置、HashMap 记录单词的首次位置,通过'位置是否一致'判断是否满足规律,减少映射维护的开销,内存效率大幅提升。

    核心逻辑拆解

    规律匹配的本质是'字符和单词的出现模式完全一致',比如:

    • pattern="abba" 的首次出现位置:a(1)、b(2)、b(2)、a(1);
    • s="dog cat cat dog" 的首次出现位置:dog(1)、cat(2)、cat(2)、dog(1);
    • 位置序列完全一致则满足规律。

    具体步骤:

    1. 前置校验:分割 s 为单词数组,长度不一致直接返回 false;
    2. 位置记录容器:
      • sPos 数组(长度 256):记录 pattern 中每个字符的首次出现位置(字符 ASCII 码为下标,初始值 0);
      • mapPos HashMap:记录 s 中每个单词的首次出现位置(初始无记录时返回 0);
    3. 遍历比对:
      • 取当前 pattern 字符 pc 和 s 单词 sc;
      • 若 pc 的首次位置(sPos[pc])与 sc 的首次位置(mapPos.getOrDefault(sc,0))不一致,返回 false;
      • 若一致,更新两者的首次位置为 i+1(避免与初始值 0 冲突);
    4. 遍历完成返回 true。
    性能优化点
    • 时间复杂度:仍为 O(n),但减少了 HashMap 的'存值/取值'比对操作,仅需比对位置数值;
    • 空间复杂度:O(k)(k 为不同单词数),但数组替代了一个 HashMap,内存开销降低;
    • 核心优势:无需维护双向映射关系,仅通过'位置一致性'判断,逻辑更简洁,减少了 Map 的 equals 比对开销。
    public boolean wordPattern(String pattern, String s) {
        String[] sArray = s.split(" ");
        if (pattern.length() != sArray.length) {
            return false;
        }
        int n = sArray.length;
        // 记录字符首次出现的位置(初始为 0)
        int[] sPos = new int[256];
        Map<String, Integer> mapPos = new HashMap<>();
        for (int i = 0; i < n; i++) {
            char pc = pattern.charAt(i);
            String sc = sArray[i];
            // 首次出现位置不同则不同构
            if (sPos[pc] != mapPos.getOrDefault(sc, 0)) {
                return false;
            }
            // 更新首次出现位置为 i+1(避免与初始值 0 冲突)
            sPos[pc] = i + 1;
            mapPos.put(sc, i + 1);
        }
        return true;
    }
    

    解法三:纯数组优化版

    解题思路

    核心方法:单词→数字编码 + 数组映射,先将 s 的单词映射为数字(如第一个单词→1,第二个新单词→2),再用两个数组分别记录 pattern 字符和 s 单词的编码,通过编码一致性判断规律,完全避免 HashMap 的使用,性能达到最优。

    核心逻辑
    1. 分割 s 为单词数组,长度不一致返回 false;
    2. 用 wordToCode HashMap 将单词映射为唯一数字编码(仅用于临时编码);
    3. 初始化两个数组 patternCode(长度 300)、wordCode(长度 300);
    4. 遍历过程:
      • 对每个单词,若未编码则分配递增编码(从 1 开始);
      • 将 pattern 字符转为编码(如 'a'→0, 'b'→1);
      • 记录 pattern 字符编码和单词编码到对应数组;
      • 若两者编码不一致,返回 false;
    5. 遍历完成返回 true。
    代码实现
    public boolean wordPattern(String pattern, String s) {
        String[] words = s.split(" ");
        if (pattern.length() != words.length) {
            return false;
        }
        int n = pattern.length();
        // 单词→数字编码(避免重复单词)
        Map<String, Integer> wordToCode = new HashMap<>();
        int[] patternCode = new int[n];
        int[] wordCodeArr = new int[n];
        int code = 1;
        for (int i = 0; i < n; i++) {
            // pattern 字符编码(a→0, b→1...)
            patternCode[i] = pattern.charAt(i) - 'a';
            // 单词编码(首次出现分配新编码)
            String word = words[i];
            if (!wordToCode.containsKey(word)) {
                wordToCode.put(word, code++);
            }
            wordCodeArr[i] = wordToCode.get(word);
            // 编码不一致则不满足规律
            if (patternCode[i] != wordCodeArr[i] - 1) {
                // 对齐编码起始值
                return false;
            }
        }
        return true;
    }
    
    优势说明
    • 时间复杂度:O(n),HashMap 仅用于单词编码,且仅做'是否存在'判断,开销极小;
    • 空间复杂度:O(k)(k 为不同单词数),数组访问速度远超 HashMap;
    • 极致性能:完全基于数组比对,无字符串 equals 操作,是大数据量下的最优选择。

    解法四:无分割优化版

    解题思路

    核心方法:遍历字符串不分割,通过双指针直接遍历 s 的单词(避免分割生成单词数组),进一步降低内存开销,适合 s 长度极大的场景。

    代码实现
    public boolean wordPattern(String pattern, String s) {
        int pLen = pattern.length();
        int sLen = s.length();
        int pIdx = 0, sIdx = 0;
        Map<Character, String> charToWord = new HashMap<>();
        Map<String, Character> wordToChar = new HashMap<>();
        while (pIdx < pLen && sIdx < sLen) {
            // 取 pattern 当前字符
            char c = pattern.charAt(pIdx);
            // 双指针找当前单词的结束位置
            int end = sIdx;
            while (end < sLen && s.charAt(end) != ' ') {
                end++;
            }
            String word = s.substring(sIdx, end);
            sIdx = end + 1;
            // 移动到下一个单词起始位置
            // 双向映射校验
            if (charToWord.containsKey(c)) {
                if (!charToWord.get(c).equals(word)) {
                    return false;
                }
            } else {
                if (wordToChar.containsKey(word)) {
                    return false;
                }
                charToWord.put(c, word);
                wordToChar.put(word, c);
            }
            pIdx++;
        }
        // 校验是否都遍历完成(避免 pattern 遍历完但 s 还有单词,或反之)
        return pIdx == pLen && sIdx > sLen;
    }
    
    优势说明
    • 内存优化:无需生成单词数组,直接遍历字符串截取单词,节省数组存储的内存;
    • 提前终止:遍历过程中若发现不匹配可立即终止,无需分割整个字符串;
    • 适用场景:s 长度极大(如接近 3000)时,避免分割生成大数组,内存效率更高。

    总结

    1. 双向哈希表法:逻辑直观,清晰体现'一一对应'约束,适合新手理解核心原理;
    2. 首次位置映射法:减少 Map 比对开销,内存效率更高,是平衡性能和可读性的优选;
    3. 纯数组优化版:极致性能,无字符串 equals 操作,适合大数据量场景;
    4. 无分割优化版:减少内存开销,避免生成单词数组,适合超长字符串场景;
    5. 关键技巧:
      • 前置校验(长度一致)是必做步骤,可快速过滤无效用例;
      • 字符类映射优先用数组(ASCII 范围)替代 HashMap,提升性能;
      • 避免不必要的字符串操作(如分割、equals),可大幅降低内存/时间开销。

    目录

    1. LeetCode 290. 单词规律
    2. 示例
    3. 提示
    4. 解法一:双向哈希表法
    5. 解题思路
    6. 核心逻辑拆解
    7. 性能说明
    8. 解法二:首次位置映射法
    9. 解题思路
    10. 核心逻辑拆解
    11. 性能优化点
    12. 解法三:纯数组优化版
    13. 解题思路
    14. 核心逻辑
    15. 代码实现
    16. 优势说明
    17. 解法四:无分割优化版
    18. 解题思路
    19. 代码实现
    20. 优势说明
    21. 总结

    更多推荐文章

    查看全部
    • 设计模式:观察者模式详解与 Java 实现
    • DeepSeek-R1 大模型基于 MS-Swift 框架的部署、推理与微调实践
    • 2026 年 3 月全球 AI 前沿动态
    • AI 重构产品能力边界:为何“人人都是产品经理”成为现实
    • Java 常用注解扩展对比
    • 宇树机器人 G1 二次开发:FAST-LIO 建图与 RViz 配置
    • 学生与教育工作者免费获取 GitHub Copilot 权限指南
    • JavaScript Proxy 代理机制与核心方法详解

    相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • 加密/解密文本

      使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

    • Gemini 图片去水印

      基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online