题目:
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?
找出没有出现3次的数字,同样要求线性时间,不使用额外空间。
Answer 1: 统计每位1的个数
思路:
考虑数字在计算机中都是按照比特存储。
如果所有数字的第i位比特位求和并且模3,结果是1或者0,因为每个数字只可能出现3次或者1次,所以这第i位比特位可以代表“single number”, 即我们可以通过最后模3,来确定single number每一个比特位的值是0还是1.。和Single Number的异或有异曲同工之妙。
复杂度:由于外层循环统计32位,所以复杂度是O(32*N) 即 O(N)
Attention:
1. 一定要初始化结果为0,函数体内的内置变量值随机(编译器可能初始化为0,但千万别依赖这种可能行为,它可能为程序带来未定义行为)。如果是全局范围内的内置类型变量均被编译器自动初始化为0值。
<span style="font-size:14px;">int ret = 0;</span>
<span style="font-size:14px;">int ret;</span>
1 / 11 test cases passed.
|
Status: Wrong Answer |
Submitted: 10 minutes ago
|
Input: | [1] |
Output: | 966166951 |
Expected: | 1 |
2. 如何每一位统计1的个数,需要设置d = 1并通过移动 i 位,来得到想要的结果。
<span style="font-size:14px;">int d = 1 << i; //计算第i位,其余位清零 //统计第i位的情况 for(int j = 0; j < n; j++) { if(A[j] & d) c++; } //当第i位,1的个数不是3的倍数时,应该将该位置1 if(c % 3) ret |= d;</span>AC Code:
<span style="font-size:14px;">class Solution { public: int singleNumber(int A[], int n) { int ret = 0; for(int i = 0; i < 32; i++) { int c = 0; int d = 1 << i; //计算第i位,其余位清零 //统计第i位的情况 for(int j = 0; j < n; j++) { if(A[j] & d) c++; } //当第i位,1的个数不是3的倍数时,应该将该位置1 if(c % 3) ret |= d; } return ret; } };</span>
思路:ones记录到目前为止计算的变量为止,二进制1出现“1次”(mod 3 为1)的数位。twos记录到目前为止计算的变量为止,二进制出现“2”次(mod 3 为2)的数位。当ones和twos中的某一位同时为1则表示,二进制出现1为3次,此时将该位清零。就是用二进制来模拟三进制计算。最终ones记录的就是最终结果。
ones
代表第ith 位只出现一次的掩码变量twos
代表第ith 位只出现两次次的掩码变量threes
代表第ith 位只出现三次的掩码变量例子:
加入数组前三个为5,则各个变量变化如下:
ones = 101, twos = 0, threes = 0;
ones = 0, twos = 101, threes = 0;
ones = 101, twos = 101, threes = 0;(执行到清ones ^= A[i]) => ones = 0, twos = 0, threes = 101;(执行到清零后)
Attention:
这种位的操作方法十分巧妙,需要好好学习,刚开始会比较难理解,但是从模拟三进制方法入手,就会容易些,并且每个变量都代表32比特位的一个数组。
AC Code:
class Solution { public: int singleNumber(int A[], int n) { int ones = 0, twos = 0, threes = 0; for(int i = 0; i < n; i++) { twos |= ones & A[i]; //twos积累值 ones ^= A[i]; //ones不断求反,3次异或和1一次异或结果一样 threes = ones & twos; //当该数出现三次时,ones和twos都保留了该位的值 ones &= ~threes; //清零出现三次的该位的值 twos &= ~threes; } return ones; } };
扩展一:
给定一个包含n个整数的数组,除了一个数出现二次外所有的整数均出现三次,找出这个只出现二次的整数。ones记录1出现一次的数,twos记录1出现2次的数,容易知道twos记录的即是最终结果。
扩展二:
给定一个包含n个整数的数组,有一个整数x出现b次,一个整数y出现c次,其他所有的数均出现a次,其中b和c均不是a的倍数,找出x和y。使用二进制模拟a进制,累计二进制位1出现的次数,当次数达到a时,对其清零,这样可以得到b mod a次x,c mod a次y的累加。遍历剩余结果(用ones、twos、fours...变量表示)中每一位二进制位1出现的次数,如果次数为b mod a 或者 c mod a,可以说明x和y的当前二进制位不同(一个为0,另一个为1),据此二进制位将原数组分成两组,一组该二进制位为1,另一组该二进制位为0。这样问题变成“除了一个整数出现a1次(a1 = b 或 a1 = c)外所有的整数均出现a次”,使用和上面相同的方式计算就可以得到最终结果,假设模拟a进制计算过程中使用的变量为ones、twos、fours...那么最终结果可以用ones | twos | fours ...表示。