最近在leetcode刷算法题,很多题的解法有相通之处,特别是同一个系列的题目,把思路写下来可以帮助理解记忆,也方便以后复习。本文记录的是Single Number系列问题的解法,主要的思路是位运算。
136. Single Number
Given an array of integers, every element appears twice except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
题意:
给定一个整数数组,除了一个元素外,其他每个元素都会出现两次。 找出那个落单的数。要求线性的时间复杂度,并且不使用额外空间。
分析:
如果没有要求不能使用额外空间,我们可以通过Hash来记录每个数出现的次数,最后输出只出现一次的数。
// TC: O(n), SC: O(n)
public int singleNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int x : nums) {
map.put(x, map.getOrDefault(x, 0) + 1);
}
for (int k : map.keySet()) {
if (map.get(k) == 1) return k;
}
return 0;
}
但是本题要求不使用额外空间,我们可以通过位运算来解决。两个相同的数按位异或的结果为0,因此一次遍历,将所有的数按位异或,出现两次的数被消去,最后的值为落单的数。
// TC: O(n), SC: O(1)
public int singleNumber(int[] nums) {
int answer = 0;
for (int i = 0; i < nums.length; i++) {
answer ^= nums[i];
}
return answer;
}
137. Single Number II
Given an array of integers, every element appears three times except for one, which appears exactly once. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
题意:
给定一个整数数组,除了一个元素外,其他每个元素都会出现三次。 找出那个落单的数。要求线性的时间复杂度,并且不使用额外空间。
分析:
此题是上题的变种,元素出现三次,不能再通过异或运算来消去相同的元素。此类题目在于统计每个比特位1出现的次数。
方法一: 对于int类型,依次统计32 bit每一位上1出现的次数,然后模3,余下1次就是single number出现的,这种方法是通用的,如出现3次改为出现5次,只需将模3改为模5. 上题采用这种解法也是没问题的。
// TC: O(n), SC: O(1)
public int singleNumber(int[] nums) {
int answer = 0;
for (int i = 0; i < 32; i++) {
int count = 0;
for (int num : nums) {
if ((num >> i & 1) == 1) {
count++;
}
}
count %= 3;
answer |= count << i;
}
return answer;
}
方法二: 用one记录出现一次的bit,two记录出现两次的bit,three记录出现三次的bit,每当有出现三次的bit时,将one,two中出现三次的bit置为0.
// TC: O(n), SC: O(1)
public int singleNumber(int[] nums) {
int one = 0, two = 0, three;
for (int num :nums) {
two |= one & num;
one ^= num;
three = one & two;
one = one & ~three;
two = two & ~three;
}
return one;
}
260. Single Number III
Given an array of numbers nums
, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.
For example:
Given nums = [1, 2, 1, 3, 2, 5]
, return [3, 5]
.
Note:
- The order of the result is not important. So in the above example,
[5, 3]
is also correct. - Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?
题意:
给定一个整数数组,除了两个元素外,其他每个元素都会出现两次。 找出那个两个落单的数(两个数的顺序任意)。要求线性的时间复杂度,并且使用常数空间。
分析:
与第136题思路类似,通过按位异或消去相同的数,因为有两个落单的数字,所以应采用某种方式将原数组分成两部分处理。
- 第一次遍历将所有元素按位异或,得到
x ^ y
的值t
(x, y
即要找的两个数); t
的二进制中,出现1的位置都是x, y
的不同之处,任意取一个出现1的位置(可以通过t & -t
取最低位的1),与原数组的元素按位与,根据该位置是否出现1将原数组分成分别包含x, y
的两部分(其他的元素中相同的一定会被分到同一部分);- 分别将这两部分按位异或,得到
x, y
。
public int[] singleNumber(int[] nums) {
int t = 0;
for (int num : nums) {
t ^= num;
}
t &= -t;
int[] answer = new int[2];
for (int num : nums) {
if ((t & num) != 0) {
answer[0] ^= num;
} else {
answer[1] ^= num;
}
}
return answer;
}
总结:
此类型的题目,要求O(n)的时间复杂度和O(1)的空间复杂度或者不能使用额外空间。我们可以借助位运算的性质(如相同的元素进行异或运算结果为0),通过统计各个bit位1出现的次数来统计元素出现的次数,以及通过异或运算的结果来区分不同的元素。