

文章目录
摘要
这道题其实挺有意思的,它要求我们找出两个字符串之间的差异。字符串 t 是由字符串 s 随机重排后,再在随机位置添加一个字母得到的。我们需要找出这个被添加的字母。
听起来简单,但实际做起来还是需要一些技巧的。关键点在于如何高效地找出两个字符串之间的差异。我们可以用字符计数的方法,也可以用异或运算,还可以用字符相减的方法。今天我们就用 Swift 来搞定这道题,顺便聊聊这种字符差异检测的方法在实际开发中的应用场景,比如数据校验、文件对比、版本控制等等。

描述
题目要求是这样的:给定两个字符串 s 和 t,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。请找出在 t 中被添加的字母。
示例 1:
输入: s = "abcd", t = "abcde"
输出: "e"
解释: 'e' 是那个被添加的字母。
示例 2:
输入: s = "", t = "y"
输出: "y"
提示:
0 <= s.length <= 1000t.length == s.length + 1s和t只包含小写字母
这道题的核心思路是什么呢?由于 t 比 s 多一个字符,我们可以统计两个字符串中每个字符的出现次数,然后找出出现次数不同的字符。或者,我们可以利用异或运算的性质,将所有字符进行异或,最后剩下的就是被添加的字符。

题解答案
下面是完整的 Swift 解决方案:
swift
class Solution {
func findTheDifference(_ s: String, _ t: String) -> Character {
// 方法一:字符计数法
return findTheDifferenceByCount(s, t)
// 方法二:异或运算法
// return findTheDifferenceByXOR(s, t)
// 方法三:字符相减法
// return findTheDifferenceBySubtract(s, t)
}
// 方法一:字符计数法
func findTheDifferenceByCount(_ s: String, _ t: String) -> Character {
// 统计 s 中每个字符的出现次数
var charCount: [Character: Int] = [:]
for char in s {
charCount[char, default: 0] += 1
}
// 遍历 t,每遇到一个字符就减 1
for char in t {
if let count = charCount[char], count > 0 {
charCount[char] = count - 1
} else {
// 如果字符不存在或数量为 0,说明是被添加的字符
return char
}
}
// 理论上不会执行到这里
return Character("")
}
// 方法二:异或运算法
func findTheDifferenceByXOR(_ s: String, _ t: String) -> Character {
var result: UInt8 = 0
// 对 s 中所有字符进行异或
for char in s {
result ^= char.asciiValue ?? 0
}
// 对 t 中所有字符进行异或
for char in t {
result ^= char.asciiValue ?? 0
}
// 由于 t 比 s 多一个字符,异或的结果就是被添加的字符
return Character(UnicodeScalar(result))
}
// 方法三:字符相减法
func findTheDifferenceBySubtract(_ s: String, _ t: String) -> Character {
var sum: Int = 0
// 计算 t 中所有字符的 ASCII 值之和
for char in t {
sum += Int(char.asciiValue ?? 0)
}
// 减去 s 中所有字符的 ASCII 值之和
for char in s {
sum -= Int(char.asciiValue ?? 0)
}
// 差值就是被添加字符的 ASCII 值
return Character(UnicodeScalar(sum)!)
}
}
题解代码分析
让我们一步步分析这个解决方案:
方法一:字符计数法
这是最直观的方法。我们统计 s 中每个字符的出现次数,然后遍历 t,每遇到一个字符就从计数中减 1。如果某个字符在 s 中不存在或者数量已经减到 0,说明这个字符是被添加的。
swift
func findTheDifferenceByCount(_ s: String, _ t: String) -> Character {
// 统计 s 中每个字符的出现次数
var charCount: [Character: Int] = [:]
for char in s {
charCount[char, default: 0] += 1
}
// 遍历 t,每遇到一个字符就减 1
for char in t {
if let count = charCount[char], count > 0 {
charCount[char] = count - 1
} else {
// 如果字符不存在或数量为 0,说明是被添加的字符
return char
}
}
return Character("")
}
执行过程示例:
假设 s = "abcd", t = "abcde":
- 统计
s:charCount = ['a': 1, 'b': 1, 'c': 1, 'd': 1] - 遍历
t:'a':charCount['a'] = 1 > 0,减 1,charCount['a'] = 0'b':charCount['b'] = 1 > 0,减 1,charCount['b'] = 0'c':charCount['c'] = 1 > 0,减 1,charCount['c'] = 0'd':charCount['d'] = 1 > 0,减 1,charCount['d'] = 0'e':charCount['e']不存在,返回'e'
方法二:异或运算法
这个方法利用了异或运算的性质:相同数字异或结果为 0,任何数字与 0 异或结果还是它本身。
swift
func findTheDifferenceByXOR(_ s: String, _ t: String) -> Character {
var result: UInt8 = 0
// 对 s 中所有字符进行异或
for char in s {
result ^= char.asciiValue ?? 0
}
// 对 t 中所有字符进行异或
for char in t {
result ^= char.asciiValue ?? 0
}
// 由于 t 比 s 多一个字符,异或的结果就是被添加的字符
return Character(UnicodeScalar(result))
}
执行过程示例:
假设 s = "abcd", t = "abcde":
- 对
s异或:result = 'a' ^ 'b' ^ 'c' ^ 'd' - 对
t异或:result = result ^ 'a' ^ 'b' ^ 'c' ^ 'd' ^ 'e' - 由于异或满足交换律和结合律:
result = ('a' ^ 'a') ^ ('b' ^ 'b') ^ ('c' ^ 'c') ^ ('d' ^ 'd') ^ 'e' = 0 ^ 0 ^ 0 ^ 0 ^ 'e' = 'e'
所以最终结果是 'e'。
为什么这个方法有效?
因为 t 包含了 s 的所有字符,再加上一个额外的字符。当我们对 s 和 t 中的所有字符进行异或时,s 中的每个字符都会在 t 中出现一次(除了被添加的字符),所以它们会两两抵消,最后剩下的就是被添加的字符。
方法三:字符相减法
这个方法计算两个字符串所有字符的 ASCII 值之和的差值。
swift
func findTheDifferenceBySubtract(_ s: String, _ t: String) -> Character {
var sum: Int = 0
// 计算 t 中所有字符的 ASCII 值之和
for char in t {
sum += Int(char.asciiValue ?? 0)
}
// 减去 s 中所有字符的 ASCII 值之和
for char in s {
sum -= Int(char.asciiValue ?? 0)
}
// 差值就是被添加字符的 ASCII 值
return Character(UnicodeScalar(sum)!)
}
执行过程示例:
假设 s = "abcd", t = "abcde":
- 计算
t的 ASCII 和:sum = 'a' + 'b' + 'c' + 'd' + 'e' - 减去
s的 ASCII 和:sum = sum - ('a' + 'b' + 'c' + 'd') = 'e' - 返回
'e'
三种方法的对比
| 方法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 字符计数法 | O(n) | O(k) | 直观易懂,容易扩展 | 需要额外空间 |
| 异或运算法 | O(n) | O(1) | 空间复杂度最优 | 需要理解异或运算 |
| 字符相减法 | O(n) | O(1) | 实现简单 | 如果差值很大可能溢出 |
对于这道题,三种方法都可以,但异或运算法和字符相减法的空间复杂度更优。
示例测试及结果
让我们用几个例子来测试一下这个解决方案:
示例 1:s = "abcd", t = "abcde"
执行过程(字符计数法):
- 统计
s:charCount = ['a': 1, 'b': 1, 'c': 1, 'd': 1] - 遍历
t:'a':计数减 1,charCount['a'] = 0'b':计数减 1,charCount['b'] = 0'c':计数减 1,charCount['c'] = 0'd':计数减 1,charCount['d'] = 0'e':charCount['e']不存在,返回'e'
**结果:**返回 'e'
示例 2:s = "", t = "y"
执行过程:
- 统计
s:charCount = [:](空字典) - 遍历
t:'y':charCount['y']不存在,返回'y'
**结果:**返回 'y'
示例 3:s = "a", t = "aa"
执行过程:
- 统计
s:charCount = ['a': 1] - 遍历
t:'a':计数减 1,charCount['a'] = 0'a':charCount['a'] = 0,不满足count > 0,返回'a'
**结果:**返回 'a'
示例 4:s = "abcd", t = "aebcd"
执行过程:
- 统计
s:charCount = ['a': 1, 'b': 1, 'c': 1, 'd': 1] - 遍历
t:'a':计数减 1,charCount['a'] = 0'e':charCount['e']不存在,返回'e'
**结果:**返回 'e'
时间复杂度
让我们分析一下这三种方法的时间复杂度:
时间复杂度:O(n)
其中 n 是字符串 s 和 t 的长度(t.length = n + 1)。
分析:
- 字符计数法 :需要遍历
s一次(O(n))和t一次(O(n+1)),总时间复杂度 O(n) - 异或运算法 :需要遍历
s一次(O(n))和t一次(O(n+1)),总时间复杂度 O(n) - 字符相减法 :需要遍历
t一次(O(n+1))和s一次(O(n)),总时间复杂度 O(n)
对于题目约束(s.length <= 1000),这个时间复杂度是完全可接受的。
空间复杂度
让我们分析一下这三种方法的空间复杂度:
字符计数法:O(k)
其中 k 是字符串中不同字符的个数。由于题目说明只包含小写字母,所以 k <= 26,空间复杂度实际上是 O(1)。
异或运算法:O(1)
只需要一个变量来存储异或结果,空间复杂度是 O(1)。
字符相减法:O(1)
只需要一个变量来存储 ASCII 值的和,空间复杂度是 O(1)。
实际应用场景
这种字符差异检测的方法在实际开发中应用非常广泛:
场景一:数据校验
在数据传输或存储中,我们经常需要检测数据是否被篡改。可以通过对比原始数据和接收到的数据,找出差异:
swift
func detectDataTampering(_ original: String, _ received: String) -> Character? {
if received.count != original.count + 1 {
return nil
}
let solution = Solution()
return solution.findTheDifference(original, received)
}
场景二:文件对比
在文件对比工具中,我们需要找出两个文件之间的差异。虽然实际的文件对比更复杂,但基本的字符差异检测是基础:
swift
func findFileDifference(_ file1: String, _ file2: String) -> [Character] {
// 简化版本:找出 file2 中比 file1 多出的字符
var differences: [Character] = []
// 实际实现会更复杂,需要考虑多行、格式等
return differences
}
场景三:版本控制
在版本控制系统中,我们需要检测文件的变化。虽然实际系统更复杂,但字符差异检测是基础功能之一:
swift
func detectVersionChange(_ oldVersion: String, _ newVersion: String) -> ChangeInfo {
// 检测版本之间的变化
let solution = Solution()
let addedChar = solution.findTheDifference(oldVersion, newVersion)
return ChangeInfo(added: addedChar)
}
场景四:字符串匹配
在字符串匹配算法中,我们经常需要找出两个字符串之间的差异。这道题的解法可以作为字符串匹配的基础:
swift
func findStringDifference(_ s1: String, _ s2: String) -> [Character] {
// 找出 s2 中比 s1 多出的字符
var result: [Character] = []
// 可以扩展为找出所有差异
return result
}
总结
这道题虽然看起来简单,但实际上涉及了很多重要的算法思想:字符计数、异或运算、字符相减等。通过不同的方法,我们可以从不同角度解决同一个问题。
关键点总结:
- 字符计数法:最直观,容易理解和扩展
- 异或运算法:空间复杂度最优,利用了异或运算的性质
- 字符相减法:实现简单,但需要注意溢出问题
算法优势:
- 时间复杂度低:只需要遍历字符串一次或两次,O(n)
- 空间复杂度可控:异或和相减法都是 O(1),计数法是 O(k),对于小写字母来说是 O(1)
- 实现简单:三种方法都容易实现
实际应用:
字符差异检测的方法在很多场景中都有应用,比如数据校验、文件对比、版本控制、字符串匹配等。理解这些方法,可以帮助我们解决很多类似的问题。