zoukankan      html  css  js  c++  java
  • 找到数组中出现特定次数数字的问题

    问题一:一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数

    牛客-NowCoder_EvenOddTimes

    LeetCode_0136_SingleNumber

    问题一解题思路

    因为a ^ a = 0, 所以出现过偶次的数异或结果都是0,又因为0^a=a,所以把数组中所有的数做异或以后的结果,就是出现了奇数次的那个数。

    问题一完整代码

    public class LeetCode_0136_SingleNumber {
    
        public static int singleNumber(int[] nums) {
            int ans = nums[0];
            for (int i = 1; i < nums.length; i++) {
                ans ^= nums[i];
            }
            return ans;
        }
    }
    

    问题二:一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数

    LeetCode_0260_SingleNumberIII

    问题二解题思路

    根据问题一的结论,假设数组中ab这两个数出现了奇数次,这个数组中的所有数字异或以后得到的结果一定a^b

    因为ab是两种不同的数,所以a^b的结果一定不等于0。

    所以,a^b的结果如果转换成二进制的话,一定有某位是1。我们假设a^b转换成二进制后最右侧位置的1在i位置,由此可以得出一个结论:a和b的二进制在i位置一定一个为0,一个为1

    不妨假设ai位置为0bi位置为1

    此外,容易得知,整个数组中的数,i位置为0的数除了a以外,其他数一定有偶数个, i位置为1的数除了b之外,其他数一定有偶数个。

    那么我们可以只对i位置为1的数求异或,最后得到的值一定是b,然后通过b^(a^b) = a,可以得到a的值。

    最后只剩下一个问题:

    如何求一个数最右侧的1呢?

    假设 某个数x二进制为:00010010, 其最右侧的1是:00000010

    算法是:对于一个数x来说,它最右侧的1等于x & ((~x) + 1)或者x & (-x)

    所以,如果一个数是a^b,那么它最右侧的1就是(a^b) & (~(a^b) + 1)

    我们用(a^b) & (~(a^b) + 1)这个值去和数组中每个值数做与运算(&),如果与完以后的结果是0,说明这个数i位置是0,否则说明这个数i位置是1。我们前面已经得到一个结论,i位置为0的数除了a以外,其他数一定有偶数个。所以,用(a^b) & (~(a^b) + 1)这个值和每个i位置是0的数组元素做与运算以后,最后的结果一定是a。 得到a以后,然后通过a^(a^b) = b,可以得到b的值。

    问题二完整代码

    public class LeetCode_0260_SingleNumberIII {
        public static int[] singleNumber(int[] arr) {
            int eor = 0;
            for (int n : arr) {
                eor ^= n;
            }
            // 假设出现奇数次的两种数为 a和b
            // eor = a ^ b
            // 获取最右侧的1
            int a = 0;
            int rightOne = eor & ((~eor) + 1);
            for (int n : arr) {
                if ((n & rightOne) == 0) {
                    a ^= n;
                }
            }
            int b = a ^ eor;
            return new int[]{a, b};
        }
    }
    

    当有如下公式计算一个数最右侧的1以后

    x & ((~x) + 1)
    

    我们还可以解决如下问题:

    LeetCode_0191_NumberOfOneBits

    思路:即提取出最右侧的1以后,与目标数进行与运算(&), 得到一个新的目标数,然后继续提取新目标数的最右侧的1,如此往复,即可把所有位置的1都提取出来。

    问题三 一个数组中有一种数出现k次,其他数都出现了m次,m > 1, k < m, 找到出现了k次的数

    要求:假设数组中所有数都是int类型,额外空间复杂度O(1),时间复杂度O(N)

    LeetCode_0137_SingleNumberII

    NowCoder_FindOneInK

    问题三解题思路

    我们可以这样考虑,设置一个32位的数组,

    int[] help = new int[32];
    

    遍历原始数组中每个数num的每一个二进制位, 伪代码如下:

    for (int num : arr) {
      for (int i = 0; i < 32; i++) {
        help[i] += num的二进制中i位置的值(只能是0或者1)
      }
    }
    

    经过以上循环,help数组就把数组中的所有数的二进制位上的信息累加起来了。

    help[0]表示数组中所有数二进制中0位置的值之和;

    help[1]表示数组中所有数二进制中1位置的值之和;

    ......

    help[31]表示数组中所有数二进制中31位置的值之和。

    然后i0位置开始拿出help[i]的值,假设help[i]=x,用x % m, 如果结果是k,说明出现k次的元素在这个位置上是1, 否则,这个出现了k次的数在i位置上是0, 遍历完help数组,出现k次元素的每一位信息都拿到了,然后还原出来即可。

    问题三关键代码

    public static int km(int[] arr, int k, int m) {
      int[] helper = new int[32];
      for (int i = 0; i < arr.length; i++) {
       for (int j = 0; j < 32; j++) {
        helper[j] += ((arr[i] >> j) & 1);
       }
      }
      int ans = 0;
      for (int i = 0; i < 32; i++) {
       if (helper[i] % m == k) {
        ans |= (1 << i);
       }
      }
      return ans;
     }
    

    更多

    算法和数据结构笔记

    参考资料

  • 相关阅读:
    JDBC值事务
    JDBC之存储过程
    2016年计划要看的书,收集来自爱学习爱阅读的精英们
    step by step设置postgresql用户密码并配置远程连接
    在CentOS上搭建svn服务器及注意事项
    我为儿子开发的第一款Android App,用于九九乘法练习
    C语言实现九九乘法口诀表
    selenium-自动化测试51job网站(MacOS + Safari)2020年10月6日
    Oracle中row_number()、rank()、dense_rank() 的区别
    SQL错题集
  • 原文地址:https://www.cnblogs.com/greyzeng/p/15385402.html
Copyright © 2011-2022 走看看