zoukankan      html  css  js  c++  java
  • 算法图解——找出整形数组里出现一次的两个数

    最近参加了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.......

  • 相关阅读:
    PNG文件格式具体解释
    opencv2对读书笔记——使用均值漂移算法查找物体
    Jackson的Json转换
    Java实现 蓝桥杯VIP 算法训练 装箱问题
    Java实现 蓝桥杯VIP 算法训练 装箱问题
    Java实现 蓝桥杯VIP 算法训练 单词接龙
    Java实现 蓝桥杯VIP 算法训练 单词接龙
    Java实现 蓝桥杯VIP 算法训练 方格取数
    Java实现 蓝桥杯VIP 算法训练 方格取数
    Java实现 蓝桥杯VIP 算法训练 单词接龙
  • 原文地址:https://www.cnblogs.com/gjmhome/p/14624683.html
Copyright © 2011-2022 走看看