Single Number I :
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?
Solution:
解法不少,贴一种:
1 class Solution: 2 # @param {integer[]} nums 3 # @return {integer} 4 def singleNumber(self, nums): 5 ans = nums[0]; 6 for i in range(1, len(nums)): 7 ans ^= nums[i] 8 return ans
其依据在于:
1、异或运算满足交换律;
2、a ^ a = 0;
3、b ^ 0 = b。
这题的关键就在于线性时间内把相同的一对 “消掉”,留下那个 “落单” 的。
异或运算给了这样的快捷的可能。
Single Number II:
Given an array of integers, every element appears three times except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
Solution:
同样,考虑用位运算解题:
在 I 中巧妙地用异或运算解决了把重复的元素的“消除”,只留下“落单”的元素的问题。而在 II 中,除了要找的元素,每个元素都出现 3 次,I 的解法适用于出现偶数次的情况,但对于奇数次已不再适用。
考虑每个数用二进制展开,将各个数横向比较:
对于一个32位(或者64位)的整数,对于这32个位中的某个位而言,如果每个数都出现三次,那么对于所有数在这个位上“1”的个数,一定是 3 的倍数;而反之,如果存在某个数不是出现 3 次(也不是 3 的倍数次,这是题目未讲明处,I 亦同理如此),那么对于它的二进制展开后数为 1 的位而言,对于所有数在这个位上“1”的个数,一定不是 3 的倍数。
所以具体的解决办法是:
用一个 32 长的数组存储对于所有数的二进制展开,每一个位上总共 “1” 的个数和,最后看那些位上 “1” 的个数不是 3 的倍数,那么这一位在 ans 中就是 1。
算法是 O(32n) 的。
有一点需要注意的是:
不同的语言中对位运算尤其是符号位的处理是不尽相同的,比如 C++ 中最高位是符号位,如果不是 3 的倍数那么最后的 ans 就是负数,符号位可以和其他位一样处理;但如果是Python,由于其动态类型的特性,当超出数据范围时,会自动转化为更大范围的数据类型,而不会将其作为符号位处理。
C++版本:
1 class Solution { 2 public: 3 int singleNumber(int A[], int n) { 4 int bitnum[32] = {0}; 5 int res=0; 6 for(int i = 0; i < 32; i++){ 7 for(int j = 0; j < n; j++){ 8 bitnum[i] += (A[j] >> i) & 1; 9 } 10 res |= (bitnum[i] % 3) << i; 11 } 12 return res; 13 } 14 };
Python版本:
1 class Solution: 2 # @param {integer[]} nums 3 # @return {integer} 4 def singleNumber(self, nums): 5 bitNum = [0] * 32 6 for i in range(32): 7 for e in nums: 8 bitNum[i] += (e >> i) & 1 9 ans = 0 10 for i, val in enumerate(bitNum): 11 if i == 31 and val % 3 != 0: 12 ans = -((1 << i) - ans) 13 else: 14 ans |= (val % 3) << i 15 return ans
其中对于逻辑上的符号位(第32位),单独判断并处理,如果为 1,则需要转化为对应的负数(对应的负数的绝对值 = 【模】- 不考虑符号位(符号位为0)的正数)。
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?
Credits:
Special thanks to @jianchao.li.fighter for adding this problem and creating all test cases.
Solution:
打怪打到系列第三级,又回到了跟 I 类似的情况,也就是相同的数字是偶数个,不同的是,这里面有两个“怪胎”。
很容易联想到 I 的解法,把所有数异或起来。但是异或之后我们得到的是我们想要的那两个数的异或值,如何把它们从异或中拆分开呢?
假设我们要找的这两个数为 a, b, 而 x = a ^ b。
首先,a 肯定不等于 b,那么说明它们的二进制位一定是不完全相同的,所以 x 肯定不为 0。
也就是说,a 与 b 一定存在“某一位”,使得在它们中的某个数中是 0,而在另一个数中是 1,这是他们之间的一个差别。
我们可不可以利用这个差别来把这两个数从 x 中揪出来呢?
是可以的。
利用这个差别,我们可以将整个 nums 集合分成两个集合。一个集合中是这 “某一位” 为 0 的在nums中的所有数,假设为集合 A。而另一个集合是这 “某一位” 为 1 的在nums中的所有数。假设 a 的这 “某一位” 是 0 ,b 的 这个“某一位”是1,那么显然 a 在集合 A 中,b 在集合 B 中,这样问题就完全转化成了与 I 一样的两个子问题,于是可以得解。
关于具体的代码实现,还有一点说明:
我们如何找到这个 “某一位” 呢?理论上,只要是在这一位上 a与b的值不同,都可以合格的成为我们需要找的某一位。既然无更多限制,那我们肯定是找好找的某一位咯。
我们可以用很常规和易懂的方法去找,但一般而言,我们肯定是找最右边(低位)那边符合要求的“某一位”嘛。更进一步说,就是找到 x 中最低位的 1 嘛。那当然我们可以从最低位开始枚举每一位,直到找到我们需要找的那个“某一位”。
还有一种更trick利用位运算的办法:找到 x 中最低位的 1,仔细想想,这跟另外一个已经熟知的问题很像。
当我们统计某个数的二进制展开中1的个数的时候,我们使用了一个技巧,即用 n &= n - 1 每次来清除 n 中当前最右边的那个 1。
n-1 是把 n 的最低位的 1 变成 0,并且使更低位的 0 全部变成 1,然后异或一下就把 最低位的 1 及其更低位全部都变成了 0,即达到了“清除最低位的 1 的目的”。
(详见:统计二进制展开中数位1的个数的优化 优化解法1)
在这个地方,我们需要逆向思维,即 保留 最低位的1,并且最好使得其他位 都变成0,这样我直接与 nums 中的每一个数相与,就可以直接将它们分成 A 和 B 两个集合了。
逆向思维会是这样:
n-1 的过程其实是把 最低位 1 和 跟低位 都位反转 (Bit Flipping) 的过程,那我们这里 首先也将 n 的所有位反转得到 n'。
然后我们再把 n'+1。。。
Opps! What did we get?
我们发现 n'+1 相对于 n 而言,最低位的1及其更低位的0 都没变,而其他位(比 最低位1 更高的位)都反转了。
那此时如果用 n & (n'+1) 得到的便是 除 n 中最低位 1 继续为 1 以外,其他各位都为 0 的数了。
n' 如何求?当然我们可以直接取反。但是联合 n'+1 来看,各个位取反再加一,这不正是计算机中 “负数补码” 的表示方式!
所以我们可以直接用 n &= -n 得到 “除 n 中最低位 1 继续为 1 以外,其他各位都为 0 的数”!
(注意正数的补码的补码是它本身,所以即便 n 本身是负数,-n是正数,但 -n 依然是求 n 的补码。)
完美!
代码如下:
1 class Solution: 2 # @param {integer[]} nums 3 # @return {integer[]} 4 def singleNumber(self, nums): 5 diff = 0 6 for e in nums: 7 diff ^= e 8 diff &= -diff 9 ans = [0, 0] 10 for e in nums: 11 if diff & e != 0: 12 ans[0] ^= e 13 else: 14 ans[1] ^= e 15 return ans
[ by Maples7 ]
[ Copyright @Maples7,转载请注明出处。 ]