最近参加了huawei的一个比赛,初赛刚结束,结果未知。虽然过程艰辛,经常搞到夜里1点,但是学到的知识还是挺多的。在学校没有参加很多的比赛也是一种遗憾,不得不说在学校自己的时间是真的多啊。感慨一番,继续造题。加油!
题目:
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6] 输出:[1,6] 或 [6,1] 示例 2:
输入:nums = [1,2,10,4,1,4,3,3] 输出:[2,10] 或 [10,2]
限制:2 <= nums.length <= 10000
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解析
思路1:
利用HashMap,优缺点:查找效率高,但是有额外空间。
步骤:
1:遍历数组,存放到map中,map中的key是数组中的值,value是该值出现的次数,在这里学到了一个新方法就是hashmap的getOrDefault();
2:遍历数组,从map中查找对应数值的次数,如果是1则取出,放置返回数组res中。
不知听懂否?没听懂不要紧,“乔哥”(微信公众号:程序员乔戈里)给出了图示:
以上就是遍历数组,将值和值对应出现的次数放在了map中。接下来,就是遍历数组,取出次数为1的数值了。
由于节省篇幅,想看详解的可点击文末链接(每一个步骤都有详细图解噢),再次致谢乔哥,小夕和皮皮。
在这里我只放两张典型的图(出现一次的,出现两次的)。
OK,话不多说。看具体实现:
代码实现
这个就不放注释了哈:
class Solution { public int[] singleNumbers(int[] nums) { Map<Integer, Integer> map = new HashMap(); for(int num: nums){ map.put(num, map.getOrDefault(num, 0) + 1); } int k= 0; int[] res= new int[2]; for(int i = 0; i < nums.length; i++){ if(map.get(nums[i]) == 1){ res[k++] = nums[i]; } } return res; } }
上面我们也说了,这样虽然能解题,但是我们显然是不满足(条件的),哈哈哈。那么还有其他思路嘛?
拔高优化
刷过剑指offer的童鞋看到这道题应该会稍微熟悉,因为在那本书中,有一个类似的题,说它类似是因为,它的题中是要求出数组中出现一次的单个数值,和这道题不一样的就是这里人家有两个次数为1的值。
但那道题可以利用异或的思路求解,何为异或?就是"^"它了,没错。
相同数字异或为0,不同数字异或为1。那道题的解法是:遍历数组,异或全部元素,最后剩下的就是出现一次的数值,因为出现两次的都异或为0了。
咦?那么我们是不是可以这样想?既然这道题的数组中有两个出现次数为1的数值,那么,如果我们能够把这俩数值分到不同的数组中(也就是一分为二),然后我们在利用该思路,分别对这两个数组进行遍历异或操作,不就可以得到这两个数值了吗?
是滴,没错。所以关键就是如何分组。
思路是这样的:假设遍历完数组后,得到的是a^b,那么我们可以知道a^b这个值中,二进制中,位数为1的位置代表了a和b这两个值的二进制值在该位置的不同(0和1),因为只有该位不同a^b在该位才会为1(根据异或规则)。
那好,我们就根据这个,将a和b分到不同的两个数组中,即:在该位为0的分到一个数组,在该位为1的分到另一个数组。
然后,遍历每个数组,异或数组中所有元素即可。
好了,文字描述完了,还不懂的话请看图解(感谢小夕提供的图解):
好的,中间省略....
结果分别异或两组数组,可得到 5 和 10 。
详细思路
再次总结步骤:
- 对 nums 进行异或,由于相同数字异或为 0,所以上述结果最终的异或结果是 5 异或 10
- 5 异或 10 5 的二进制 0101 10 二进制 1010 异或结果 1111
- 接下来我们需要找到1111的第一个二进制1出现的位置,原因:异或结果为1说明a和b在这一位上不同,那用只有这一位为1的数字m去分别相与a和b,得到的结果一定不同,也就把a和b分到了不同的子数组。结合上一点得出结果。
- 异或结果:1111 我们只需要找到第一个不为1的地方。例如:0001 0010 0100 1000 都可以
- 使用 0001 来与数组中的每个数字相同的这一位进行相与操作
- 遍历数组依次与0001进行异或
- 结果不为0分组异或组res1:[1,5,1,3,3]
- 结果为0分组异或组res2:[10,4,4]
- 分别对res1 和 res2 进行异或 res1结果为5res2结果为10返回即可
代码实现
class Solution { public int[] singleNumbers(int[] nums) { //用于将所有的数异或起来 int k = 0; for(int num: nums) { k ^= num; } //获得k中最低位的1 int mask = 1; while((k & mask) == 0) {//进行与操作 mask <<= 1; } int a = 0;//省去了两个数组的定义,直接在后序遍历中就进行异或操作 int b = 0; for(int num: nums) { if((num & mask) == 0) { a ^= num; } else { b ^= num; } } return new int[]{a, b}; } }
【参考及致谢】
1、小夕深夜联系两名知名博主开始搞事情,一起怒刷一道阿里高频面试题,击败100%的用户!
Over.......