力扣解题-290. 单词规律

8 阅读8分钟

力扣解题-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 中每个单词都由单个空格分隔

Related Topics

哈希表、字符串


第一次解答(双向哈希表法)

解题思路

核心方法:双向哈希表映射法,通过两个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长度,整体仍为线性),耗时1ms击败99.29%用户;
  • 空间复杂度:O(k)(k为不同字符/单词的数量,最多等于pattern长度);
  • 优势:逻辑直观,双向Map能清晰体现“一一对应”的约束,新手易理解;
  • 内存损耗点:两个HashMap的存储开销 + 字符串分割生成的单词数组,导致内存消耗42.3MB仅击败10.71%用户。
    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字符pcs单词sc
    • pc的首次位置(sPos[pc])与sc的首次位置(mapPos.getOrDefault(sc,0))不一致,返回false;
    • 若一致,更新两者的首次位置为i+1(避免与初始值0冲突);
  4. 遍历完成返回true。
性能优化点
  • 时间复杂度:仍为O(n),但减少了HashMap的“存值/取值”比对操作,仅需比对位置数值;
  • 空间复杂度:O(k)(k为不同单词数),但数组替代了一个HashMap,内存开销降低;
  • 内存表现:42.02MB击败70.57%用户,相比双Map法内存效率显著提升;
  • 核心优势:无需维护双向映射关系,仅通过“位置一致性”判断,逻辑更简洁,减少了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;
    }

示例解答

解题思路

解法1:纯数组优化版(极致性能)

核心方法:单词→数字编码 + 数组映射,先将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操作,是大数据量下的最优选择。
解法2:无分割优化版(减少内存开销)

核心方法:遍历字符串不分割,通过双指针直接遍历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),可大幅降低内存/时间开销。