题目
给定两个字符串 s
和 t
,它们只包含小写字母。
字符串 t
由字符串 s
随机重排,然后在随机位置添加一个字母。
请找出在 t
中被添加的字母。
示例 1:
输入:s = "abcd", t = "abcde" 输出:"e" 解释:'e' 是那个被添加的字母。
示例 2:
输入:s = "", t = "y" 输出:"y"
提示:
0 <= s.length <= 1000
t.length == s.length + 1
s
和t
只包含小写字母
详解
此题目涉及到一个非常基础且重要的知识点:异或,下面我将先带大家复习异或的性质,再对题目以及代码做出最详细的解释
异或运算(XOR,全称为“exclusive OR”)是一种二进制运算,它接受两个操作数,返回一个结果。异或运算的特性是,如果两个相应的二进制位相同,则结果为 0;如果不同,则结果为 1。异或运算在计算机科学和数字电子中有着广泛的应用。
异或
异或运算的真值表
对于两个二进制位 ( A ) 和 ( B ),异或运算的结果 ( A \oplus B ) 可以表示如下:
( A ) | ( B ) | ( A \oplus B ) |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
异或运算的性质
- 交换律:( A \oplus B = B \oplus A )
- 结合律:( (A \oplus B) \oplus C = A \oplus (B \oplus C) )
- 自逆性:( A \oplus A = 0 )
- 单位元:( A \oplus 0 = A )
- 分配律:( A \oplus (B \land C) = (A \oplus B) \land (A \oplus C) )
异或运算的应用
- 数据加密:异或运算常用于简单的加密算法,如一次一密(OTP)。
- 错误检测和校正:在通信和存储中,异或运算用于奇偶校验位的计算。
- 数字逻辑设计:在数字电路中,异或门是基本的逻辑门之一。
- 计算机图形学:用于位图图像的处理,如图像的翻转和透明度计算。
- 算法设计:在一些算法中,异或运算用于高效地交换两个变量的值,而不需要额外的存储空间。
示例
假设我们有两个二进制数 ( 0110 ) 和 ( 1100 ),进行异或运算的过程如下:
0110
⊕ 1100
------
1010
逐位进行异或运算,得到结果 ( 1010 )。
在编程中的应用
在编程中,异或运算通常用符号 ^
表示。例如,在 C、C++、Java 和 Python 中,^
运算符用于执行异或运算。
int a = 6; // 二进制表示为 0110
int b = 12; // 二进制表示为 1100
int result = a ^ b; // 结果为 10 (二进制 1010)
解题思路
题目中有两个显而易见的要点:
i.两个字符串只包含小写字母;
ii.字符串t是随机重排;
思路一:for循环去遍历这两个字符串,可是由于随机重排,导致每个s字符串的元素都要遍历一遍t字符串,这样运行下去时间复杂度太高了,太麻烦了。
思路二:对两个字符串根据ascii码进行冒泡排序,再一一对比,也很麻烦。
下面用刚刚学习的异或操作来处理,附带代码详细执行逻辑
思路
-
异或运算的性质:
- 异或运算(XOR)有一个重要的性质:对于任意两个相同的数
a
,有a XOR a = 0
。 - 另一个性质是:对于任意数
a
,有a XOR 0 = a
。 - 这些性质使得异或运算非常适合用于查找不同的元素。
- 异或运算(XOR)有一个重要的性质:对于任意两个相同的数
-
算法流程:
- 初始化一个字符
c
为 0。 - 遍历字符串
s
的每一个字符,同时对应地获取字符串t
中的字符。 - 对这些字符进行异或运算,将结果累积在
c
中。 - 最后,再对
t
中多出的一个字符进行异或运算。 - 返回最终的字符
c
,它就是t
中比s
多出的那个字符。
- 初始化一个字符
代码解释
public class Solution {
public char findTheDifference(String s, String t) {
// 初始化字符 c 为 0
char c = 0;
// 遍历字符串 s 的每一个字符
for (int i = 0; i < s.length(); ++i) {
// 对 s 中的字符进行异或运算
c ^= s.charAt(i);
// 对 t 中的对应字符进行异或运算
c ^= t.charAt(i);
}
// 对 t 中多出的一个字符进行异或运算
c ^= t.charAt(s.length());
// 返回多出的字符
return c;
}
}
收起
- 初始化:
char c = 0;
初始化一个字符c
为 0,用于存储最终的结果。 - 遍历字符串:
for (int i = 0; i < s.length(); ++i)
遍历字符串s
的每一个字符。 - 异或运算:
c ^= s.charAt(i);
对s
中的字符进行异或运算。c ^= t.charAt(i);
对t
中的对应字符进行异或运算。
- 处理多出的字符:
c ^= t.charAt(s.length());
对t
中多出的一个字符进行异或运算。 - 返回结果:
return c;
返回最终的字符c
。
为什么有效
由于 s
和 t
只相差一个字符,且 t
是由 s
添加一个字符后打乱顺序得到的,因此通过异或运算可以有效地找出这个多出的字符。所有相同的字符在异或运算后都会抵消为 0,最终的结果就是那个多出的字符。
代码详细执行过程
详细分析一下当 s
是 "asdf"
而 t
是 "dsafg"
时,异或运算的过程。
输入
s = "asdf"
t = "dsafg"
目标
找出 t
中比 s
多出的字符。
步骤
-
初始化:
char c = 0;
初始化字符c
为 0。
-
遍历字符串:
- 使用
for
循环遍历s
的每一个字符,同时对t
中的对应字符进行异或运算。
- 使用
-
异或运算:
c ^= s.charAt(i);
对s
中的字符进行异或运算。c ^= t.charAt(i);
对t
中的对应字符进行异或运算。
-
处理多出的字符:
c ^= t.charAt(s.length());
对t
中多出的一个字符进行异或运算。
-
返回结果:
return c;
返回最终的字符c
。
详细过程
假设我们用 ASCII 码表示字符进行运算(因为 char
在 Java 中是 16 位的 Unicode 字符,但为了简化说明,我们用 ASCII 码表示):
'a'
的 ASCII 码是 97's'
的 ASCII 码是 115'd'
的 ASCII 码是 100'f'
的 ASCII 码是 102
循环过程(小写字母转ASCII码再转二进制进行异或)
-
i = 0:
c ^= s.charAt(0);
→c = 0 XOR 97
→c = 97
c ^= t.charAt(0);
→c = 97(01100001) XOR 100
(01100100)→c = 5(00000101)
-
i = 1:
c ^= s.charAt(1);
→c = 5(00000101) XOR 115
(01110011)→c = 118(01110110)
c ^= t.charAt(1);
→c = 118(01110110) XOR 115
(01110011) →c = 5(00000101)
-
i = 2:
c ^= s.charAt(2);
→c = 5(00000101) XOR 100
(01100100)→c = 97(01100001)
c ^= t.charAt(2);
→c = 97 XOR 97
→c = 0
-
i = 3:
c ^= s.charAt(3);
→c = 0 XOR 102
→c = 102
c ^= t.charAt(3);
→c = 102 XOR 102
→c = 0
处理多出的字符
c ^= t.charAt(s.length());
→c = 0 XOR 103
→c = 103
,其中 103 是'g'
的 ASCII 码。
最终结果
return c;
返回字符c
,即'g'
。
总结
通过上述过程,我们可以看到,尽管 t
是通过对 s
进行重排得到的,但由于 t
中多出一个字符 'g'
,在异或运算的过程中,所有相同的字符都会被抵消,最终的结果就是多出的字符 'g'
。