LeetCode 389 找不同 - Swift 题解


文章目录

摘要

这道题其实挺有意思的,它要求我们找出两个字符串之间的差异。字符串 t 是由字符串 s 随机重排后,再在随机位置添加一个字母得到的。我们需要找出这个被添加的字母。

听起来简单,但实际做起来还是需要一些技巧的。关键点在于如何高效地找出两个字符串之间的差异。我们可以用字符计数的方法,也可以用异或运算,还可以用字符相减的方法。今天我们就用 Swift 来搞定这道题,顺便聊聊这种字符差异检测的方法在实际开发中的应用场景,比如数据校验、文件对比、版本控制等等。

描述

题目要求是这样的:给定两个字符串 st,它们只包含小写字母。字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。请找出在 t 中被添加的字母。

示例 1:

复制代码
输入: s = "abcd", t = "abcde"
输出: "e"
解释: 'e' 是那个被添加的字母。

示例 2:

复制代码
输入: s = "", t = "y"
输出: "y"

提示:

  • 0 <= s.length <= 1000
  • t.length == s.length + 1
  • st 只包含小写字母

这道题的核心思路是什么呢?由于 ts 多一个字符,我们可以统计两个字符串中每个字符的出现次数,然后找出出现次数不同的字符。或者,我们可以利用异或运算的性质,将所有字符进行异或,最后剩下的就是被添加的字符。

题解答案

下面是完整的 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"

  1. 统计 scharCount = ['a': 1, 'b': 1, 'c': 1, 'd': 1]
  2. 遍历 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"

  1. s 异或:result = 'a' ^ 'b' ^ 'c' ^ 'd'
  2. t 异或:result = result ^ 'a' ^ 'b' ^ 'c' ^ 'd' ^ 'e'
  3. 由于异或满足交换律和结合律:result = ('a' ^ 'a') ^ ('b' ^ 'b') ^ ('c' ^ 'c') ^ ('d' ^ 'd') ^ 'e' = 0 ^ 0 ^ 0 ^ 0 ^ 'e' = 'e'

所以最终结果是 'e'

为什么这个方法有效?

因为 t 包含了 s 的所有字符,再加上一个额外的字符。当我们对 st 中的所有字符进行异或时,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"

  1. 计算 t 的 ASCII 和:sum = 'a' + 'b' + 'c' + 'd' + 'e'
  2. 减去 s 的 ASCII 和:sum = sum - ('a' + 'b' + 'c' + 'd') = 'e'
  3. 返回 'e'

三种方法的对比

方法 时间复杂度 空间复杂度 优点 缺点
字符计数法 O(n) O(k) 直观易懂,容易扩展 需要额外空间
异或运算法 O(n) O(1) 空间复杂度最优 需要理解异或运算
字符相减法 O(n) O(1) 实现简单 如果差值很大可能溢出

对于这道题,三种方法都可以,但异或运算法和字符相减法的空间复杂度更优。

示例测试及结果

让我们用几个例子来测试一下这个解决方案:

示例 1:s = "abcd", t = "abcde"

执行过程(字符计数法):

  1. 统计 scharCount = ['a': 1, 'b': 1, 'c': 1, 'd': 1]
  2. 遍历 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"

执行过程:

  1. 统计 scharCount = [:](空字典)
  2. 遍历 t
    • 'y'charCount['y'] 不存在,返回 'y'

**结果:**返回 'y'

示例 3:s = "a", t = "aa"

执行过程:

  1. 统计 scharCount = ['a': 1]
  2. 遍历 t
    • 'a':计数减 1,charCount['a'] = 0
    • 'a'charCount['a'] = 0,不满足 count > 0,返回 'a'

**结果:**返回 'a'

示例 4:s = "abcd", t = "aebcd"

执行过程:

  1. 统计 scharCount = ['a': 1, 'b': 1, 'c': 1, 'd': 1]
  2. 遍历 t
    • 'a':计数减 1,charCount['a'] = 0
    • 'e'charCount['e'] 不存在,返回 'e'

**结果:**返回 'e'

时间复杂度

让我们分析一下这三种方法的时间复杂度:

时间复杂度:O(n)

其中 n 是字符串 st 的长度(t.length = n + 1)。

分析:

  1. 字符计数法 :需要遍历 s 一次(O(n))和 t 一次(O(n+1)),总时间复杂度 O(n)
  2. 异或运算法 :需要遍历 s 一次(O(n))和 t 一次(O(n+1)),总时间复杂度 O(n)
  3. 字符相减法 :需要遍历 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
}

总结

这道题虽然看起来简单,但实际上涉及了很多重要的算法思想:字符计数、异或运算、字符相减等。通过不同的方法,我们可以从不同角度解决同一个问题。

关键点总结:

  1. 字符计数法:最直观,容易理解和扩展
  2. 异或运算法:空间复杂度最优,利用了异或运算的性质
  3. 字符相减法:实现简单,但需要注意溢出问题

算法优势:

  1. 时间复杂度低:只需要遍历字符串一次或两次,O(n)
  2. 空间复杂度可控:异或和相减法都是 O(1),计数法是 O(k),对于小写字母来说是 O(1)
  3. 实现简单:三种方法都容易实现

实际应用:

字符差异检测的方法在很多场景中都有应用,比如数据校验、文件对比、版本控制、字符串匹配等。理解这些方法,可以帮助我们解决很多类似的问题。

相关推荐
寻寻觅觅☆2 小时前
东华OJ-基础题-124-分数化小数(C++)-难度中
开发语言·c++·算法
3Bronze1Pyramid3 小时前
【RNAErnie 大模型】
人工智能·深度学习·算法
Дерек的学习记录10 小时前
C++:入门基础(下)
开发语言·数据结构·c++·学习·算法·visualstudio
yugi98783810 小时前
无线传感器网络中GAF算法节点特性分析
网络·算法
1027lonikitave10 小时前
使用斐波那契数列讲解尾递归
算法
滴滴答滴答答12 小时前
LeetCode Hot100 之 16 合并两个有序链表
算法·leetcode·链表
ASKED_201912 小时前
企业级大模型微调(Fine-tuning)策略
大数据·人工智能·算法
圣保罗的大教堂12 小时前
leetcode 3713. 最长的平衡子串 I 中等
leetcode
t1987512812 小时前
基于Chirp分解和多相快速算法的离散分数傅里叶变换(DFRFT)MATLAB实现
开发语言·算法·matlab