Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
一、前言
刷题啊!!!
开始刷 “剑指 Offer” 31天。刷完时间:2022.3.6 ~ 2022.3.20。
位运算:
| 符号 | 说明 | 运算规则 | |
|---|---|---|---|
| & | 与 | 两个位均为1,则为1 | |
| ` | ` | 或 | 两个位都为0, 则为0 |
| 异或 | 两个位相同为0, 相异则为1 | ||
| ~ | 取反 | 0变1, 1变0 | |
| << | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0 | |
>> | 右移 | 各二进位全部右移若干位,对于无符号数,高位补0; 有符号数,有的补符号位 |
小技巧:
- 异或运算:
x ^ 0 = x - 与运算:
x & 0 = 0
二、题目
题目:
- 数组中数字出现的次数 I
- 数组中数字出现的次数 II
(1)剑指 Offer 56 - I. 数组中数字出现的次数
题目描述
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是 O(n),空间复杂度是 O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
限制:2 <= nums.length <= 10000
题解
题意:明确要求,时间复杂度是 O(n),空间复杂度是 O(1),那么就不能使用额外存储 和 暴力法(2次循环)。
问题一:如何找到只出现一次的数?
用异或
^:两个相同的数异或后为 0
# 1 异或 1 = 0
1 ^ 1 == 0
# 根据这个特性,就能过滤出只出现过一次的数。
# 根据题目,会有两个数只出现过一次。
# 那么结果就是那两个数的异或值。
问题二:如何从异或值中分解出两个数?
# 假设两个只出现过一次的数为 x, y
# 因为 x != y, 所以 x 和 y 的二进制至少有一位不同
# 那么就要找到这位。
# 通过这个位可以将数组 nums 划分为两个数组 nums1 和 nums2 (相同的数,一定在同一组内)
# 再通过异或两个数组,就能得出 x 和 y
思路:假设两个只出现过一次的数为 x, y
- 遍历数组
nums,异或得到结果a - 找到数
a中最低位为 1 的数b - 通过数
b将数组nums划分为两个数组nums1和nums2 - 分别异或这两个数组,得到 x 和 y
AC 代码如下:
class Solution {
public int[] singleNumbers(int[] nums) {
int a = 0;
// 1. 异或得到 a
for (int num : nums) {
a ^= num;
}
// 2. 求最低数 b
int b = 1;
while ((a & b) == 0) b <<= 1;
// 3. 分组 异或
int x = 0, y = 0;
for (int num : nums) {
if ((num & b) != 0) x ^= num;
else y ^= num;
}
return new int[] {x, y};
}
}
(2)剑指 Offer 56 - II. 数组中数字出现的次数 II
题目描述
在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
限制:
1 <= nums.length <= 100001 <= nums[i] < 2^31
题解
AC 代码如下:
class Solution {
public int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for(int num : nums){
ones = ones ^ num & ~twos;
twos = twos ^ num & ~ones;
}
return ones;
}
}