概述
1.只出现一次的数字I
先看题目
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
方法一:map集合
看完题干,脑子第一反应就是用map来解决,数组中的元素作为key,出现的次数作为value。于是两三下就敲好代码,果然困难题我唯唯诺诺,简单题我重拳出击。
public int singleNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
int ans = 0;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() == 1) {
ans = entry.getKey();
}
}
return ans;
}
方法二:位运算
法一的代码提交,发现耗时15ms,只击败了12%的人。我一看这个结果,心想难道还有更加巧妙的方法?于是看了下提示,说是采用位运算来解决。仔细分析题干,只有一个元素出现一次,其余出现两次。那么有没有一种位运算,可以使得相同的两个数进行运算以后的值是固定的。答案当然是^(异或)运算。
进行异或运算时,会先转成二进制,然后每一位上在进行异或,相同为0不同为1。
例子:
2^3
010
011
——
001
a | b | a^b |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
public int singleNumber(int[] nums) {
int ans = 0;
for (int num : nums) {
ans ^= num;
}
return ans;
}
2.只出现一次的数字II
先看题目
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
大家都是体面人,map的方法就不在这里展示了,直接思考怎么用位运算搞定。仔细思考发现,我们可以用逻辑电路的方法来解决。
刚开始没有出现过是0,出现了第一次,状态变为1,再来一个状态又变为2,最后一次出现,状态置为0。这个状态我们分别用二进制00,01,10来表示,因为计算机一位只能表示0或1,所以我们需要两位来表示。画出它的真值表:(one* 和two* 表示下一个状态)
n | one | two | one* | two* |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 0 | 1 | 0 |
1 | 0 | 0 | 0 | 1 |
1 | 0 | 1 | 1 | 0 |
1 | 1 | 0 | 0 | 0 |
根据真值表写出逻辑关系式:
two* = ~n & ~one & two + n & ~one & ~two
化解一下
two* = two^num&~one
同理
one* = ~n & one & ~two + n & one & two
真值表化逻辑表达式:
方法一:
- 根据真值表当中结果是true的项,找出它的输入项,然后进行与运算;如果遇到输入项的取值是false,就需要把它写成非的形式,反之,如果输入项是true,那么就不用管,写成原来的样子就行了。
- 然后,遍历整一个真值表,把所有输出值为true的项进行或运算(相加),就可以得到最原始的逻辑关系式。
方法二:
- 根据真值表当中结果为false的项,找出输入项,然后进行或运算;如果遇到输入项的取值是true,就需要把它写成非的形式,反之,如果输入项是false,那么就不用管,写成原来的样子就行了。
- 然后,遍历整一个真值表,把所有输出值为false的项进行与运算(相乘),就可以得到最原始的逻辑关系式。
public int singleNumber(int[] nums) {
int one = 0, two = 0;
for (int num : nums) {
int newone = (~num & one & ~two) | (num & ~one & two);
int newtwo = two ^ num & ~one;
one = newone;
two = newtwo;
}
return two;//因为01代表出现一次,所以返回two
}
上面代码还能简化一下,首先two的表达式不变,将two* 的值赋给two,重新画一份真值表。
n | one | two | one* |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 |
0 | 1 | 0 | 1 |
1 | 0 | 1 | 0 |
1 | 0 | 0 | 1 |
1 | 1 | 0 | 0 |
得出one* = ~n & one & ~two + n & ~one & ~two
化解one* = one ^ num & ~two
public int singleNumber(int[] nums) {
int one = 0, two = 0;
for (int num : nums) {
one = one ^ num & ~two;//第一次进来one和two是0,经过位运算one变成num 第二次one变成0,第三次one又变成num,注意这里的num指的是同一个数例如3;
two = two ^ num & ~one;//1.two还是0 ,2.two是num,3.two是0
}
return one;//one保存了1次和3次,所以返回one
}
用到的主要位运算是: a^a = 0, ~0 = -1, a&-1=a
果然大学里面学的数字电路还是有用,后悔当初没有认真学????!
2.只出现一次的数字III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
看到这题,首先想到的就是用异或(异或大法好啊),但是本题最关键的是怎么将两个只出现一次的数分开。核心就是这行代码 diff = ans & (-ans),是什么意思呢?这行代码是计算出转化为二进制后最右边出现的第一个1。是不是很神奇,举个例子:ans为6,那么化成二进制就是110,最右边的1在第二位,所以diff的值就为2。
假设a和b分别表示两个只出现过一次的数。那么这个最右边的1不是来自a就是来自b,那么我们可以将原数组分为两个部分,一个是这个位置上是1的数,化为一组,另一组是这个位置上不是1而是0的数。这样a与b必定不会在同一组,然后在分别进行异或。
public int[] singleNumber(int[] nums) {
int ans = 0;
for (int num : nums) {
ans ^= num;//最后的结果就是a^b(a和b分别代表只出现一次的数)
}
int diff = ans & (-ans);//算出右边出现的第一个1,这个1不是来自a就是来自b
int x = 0;
for (int num : nums) {
if ((num & diff) != 0) {
x ^= num;//分组异或
}
}
return new int[]{x, ans ^ x};
}
珍惜所有的不期而遇,看淡所有的不辞而别!拜拜!
最后
以上就是谦让海燕为你收集整理的算法位运算——只出现一次的数字系列的全部内容,希望文章能够帮你解决算法位运算——只出现一次的数字系列所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复