前言
寻找独特数字卡片青训营X豆包MarsCode刷题打卡中分类为哈希表、简单级的问题。
哈希表是什么?
想知道哈希表,我们必须先了解一种新的存储方式 —— 散列技术。
散列技术是指在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每一个关键字都对应一个存储位置。即:存储位置 = f(关键字)。
这样,在查找的过程中,只需要通过这个对应关系f 找到给定值key的映射f(key)。只要集合中存在关键字和key相等的记录,则必在存储位置f(key)处。
我们把这种对应关系f 称为散列函数或哈希函数。
采用散列技术将记录存储在一块连续的存储空间中,这块连续的存储空间称为哈希表。
所得的存储地址称为哈希地址或散列地址。
哈希冲突
在理想的情况下,每一个关键字,通过哈希函数计算出来的地址都是不一样的。
但是在实际情况中,我们常常会碰到两个关键字key1≠key2,但是f(key1) = f(key2), 这种现象称为冲突。
哈希冲突的解决
哈希冲突不能避免,所以我们需要找到方法来解决它。
哈希冲突的解决方案主要有四种:开放地址法、再哈希、链地址法、公共溢出区法。
问题描述
在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上的数字是什么。
要求:
- 设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。
- 尽量减少额外空间的使用,以体现你的算法优化能力。
测试样例
- 样例 1:
输入:cards = [1, 1, 2, 2, 3, 3, 4, 5, 5]
输出:4
解释:拿到数字 4 的同学是唯一一个没有配对的。
- 样例 2:
输入:cards = [0, 1, 0, 1, 2]
输出:2
解释:数字 2 只出现一次,是独特的卡片。
- 样例 3:
输入:cards = [7, 3, 3, 7, 10]
输出:10
解释:10 是班级中唯一一个不重复的数字卡片。
约束条件
- 1 ≤ cards.length ≤ 1001
- 0 ≤ cards[i] ≤ 1000
- 班级人数为奇数
- 除了一个数字卡片只出现一次外,其余每个数字卡片都恰好出现两次
对问题的理解
需要在一个整数列表中找到唯一一个出现一次的数字。其他数字都恰好出现两次。
确定数据结构
由于题目要求时间复杂度为 O(n),并且尽量减少额外空间的使用,我们可以考虑使用位运算来解决这个问题。
算法步骤
异或运算
异或运算有一个重要的性质:a ^ a = 0 和 a ^ 0 = a。
这意味着,如果我们对列表中的所有数字进行异或运算,所有出现两次的数字会相互抵消,最终剩下的就是那个只出现一次的数字。
问题解答
def solution(cards):
unique_card = 0
for card in cards:
unique_card ^= card
return unique_card
if __name__ == "__main__":
print(solution([1, 1, 2, 2, 3, 3, 4, 5, 5]) == 4) # 输出 4
print(solution([0, 1, 0, 1, 2]) == 2) # 输出 2
print(solution([7, 3, 3, 7, 10]) == 10) # 输出 10
到此,问题算是正式解决。
而位运算要如何应用到编程中呢?
位运算主要是对整数在二进制位上进行操作。
- 明确位运算的操作符
• 与(&):两个相应二进制位都为1时,结果位才为1,如3 & 5,3的二进制是011,5的二进制是101,按位与后得到001,即1。
• 或(|):两个相应二进制位只要有一个为1,结果位就为1。
• 异或(^):两个相应二进制位不同时为1。
• 取反(~):将二进制位0变1、1变0。
• 左移(<<):将二进制数整体向左移动若干位,高位丢弃,低位补0,左移n位相当于乘以2ⁿ。
• 右移(>>):将二进制数整体向右移动若干位,低位丢弃。右移n位相当于除以2ⁿ(无符号数)或向下取整除以2ⁿ(有符号数)。
分析问题的特点
• 当问题涉及数字的二进制特性,如判断奇偶(偶数最后一位是0,奇数是1,可通过num & 1判断,结果为0是偶数)、数字的幂(x & (x - 1)可用于判断x是否为2的幂次方,如果结果为0,则x是2的幂)等,就可以考虑位运算。
转换思路
• 把问题从常规的算术运算或其他逻辑转换为位运算逻辑。例如,交换两个数的值,不用临时变量,可通过a = a ^ b; b = a ^ b; a = a ^ b;来实现,利用了异或运算的特性。
今天算是正式结束啦~