LeetCode算法日记 - Day 18: 只出现一次的数字、只出现一次的数字III
目录
1. 只出现一次的数字
1.1 题目解析
1.2 解法
1.3 代码实现
2. 只出现一次的数字 III
2.1 题目解析
2.2 解法
2.3 代码实现
1. 只出现一次的数字
136. 只出现一次的数字 - 力扣(LeetCode)
给你一个 非空 整数数组 nums
,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
1.1 题目解析
数组中,除了一个数字出现一次,其余数字都出现两次。问题抽象成:如何在 O(N) 时间、O(1) 空间内,从数组里找到。
常规解法
最直观做法:用哈希表记录每个数的出现次数,最后找到出现 1 次的数。
哈希表解法能找到答案,但:
-
空间复杂度是 O(N),不符合“常量额外空间”的要求。
-
时间复杂度 O(N),但存储开销大。
思路转折
要想省空间,必须抛弃“存储次数”的思路,转而利用位运算。
注意到:
-
相同的两个数异或结果是 0。
-
一个数和 0 异或结果是它自己。
所以整组数异或,成对的数会被抵消,只剩下那个唯一的数。
这就把问题转化成 全体元素求异或,一步解决。
1.2 解法
利用异或运算性质:
-
a ^ a = 0
-
a ^ 0 = a
-
异或满足交换律、结合律
所以:nums[0] ^ nums[1] ^ ... ^ nums[n-1] = 只出现一次的那个数
i)初始化结果 ret = 0。
ii)遍历数组,把每个数异或到 ret 上。
iii)遍历结束,ret 就是唯一出现一次的数。
1.3 代码实现
class Solution {public int singleNumber(int[] nums) {int ret = 0;// 遍历数组,逐个异或for (int num : nums) {ret ^= num;}return ret;}
}
2. 只出现一次的数字 III
260. 只出现一次的数字 III - 力扣(LeetCode)
给你一个整数数组 nums
,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。
示例 1:
输入:nums = [1,2,1,3,2,5] 输出:[3,5] 解释:[5, 3] 也是有效的答案。
示例 2:
输入:nums = [-1,0] 输出:[-1,0]
示例 3:
输入:nums = [0,1] 输出:[1,0]
提示:
2 <= nums.length <= 3 * 104
-231 <= nums[i] <= 231 - 1
- 除两个只出现一次的整数外,
nums
中的其他数字都出现两次
2.1 题目解析
题目本质
数组中有两个数只出现一次,其余数都出现两次。问题抽象为:如何在 O(N) 时间、O(1) 空间下,把两个数从数组里分离出来。
常规解法
最直观想法:用哈希表统计频次,最后挑出出现 1 次的两个数
哈希表能直接解题,但:
-
空间复杂度 O(N),违背了题目“常量空间”的要求。
-
频次统计过程额外开销大。
思路转折
既然每个数都出现两次,只有两个例外 → 可以考虑异或:
-
异或能抵消相同元素。
-
全部异或后结果就是 a ^ b(a、b 是两个目标数)。
-
问题变成:如何从 a ^ b 里“拆”出 a 和 b?
关键洞察:
-
a ^ b 二进制表示里必然有至少一位是 1
-
这一位说明 a、b 在该位上不同:一个是 0,一个是 1。
-
只要用这一位做分组依据,就能把 a 和 b 分开。
这一步就是利用 mark = Integer.lowestOneBit(a ^ b),取到能区分它们的最低位 1
2.2 解法
i)先整体异或,得到 tmp = a ^ b
ii)找出 tmp 的最低位 1,作为掩码 mark。
-
性质:mark 可能是 1, 2, 4, 8...,不是固定的 1。
-
(num & mark) 结果要么是 0,要么是 mark 本身。
-
mark = Integer.lowestOneBit(tmp)
iii)遍历数组,按 (num & mark) 是否为 0 分两组,分别异或。
-
因为 a、b 在这一位上不同,它们必然分到不同组。
-
如果 (num & mark) == 0 → XOR 进 a 否则 → XOR 进 b。
-
否则 → XOR 进 b。
-
组内其它成对数抵消,剩下的就是 a 和 b。
2.3 代码实现
class Solution {public int[] singleNumber(int[] nums) {// 第一步:全体异或,得到 a ^ bint tmp = 0;for (int num : nums) {tmp ^= num;}// 第二步:取最低位的 1 作为区分标志int mark = Integer.lowestOneBit(tmp);// 第三步:分组异或int a = 0, b = 0;for (int num : nums) {if ((num & mark) == 0) {a ^= num;} else {b ^= num;}}return new int[]{a, b};}
}