找单独的数之题解
题目概述
在一个班级中,每位同学都拿到了一张卡片,上面有一个整数。有趣的是,除了一个数字之外,所有的数字都恰好出现了两次。现在需要你帮助班长小C快速找到那个拿了独特数字卡片的同学手上的数字是什么。
要求:
- 设计一个算法,使其时间复杂度为 O(n),其中 n 是班级的人数。
- 尽量减少额外空间的使用,以体现你的算法优化能力,并编写对该算法的测试用例。
限制是
- 设计一个算法,使其时间复杂度为 O(n),其中n是数集元素的个数
- 1≤ cards.ength ≤ 1001
- 0≤ cards[i]≤ 1000
- 集合元素个数为奇数
- 除了一个数字只出现一次外,其余每个数字都恰好出现两次
解题思路
- 数据结构选择 因为题目要求时间复杂度为 O(n),并且尽量减少额外空间的使用,所以考虑使用位运算来解题。
- 算法选择
- 异或运算 应用异或运算的重要性质,a^a=0,a^0=a(会在后面展开解释),该性质可以保证对集合中所有元素依次进行异或运算后,得到的结果就是在寻找的特殊数
- 遍历数组 对数组进行遍历,确定没有遗漏的未参与异或运算的数字
- 最后把结果值返回
实现代码
public class Main{
public static int solution(int[] cards) {
int result = 0;
for (int card : cards) {
result ^= card;
}
return result;
}
public static void main(String[] args) {
System.out.println(solution(new int[]{7, 3, 3, 7, 10}) == 10);
System.out.println(solution(new int[]{1, 1, 2, 2, 3, 3, 4, 5, 5}) == 4);
System.out.println(solution(new int[]{0, 1, 0, 1, 2}) == 2);
}
}
输出范例
true
true
true
增强for循环的语法
for (元素类型 变量名 : 数组或集合) { // 循环体 }
具体运行过程
-
假设数组 cards 为测试用例里的 [7, 3, 3, 7, 10]: result 初始值为 0。
-
增强for循环遍历数组:
- 第一次:card = 7,result = 0 ^ 7 = 7
- 第二次:card = 3,result = 7 ^ 3 = 4
- 第三次:card = 3,result = 4 ^ 3 = 7
- 第四次:card = 7,result = 7 ^ 7 = 0
- 第五次:card = 10,result = 0 ^ 10 = 10
- 最终 result 的值为 10,即数组中唯一出现一次的元素。
- 增强for循环:for (int card : cards) 表示遍历数组 cards 中的每个元素,并将每个元素赋值给变量 card。
- 异或运算:result ^= card; 表示将 result 和当前数组元素 card 进行异或运算,并更新 result。
- 即
result ^= card; 将 result 和当前数组元素 card 进行异或运算,并更新 result。
增强for循环自动遍历数组中的每个元素,不需要手动管理索引。
异或运算的性质(引用部分摘自网络)
- 相同为0,不同为1:异或运算的规则是,如果两个位相同,结果为0;如果两个位不同,结果为1。
- 自反性:a ^ a = 0,即任何数与自身异或的结果为0。
- 恒等性:a ^ 0 = a,即任何数与0异或的结果为它本身。
- 如果一个数与a进行了两次异或运算,那么结果是这个数它本身。
- 1. 归零律:a ⊕ a = 0
- 2. 恒等律:a ⊕ 0 = a
- 3. 交换律:a ⊕ b = b ⊕ a
- 4. 结合律:a ⊕b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c
- 5. 自反:a ⊕ b ⊕ a = b
- d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c
- 7.若x是二进制数0101,y是二进制数1011; 则x⊕y=****1110
- 只有在两个比较的位不同时其结果是1,否则结果为0
测试用例的说明
考虑三种情况,即
- 其他数字成对连续出现,特殊数单独出现在数组的末尾或开头
- 其他数字成对连续出现,特殊数在某两对数字之间
- 其他数字乱序出现,特殊数在任意位置
写在第一题后的tips
- 位运算的含义:对二进制位进行操作的运算
常见的位运算符
- 按位与
&:两个位都为1时,结果为1,否则为0。- 按位或
|:两个位中只要有一个为1,结果为1,否则为0。- 按位异或
^:两个位相同为0,不同为1。- 按位取反
~:将每一位取反,1变为0,0变为1。- 左移
<<:将二进制位向左移动指定的位数,右边补0。- 右移
>>:将二进制位向右移动指定的位数,左边补符号位(正数补0,负数补1)。- 无符号右移
>>>:将二进制位向右移动指定的位数,左边补0。
- 位运算的优点
- 位运算通常比算术运算更快
- 使用位来表示状态,节省空间
找出整型数组中占比超过一半的数之题解
题目概述
小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。
限制是
- 整型数组,只考虑整数
解题思路
解决此题可以考虑数组的一个重要性质:
在数组中,出现次数超过一半的元素(称为“多数元素”),其出现次数比其他所有元素的出现次数之和还要多。
- 摩尔投票算法就是基于这个性质设计的算法。
- 摩尔投票算法: 摩尔投票算法是一种用于寻找数组中多数元素的经典算法,核心思想是通过抵消的方式来找到多数元素。
算法步骤如下:
- 初始化一个候选元素
candidate和计数器count。- 遍历数组中的每个元素:
- 如果
count为 0,将当前元素设为candidate。- 如果当前元素与
candidate相同,则count加 1。- 如果当前元素与
candidate不同,则count减 1。
- 最终,
candidate就是多数元素。
2.验证多数元素:
- 虽然摩尔投票算法能够找到一个候选元素,但为了确保它是多数元素,我们还需要再次遍历数组,统计该候选元素的出现次数,确认其出现次数确实超过数组长度的一半。
实现代码
public class Main {
public static int solution(int[] array) {
int candidate = 0;
int count = 0;
// 摩尔投票算法
for (int num : array) {
if (count == 0) {
candidate = num;
}
if (num == candidate) {
count++;
} else {
count--;
}
}
count = 0;
for (int num : array) {
if (num == candidate) {
count++;
}
}
if (count > array.length / 2) {
return candidate;
} else {
throw new IllegalArgumentException("No majority element found");
}
}
public static void main(String[] args) {
System.out.println(solution(new int[]{5, 5, 5, 1, 2, 5, 5}) == 5);
System.out.println(solution(new int[]{1, 3, 8, 2, 3, 1, 3, 3, 3}) == 3);
}
}
输出范例
true
true
具体运行过程
-
初始化:
candidate初始化为 0。count初始化为 0。
-
摩尔投票算法:
-
遍历数组中的每个元素
num(此处亦使用了增强for循环):- 如果
count为 0,将当前元素num设为candidate。 - 如果当前元素
num与candidate相同,则count加 1。 - 如果当前元素
num与candidate不同,则count减 1。
- 如果
-
-
验证候选元素:
- 再次遍历数组,统计
candidate的出现次数。 - 如果
candidate的出现次数超过数组长度的一半,则返1. - 回candidate。 - 否则,抛出异常
IllegalArgumentException,表示没有找到多数元素。
- 再次遍历数组,统计
测试用例的运行过程
以测试用例 [1, 3, 8, 2, 3, 1, 3, 3, 3] 为例:
-
初始化:
candidate = 0count = 0
-
摩尔投票算法:
-
遍历数组:
num = 1,count = 0,candidate = 1,count = 1num = 3,count = 1,candidate = 1,count = 0num = 8,count = 0,candidate = 8,count = 1num = 2,count = 1,candidate = 8,count = 0num = 3,count = 0,candidate = 3,count = 1num = 1,count = 1,candidate = 3,count = 0num = 3,count = 0,candidate = 3,count = 1num = 3,count = 1,candidate = 3,count = 2num = 3,count = 2,candidate = 3,count = 3
最终,
candidate = 3,count = 3。 -
-
验证候选元素:
-
再次遍历数组,统计
candidate = 3的出现次数:count = 5
-
检查
count > array.length / 2:5 > 9 / 2,即5 > 4,条件成立。
-
返回
candidate = 3。
-
测试用例的说明
- 尽可能多写几种不同的测试用例,以保障程序的正确
- 注意数据类型,整数