Swift 数据结构与算法( ) + Leetcode 掘金 #日新计划更文活动
题目
给定两个字符串 s 和 t ,判断它们是否是同构的。
如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
每个出现的字符都应当映射到另一个字符,同时不改变字符的顺序。不同字符不能映射到同一个字符上,相同字符只能映射到同一个字符上,字符可以映射到自己本身。
示例 1:
输入: s = "egg", t = "add"
输出: true
示例 2:
输入: s = "foo", t = "bar"
输出: false
示例 3:
输入: s = "paper", t = "title"
输出: true
提示:
1 <= s.length <= 5 * 104t.length == s.lengths和t由任意有效的 ASCII 字符组成
解题思路🙋🏻 ♀️
1. 分析题目
题目要求:
给定两个字符串 s 和 t,我们需要判断这两个字符串是否是同构的。同构的定义是:如果字符串 s 可以通过一个字符到字符的映射被转换为字符串 t,则它们是同构的。
函数返回值:
函数需要返回一个布尔值:true 表示两个字符串是同构的,false 表示它们不是。
题目类型:
这是一个哈希映射问题。在 LeetCode 上,这种问题通常使用哈希表来解决,因为哈希表可以帮助我们轻松地存储和查找字符之间的映射关系。
2. 解题思路
为了确定两个字符串是否是同构的,我们需要确保:
- 对于字符串
s中的每个字符,它都映射到字符串t中的一个特定字符。 - 对于字符串
t中的每个字符,它也映射到字符串s中的一个特定字符。
我们可以使用两个哈希表来跟踪这两种映射。
示例:
考虑 s = "paper" 和 t = "title"。
[ \begin{align*} s & : \text{"paper"} \ t & : \text{"title"} \end{align*} ]
步骤:
- 我们先看字符串
s的第一个字符 'p'。在哈希表中,我们没有为 'p' 设置映射,所以我们将 'p' 映射到 't'。 - 接下来,我们看 'a'。我们再次没有为 'a' 设置映射,所以我们将 'a' 映射到 'i'。
- 之后是 'p'。我们已经为 'p' 设置了映射,所以我们检查它是否映射到 't'。因为它确实是,所以我们继续。
- 对于 'e',我们将其映射到 'l',对于 'r',我们将其映射到 'e'。
我们还需要确保反过来也是这样。即,'t' 被映射到 'p','i' 被映射到 'a',等等。
由于在整个过程中,我们没有遇到任何不一致的映射,所以这两个字符串是同构的。
哈希映射图:
s -> t
-------------
p -> t
a -> i
p -> t
e -> l
r -> e
t -> s
-------------
t -> p
i -> a
t -> p
l -> e
e -> r
边界思考🤔
代码
func isIsomorphic(_ s: String, _ t: String) -> Bool {
// 使用两个字典来分别存储从 s 到 t 和从 t 到 s 的字符映射
var mapS = [Character: Character]()
var mapT = [Character: Character]()
// 将字符串转为字符数组方便遍历
let sChars = Array(s)
let tChars = Array(t)
// 遍历字符数组
for i in 0..<sChars.count {
let charS = sChars[i]
let charT = tChars[i]
// 检查从 s 到 t 的映射是否存在
if let mappedChar = mapS[charS] {
if mappedChar != charT { // 如果映射已存在但不匹配当前的字符,则返回 false
return false
}
} else {
mapS[charS] = charT
}
// 检查从 t 到 s 的映射是否存在
if let mappedChar = mapT[charT] {
if mappedChar != charS { // 如果映射已存在但不匹配当前的字符,则返回 false
return false
}
} else {
mapT[charT] = charS
}
}
// 如果所有字符都匹配,则返回 true
return true
}
时空复杂度分析
错误与反思
代码中,我们使用了一个单循环,并在每次迭代中处理 s 和 t 中的相应字符。例如,当我们在循环的第 i 次迭代时,我们会处理 s[i] 和 t[i]。
这样做的好处是,我们可以在单次遍历中完成所有工作,而不需要两次单独的遍历。这不仅使代码更简洁,而且效率更高。
让我给你一个更详细的步骤说明:
-
初始化两个映射
mapS和mapT。 -
开始遍历字符串
s。 -
对于每个字符
charS在s和charT在t:- 检查
charS是否已经在mapS中有映射。如果有,确保它映射到的是charT。如果不是,返回false。 - 如果
charS还没有映射,将其映射到charT。 - 使用相同的逻辑处理从
t到s的映射。
- 检查
-
如果循环完成并且没有返回
false,则返回true。
通过这种方式,我们可以确保每对字符都被检查,而无需进行两次独立的遍历。
2.
if let mappedChar = mapS[charS]{ if mappedChar != charT { return false } } else { mapS[charS] = charT } if let mappedChar = mapT[charT]{ if mappedChar != charS { return false } } else { mapT[charT] = charS }
这两段代码是在一个大的循环中,它们的目的是确保双向映射的完整性和唯一性。也就是说,我们不仅要确保从字符串 s 到字符串 t 的映射是唯一的,而且还要确保从字符串 t 到字符串 s 的映射也是唯一的。
-
从 s 到 t 的映射:
- 如果字符
charS已经在mapS中有一个映射,那么我们需要确保这个映射指向的字符与当前遍历到的字符charT是相同的。如果不是,则两个字符串不是同构的,直接返回false。 - 如果字符
charS还没有映射,我们就在mapS中为它创建一个映射,指向字符charT。
- 如果字符
-
从 t 到 s 的映射:
- 同理,如果字符
charT已经在mapT中有一个映射,我们需要确保这个映射指向的字符与当前遍历到的字符charS是相同的。如果不是,则两个字符串不是同构的,直接返回false。 - 如果字符
charT还没有映射,我们就在mapT中为它创建一个映射,指向字符charS。
- 同理,如果字符
考虑下面的示例来说明为什么需要两个映射:
假设 s = "ab" 和 t = "aa"。
仅使用从 s 到 t 的映射时,我们得到:
a映射到ab映射到a
此时,我们可能会错误地认为这两个字符串是同构的,因为我们没有检查从 t 到 s 的映射。但是,当我们检查从 t 到 s 的映射时,我们发现:
a既可以映射到a又可以映射到b
这就违反了同构的定义,所以我们需要返回 false。
这就是为什么我们需要两次遍历来确保双向映射的唯一性。
概念
可以使用“语言翻译”作为比喻来解释这个方法。
假设正在学习两种全新的外语:语言A和语言B。为了帮助你学习,你决定创建一本小词典,将语言A的词汇映射到语言B的词汇。
当你遇到一个新单词时,你会查看词典以确定是否已经有对应的翻译。如果有,你会确保这个翻译是正确的。但如果没有,你会添加一个新的条目。
这听起来很简单,但有一个附加的挑战:确保翻译是双向一致的。也就是说,当你从语言A翻译到语言B时,反向翻译也应该是正确的。为了确保这一点,你需要另一本词典,将语言B的词汇映射到语言A的词汇。
现在,将这个情境与我们的问题相比较:
- 字符串
s代表语言A,字符串t代表语言B。 mapS是从语言A到语言B的词典,mapT是从语言B到语言A的词典。- 当我们遍历字符串并检查映射时,这就像在两本词典中查找单词,确保翻译是一致的。
因此,这个方法就像是在创建和检查一个双向词典,确保两种语言之间的每个单词都有一个一致的翻译。这种双向的核实确保了同构的特性。
使用场景与应用
1. 需要学习的概念及其应用到实际场景:
核心概念: 双向映射验证
这一题的核心概念是建立两个字符串之间的映射关系,并确保这种映射在两个方向上都是一致的。只有当两个映射都满足条件时,两个字符串才被认为是同构的。
实际应用场景:
-
数据同步:当在两个不同的系统或数据库之间同步数据时,你需要确保数据在两边都是一致的。例如,在主数据库和备份数据库之间,或在云服务器和本地服务器之间。
技术点: 使用双向哈希表或字典来存储和验证数据的映射关系。
-
双向认证:在安全领域,双方可能需要验证彼此的身份。例如,客户端和服务器之间的SSL握手。
技术点: 证书和公钥基础设施 (PKI)。
-
语言翻译软件:在翻译软件中,你可能需要确保从语言A翻译到语言B的结果可以被准确地翻译回语言A。
技术点: 使用双向字典和机器学习算法来优化翻译。
2. iOS app 开发的实际使用场景:
-
用户设置同步:当用户在iOS设备上更改设置,并希望这些设置在其他Apple设备上也得到同步时(如iPad,Mac等)。
技术应用: 使用iCloud来存储用户的设置,并确保在每个设备上的设置都是一致的。可以使用双向映射验证来确保数据的一致性。
-
双向数据绑定:在某些iOS应用中,用户界面元素的状态可能需要与后台数据模型保持同步。例如,一个开关的状态可能与一个布尔变量绑定。
技术应用: 使用Swift中的数据绑定技术,如Combine框架或Observable对象。这种双向数据绑定确保了UI和数据模型之间的一致性。
-
社交应用中的消息同步:当用户在一个设备上发送消息,并希望在其他设备上也能看到这条消息。
技术应用: 使用WebSocket或其他实时通信技术来同步消息。双向映射验证可以确保消息在所有设备上都是一致的。