LeetCode100天Day10-单词规律与同构字符串

LeetCode100天Day10-单词规律与同构字符串:双向映射与字符映射

摘要:本文详细解析了LeetCode中两道经典题目------"单词规律"和"同构字符串"。通过双向映射解决模式匹配问题,以及使用HashMap实现字符映射,帮助读者掌握一一对应关系的处理技巧。

目录

文章目录

  • LeetCode100天Day10-单词规律与同构字符串:双向映射与字符映射
    • 目录
    • [1. 单词规律(Word Pattern)](#1. 单词规律(Word Pattern))
    • [2. 同构字符串(Isomorphic Strings)](#2. 同构字符串(Isomorphic Strings))
      • [2.1 题目描述](#2.1 题目描述)
      • [2.2 解题思路](#2.2 解题思路)
      • [2.3 代码实现](#2.3 代码实现)
      • [2.4 代码逐行解释](#2.4 代码逐行解释)
      • [2.5 执行流程详解](#2.5 执行流程详解)
      • [2.6 算法图解](#2.6 算法图解)
      • [2.7 复杂度分析](#2.7 复杂度分析)
      • [2.8 边界情况](#2.8 边界情况)
    • [3. 两题对比与总结](#3. 两题对比与总结)
      • [3.1 算法对比](#3.1 算法对比)
      • [3.2 双向映射模板](#3.2 双向映射模板)
      • [3.3 为什么需要双向映射](#3.3 为什么需要双向映射)
      • [3.4 HashMap的containsKey与get](#3.4 HashMap的containsKey与get)
      • [3.5 String比较的坑](#3.5 String比较的坑)
    • [4. 总结](#4. 总结)
    • 参考资源
    • 文章标签

1. 单词规律(Word Pattern)

1.1 题目描述

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

这里的 遵循 指完全匹配,例如,pattern 里的每个字母和字符串 s 中的每个非空单词之间存在着双向连接的对应规律。具体来说:

  • pattern 中的每个字母都 恰好 映射到 s 中的一个唯一单词。
  • s 中的每个唯一单词都 恰好 映射到 pattern 中的一个字母。
  • 没有两个字母映射到同一个单词,也没有两个单词映射到同一个字母。

示例 1

复制代码
输入: pattern = "abba", s = "dog cat cat dog"
输出: true
解释: "a"对应"dog","b"对应"cat",满足双向映射

示例 2

复制代码
输入: pattern = "abba", s = "dog cat cat fish"
输出: false
解释: "a"不能同时对应"dog"和"fish"

示例 3

复制代码
输入: pattern = "aaaa", s = "dog cat cat dog"
输出: false
解释: 不同字母不能映射到同一个单词

1.2 解题思路

这道题的核心是维护双向映射关系:

  1. 使用两个HashMap,一个记录字母到单词的映射,一个记录单词到字母的映射
  2. 检查每个字母-单词对是否满足双向一致性
  3. 如果映射冲突,返回false

解题步骤

  1. 将字符串s按空格分割成单词数组
  2. 检查pattern长度与单词数组长度是否相等
  3. 遍历pattern,检查每个字符对应单词的映射关系
  4. 如果出现冲突,返回false
  5. 遍历结束,返回true

1.3 代码实现

java 复制代码
class Solution {
    public boolean wordPattern(String s, String t) {
        String[] t2 = t.split(" ");
        if(t2.length != s.length()){
            return false;
        }
        Map<Character,String>s2t = new HashMap<>();
        Map<String,Character>t2s = new HashMap<>();
        for(int i = 0;i < s.length(); i++){
            char a = s.charAt(i);
            String b = t2[i];
            if((s2t.containsKey(a) && !s2t.get(a).equals(b)) ||
            (t2s.containsKey(b) && t2s.get(b) != a)){
                return false;
            }
            s2t.put(a,b);
            t2s.put(b,a);
        }
        return true;
    }
}

1.4 代码逐行解释

第一部分:分割字符串与长度检查
java 复制代码
String[] t2 = t.split(" ");
if(t2.length != s.length()){
    return false;
}

功能说明

代码 作用
t.split(" ") 按空格分割字符串
t2.length 单词数量
s.length() pattern字符数量

示例

复制代码
t = "dog cat cat dog"
t.split(" ") → ["dog", "cat", "cat", "dog"]
t2.length = 4

pattern = "abba"
s.length() = 4

长度相等,继续检查
第二部分:创建双向映射HashMap
java 复制代码
Map<Character,String>s2t = new HashMap<>();
Map<String,Character>t2s = new HashMap<>();

两个HashMap的作用

HashMap 键类型 值类型 作用
s2t Character String 字符 → 单词
t2s String Character 单词 → 字符

为什么需要两个HashMap

复制代码
只有一个HashMap的情况:
pattern = "abba"
s = "dog dog dog dog"

s2t: {'a' → "dog", 'b' → "dog"}

问题:无法检测到"dog"被两个字符映射!

使用两个HashMap:
pattern = "abba"
s = "dog dog dog dog"

i=0: s2t={'a':"dog"}, t2s={"dog":'a'}

i=1: 'b'映射到"dog"
     但t2s中"dog"已映射到'a'
     冲突!返回false
第三部分:遍历检查映射
java 复制代码
for(int i = 0;i < s.length(); i++){
    char a = s.charAt(i);
    String b = t2[i];
    if((s2t.containsKey(a) && !s2t.get(a).equals(b)) ||
    (t2s.containsKey(b) && t2s.get(b) != a)){
        return false;
    }
    s2t.put(a,b);
    t2s.put(b,a);
}

条件判断详解

java 复制代码
s2t.containsKey(a) && !s2t.get(a).equals(b)
条件 含义
s2t.containsKey(a) 字符a已经映射过
!s2t.get(a).equals(b) 但映射的不是单词b
java 复制代码
t2s.containsKey(b) && t2s.get(b) != a
条件 含义
t2s.containsKey(b) 单词b已经映射过
t2s.get(b) != a 但映射的不是字符a

1.5 执行流程详解

示例1pattern = "abba", s = "dog cat cat dog"

复制代码
初始状态:
s2t = {}
t2s = {}
t2 = ["dog", "cat", "cat", "dog"]

i=0, a='a', b="dog":
  s2t不包含'a'
  t2s不包含"dog"
  s2t = {'a': "dog"}
  t2s = {"dog": 'a'}

i=1, a='b', b="cat":
  s2t不包含'b'
  t2s不包含"cat"
  s2t = {'a': "dog", 'b': "cat"}
  t2s = {"dog": 'a', "cat": 'b'}

i=2, a='b', b="cat":
  s2t包含'b',s2t.get('b') = "cat"
  "cat".equals("cat")? 是,不冲突
  t2s包含"cat",t2s.get("cat") = 'b'
  'b' == 'b'? 是,不冲突
  s2t = {'a': "dog", 'b': "cat"}
  t2s = {"dog": 'a', "cat": 'b'}

i=3, a='a', b="dog":
  s2t包含'a',s2t.get('a') = "dog"
  "dog".equals("dog")? 是,不冲突
  t2s包含"dog",t2s.get("dog") = 'a'
  'a' == 'a'? 是,不冲突
  s2t = {'a': "dog", 'b': "cat"}
  t2s = {"dog": 'a', "cat": 'b'}

循环结束,返回 true

示例2pattern = "abba", s = "dog cat cat fish"

复制代码
初始状态:
s2t = {}
t2s = {}
t2 = ["dog", "cat", "cat", "fish"]

i=0, a='a', b="dog":
  s2t = {'a': "dog"}
  t2s = {"dog": 'a'}

i=1, a='b', b="cat":
  s2t = {'a': "dog", 'b': "cat"}
  t2s = {"dog": 'a', "cat": 'b'}

i=2, a='b', b="cat":
  不冲突,继续
  s2t = {'a': "dog", 'b': "cat"}
  t2s = {"dog": 'a', "cat": 'b'}

i=3, a='a', b="fish":
  s2t包含'a',s2t.get('a') = "dog"
  "dog".equals("fish")? 否,冲突!
  返回 false

输出: false

1.6 算法图解

复制代码
pattern = "abba"
s = "dog cat cat dog"

双向映射关系:

      字符           单词
      'a'  ←─────→  "dog"
      'b'  ←─────→  "cat"

映射检查:
i=0: 'a' → "dog" ✓
i=1: 'b' → "cat" ✓
i=2: 'b' → "cat" ✓
i=3: 'a' → "dog" ✓

结果:true

pattern = "abba"
s = "dog dog dog dog"

双向映射关系:

i=0: 'a' → "dog"
     s2t = {'a': "dog"}
     t2s = {"dog": 'a'}

i=1: 'b' → "dog"
     检查:t2s中"dog"已映射到'a'
     但现在要映射到'b'
     冲突!

     s2t = {'a': "dog", 'b': "dog"}
     t2s = {"dog": 'a'}

问题:一个单词对应两个字符!
结果:false

1.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n) n是pattern长度
空间复杂度 O(n) 存储映射关系

1.8 边界情况

pattern s 说明 输出
"a" "dog" 单字符 true
"ab" "dog" 长度不等 false
"ab" "dog dog" 一对多 false
"aa" "dog cat" 多对一 false

2. 同构字符串(Isomorphic Strings)

2.1 题目描述

给定两个字符串 st,判断它们是否是同构的。

如果 s 中的字符可以按某种映射关系替换得到 t,那么这两个字符串是同构的。

每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。

示例 1

复制代码
输入:s = "egg", t = "add"
输出:true
解释:'e'→'a', 'g'→'d'

示例 2

复制代码
输入:s = "foo", t = "bar"
输出:false
解释:'o'不能同时映射到'a'和'r'

示例 3

复制代码
输入:s = "paper", t = "title"
输出:true
解释:'p'→'t', 'a'→'i', 'e'→'l', 'r'→'e'

2.2 解题思路

这道题与单词规律类似,也是维护双向映射:

  1. 使用两个HashMap记录字符映射关系
  2. 检查每个位置的两个字符是否满足双向一致性
  3. 如果映射冲突,返回false

解题步骤

  1. 创建两个HashMap,分别记录s→t和t→s的映射
  2. 遍历字符串,检查每个字符对
  3. 如果映射冲突,返回false
  4. 遍历结束,返回true

2.3 代码实现

java 复制代码
class Solution {
    public boolean isIsomorphic(String s, String t) {
        Map<Character,Character>s2t = new HashMap<>();
        Map<Character,Character>t2s = new HashMap<>();
        for(int i = 0;i < s.length(); i++){
            char a = s.charAt(i);
            char b = t.charAt(i);
            if((s2t.containsKey(a) && s2t.get(a) != b) ||
            (t2s.containsKey(b) && t2s.get(b) != a)){
                return false;
            }
            s2t.put(a,b);
            t2s.put(b,a);
        }
        return true;
    }
}

2.4 代码逐行解释

第一部分:创建双向映射
java 复制代码
Map<Character,Character>s2t = new HashMap<>();
Map<Character,Character>t2s = new HashMap<>();
HashMap 键类型 值类型 作用
s2t Character Character s的字符 → t的字符
t2s Character Character t的字符 → s的字符
第二部分:遍历检查
java 复制代码
for(int i = 0;i < s.length(); i++){
    char a = s.charAt(i);
    char b = t.charAt(i);
    if((s2t.containsKey(a) && s2t.get(a) != b) ||
    (t2s.containsKey(b) && t2s.get(b) != a)){
        return false;
    }
    s2t.put(a,b);
    t2s.put(b,a);
}

条件详解

java 复制代码
s2t.containsKey(a) && s2t.get(a) != b
条件 含义
s2t.containsKey(a) s中的字符a已经映射过
s2t.get(a) != b 但映射的不是t中的字符b
java 复制代码
t2s.containsKey(b) && t2s.get(b) != a
条件 含义
t2s.containsKey(b) t中的字符b已经映射过
t2s.get(b) != a 但映射的不是s中的字符a

2.5 执行流程详解

示例1s = "egg", t = "add"

复制代码
初始状态:
s2t = {}
t2s = {}

i=0, a='e', b='a':
  s2t不包含'e'
  t2s不包含'a'
  s2t = {'e': 'a'}
  t2s = {'a': 'e'}

i=1, a='g', b='d':
  s2t不包含'g'
  t2s不包含'd'
  s2t = {'e': 'a', 'g': 'd'}
  t2s = {'a': 'e', 'd': 'g'}

i=2, a='g', b='d':
  s2t包含'g',s2t.get('g') = 'd'
  'd' == 'd'? 是,不冲突
  t2s包含'd',t2s.get('d') = 'g'
  'g' == 'g'? 是,不冲突
  s2t = {'e': 'a', 'g': 'd'}
  t2s = {'a': 'e', 'd': 'g'}

循环结束,返回 true

输出: true

示例2s = "foo", t = "bar"

复制代码
初始状态:
s2t = {}
t2s = {}

i=0, a='f', b='b':
  s2t = {'f': 'b'}
  t2s = {'b': 'f'}

i=1, a='o', b='a':
  s2t = {'f': 'b', 'o': 'a'}
  t2s = {'b': 'f', 'a': 'o'}

i=2, a='o', b='r':
  s2t包含'o',s2t.get('o') = 'a'
  'a' != 'r'? 是,冲突!
  返回 false

输出: false

示例3s = "paper", t = "title"

复制代码
i=0, a='p', b='t':
  s2t = {'p': 't'}
  t2s = {'t': 'p'}

i=1, a='a', b='i':
  s2t = {'p': 't', 'a': 'i'}
  t2s = {'t': 'p', 'i': 'a'}

i=2, a='p', b='t':
  s2t包含'p',s2t.get('p') = 't'
  't' == 't'? 是,不冲突
  t2s包含't',t2s.get('t') = 'p'
  'p' == 'p'? 是,不冲突
  映射保持不变

i=3, a='e', b='l':
  s2t = {'p': 't', 'a': 'i', 'e': 'l'}
  t2s = {'t': 'p', 'i': 'a', 'l': 'e'}

i=4, a='r', b='e':
  s2t = {'p': 't', 'a': 'i', 'e': 'l', 'r': 'e'}
  t2s = {'t': 'p', 'i': 'a', 'l': 'e', 'e': 'r'}

循环结束,返回 true

输出: true

2.6 算法图解

复制代码
s = "egg"
t = "add"

字符对应关系:
位置:  0  1  2
s:     e  g  g
t:     a  d  d
       ↓  ↓  ↓
映射:  e→a g→d g→d

检查:
位置0: e→a, a→e ✓
位置1: g→d, d→g ✓
位置2: g→d, d→g ✓ (与位置1一致)

结果:true

s = "foo"
t = "bar"

字符对应关系:
位置:  0  1  2
s:     f  o  o
t:     b  a  r
       ↓  ↓  ↓
映射:  f→b o→a o→r?

检查:
位置0: f→b, b→f ✓
位置1: o→a, a→o ✓
位置2: o→r, 但o已映射到'a'
       'a' != 'r',冲突!

结果:false

2.7 复杂度分析

分析维度 复杂度 说明
时间复杂度 O(n) n是字符串长度
空间复杂度 O(1) 字符集有限(ASCII)

2.8 边界情况

s t 说明 输出
"a" "a" 单字符 true
"ab" "aa" 多对一 false
"aa" "ab" 一对多 false
"" "" 空字符串 true

3. 两题对比与总结

3.1 算法对比

对比项 单词规律 同构字符串
核心算法 双向映射 双向映射
数据结构 两个HashMap 两个HashMap
映射类型 字符→字符串 字符→字符
时间复杂度 O(n) O(n)
空间复杂度 O(n) O(1)

3.2 双向映射模板

java 复制代码
// 双向映射标准模板
Map<TypeA, TypeB> a2b = new HashMap<>();
Map<TypeB, TypeA> b2a = new HashMap<>();

for(int i = 0; i < length; i++){
    TypeA a = getA(i);
    TypeB b = getB(i);

    // 检查a→b的映射
    if(a2b.containsKey(a) && !a2b.get(a).equals(b)){
        return false;
    }

    // 检查b→a的映射
    if(b2a.containsKey(b) && !b2a.get(b).equals(a)){
        return false;
    }

    // 建立双向映射
    a2b.put(a, b);
    b2a.put(b, a);
}

return true;

3.3 为什么需要双向映射

单向映射的问题

复制代码
只有s→t的映射:
s = "foo"
t = "bar"

i=0: f→b
     s2t = {'f': 'b'}

i=1: o→a
     s2t = {'f': 'b', 'o': 'a'}

i=2: o→r
     s2t包含'o',映射到'a'
     'a' != 'r',检测到冲突 ✓

但在某些情况下:
s = "ab"
t = "aa"

i=0: a→a
     s2t = {'a': 'a'}

i=1: b→a
     s2t不包含'b',可以映射
     s2t = {'a': 'a', 'b': 'a'}

问题:'a'被两个字符映射!
需要反向检查才能发现

双向映射的必要性

检查项 s→t映射 t→s映射
一对多 无法检测 可以检测
多对一 可以检测 无法检测
完全检测 需要两者 需要两者

3.4 HashMap的containsKey与get

java 复制代码
// 检查映射是否冲突
if(map.containsKey(key) && !map.get(key).equals(value)){
    // key已存在,但映射到不同的value
}

// 等价写法
if(map.containsKey(key)){
    if(!map.get(key).equals(value)){
        // 冲突
    }
}

为什么不能只用get

java 复制代码
// 错误写法
if(map.get(key) != value){
    // 如果key不存在,get返回null
    // null != value会误判为冲突
}

3.5 String比较的坑

java 复制代码
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");

// == 比较引用
s1 == s2;  // true (字符串常量池)
s1 == s3;  // false (不同对象)

// equals比较内容
s1.equals(s2);  // true
s1.equals(s3);  // true

// 所以必须用equals
if(!s2t.get(a).equals(b)){
    // 正确
}

4. 总结

今天我们学习了两道映射匹配题目:

  1. 单词规律:掌握双向映射处理模式匹配,理解字符串与字符的映射关系
  2. 同构字符串:掌握字符映射检查,理解双向一致性验证

核心收获

  • 双向映射是解决一一对应问题的关键
  • HashMap可以高效建立和检查映射关系
  • containsKey配合get可以检测映射冲突
  • String比较必须使用equals,而非==

练习建议

  1. 尝试用数组代替HashMap(假设字符集是ASCII)
  2. 思考如果允许一对多映射,应该如何修改
  3. 尝试找出所有满足条件的映射方案

参考资源

文章标签

#LeetCode #算法 #Java #HashMap #字符串

喜欢这篇文章吗?别忘了点赞、收藏和分享!你的支持是我创作的最大动力!

相关推荐
攻心的子乐3 小时前
Spring IOC 源码
java·后端·spring
Sirens.3 小时前
Java异常处理解析:从防御式编程到自定义异常类
java·开发语言·笔记·学习·github·javac
lsx2024063 小时前
MySQL 运算符
开发语言
逆境清醒3 小时前
python教程总目录(更新中ing。。。)
开发语言·python
千寻技术帮3 小时前
10351_基于Springboot的二手交易平台
java·spring boot·mysql·毕业设计·源码·代码·二手交易
CC.GG3 小时前
【Qt】常用控件----显示类控件(QLabel、QLCDNumber、QProgressBar、QCalendarWidget)
开发语言·数据库·qt
程芯带你刷C语言简单算法题3 小时前
Day43~实现一个算法求一个数字的树根
c语言·开发语言·算法·c
alonewolf_993 小时前
Spring依赖注入源码深度解析:从@Autowired到@Resource的完整实现机制
java·后端·spring
雪碧聊技术4 小时前
如何界定人工智能和java开发二者的关系?
java·人工智能·二者关系界定
Chase_______4 小时前
【JAVA基础指南(四)】快速掌握类和对象
java·开发语言