1. 其余数字出现两次——小试牛刀
题目描述
现在有一个整数类型的数组,数组中素只有一个元素只出现一次,其余的元素都出现两次。
注意:
你需要给出一个线性时间复杂度的算法,你能在不使用额外内存空间的情况下解决这个问题么?
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?
2. 其余数字出现三次——神仙解法
题目描述
现在有一个整数类型的数组,数组中只有一个元素只出现一次,其余元素都出现三次。
注意:
你需要给出一个线性时间复杂度的算法,你能在不使用额外内存空间的情况下解决这个问题么?
3. 思路
来自牛客的大神的思路,当然这道题为了速度是必须要用位运算的,这道题本质是什么呢?
对于题1来说,其实是不难的,假设所有的数据都是成对出现的,那么所有的数据进行异或之后,一定是归零的,那么既然有一个多余的数字,那么所有的数字进行异或之后,异或得到的结果就是那个落单的数字!
三个数字重复的就稍微麻烦了,在剑指offer上提供的思路是三个数字各个位上之和肯定是能够被3整除的,只需要检验每个位上的和能否被3整除,能的话,结果在这位上的数字就是0,而不能的话,结果在这位上的数字就是1。那这个思路不仅涉及到位运算,还涉及到四则运算,个人觉得比较混乱。
而在牛客上的大神提出的一个三进制思路令人拍案叫绝。
考虑如下,三进制情况下,我们用变量ones来存储所有数字加和之后,位为1的情况;twos用来存储哪些位数字是2,threes则表示哪些位上加起来已经是3了,这个变量有什么用呢?threes是由ones和twos得来, 当然是在目前更新的情况下,反向置零ones和twos相应的位,然后threes不用保存。
可能我说得不清楚,没有大佬理解透彻,我就引用一下大佬的原话:
链接:https://www.nowcoder.com/questionTerminal/1097ca585245418ea2efd0e8b4d9eb7a?f=discussion
来源:牛客网
Single Number的本质,就是用一个数记录每个bit出现的次数,如果一个bit出现两次就归0,这种运算采用二进制底下的位操作^是很自然的。Single Number II中,如果能定义三进制底下的某种位操作,也可以达到相同的效果,Single Number II中想要记录每个bit出现的次数,一个数搞不定就加两个数,用ones来记录只出现过一次的bits,用twos来记录只出现过两次的bits,ones&twos实际上就记录了出现过三次的bits,这时候我们来模拟进行出现3次就抵消为0的操作,抹去ones和twos中都为1的bits。
举个例子吧:
ones = 1 0 1 0
twos = 0 1 0 0
这时进来一个新数t=6,它的二进制是 0 1 1 0
想象一下,它进来之后,ones和twos是不是就会变了,这里要先更新twos,容易得twos第三位(从左到右)会因为ones和t 变成1,其他位保持,更新为0 1 1 0,然后ones会更新为 1 1 0 0 ,threes当然就是ones和twos位都位1的位置被置一,更新为0 1 0 0,既然threes被更新了,那么ones和twos对应位就应该被抹去了。ones:1 0 0 0 ,twos:0 0 1 0。然后继续等待下一个新数 t 。理解到了吗,这么清晰,还不懂的话,那你就来打死我。
下面上代码;
代码
class Solution {
public:
int singleNumber(int A[], int n) {
int ones = 0;
int twos = 0;
int threes;
for (int i=0; i<n;i++){
int t = A[i];
twos |= ones&t;
ones ^= t;
threes = ones & twos; //默认threes是0;
ones &= ~threes;
twos &= ~threes;
}
return ones;
}
};
就是这么简洁,举重若轻,至于其中的先用顺序,比如为什么要先更新twos,聪明的你,很容易想明白!!