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;
     }
    

    更多

    算法和数据结构笔记

    参考资料

  • 相关阅读:
    机器学习(深度学习)
    机器学习(六)
    机器学习一-三
    Leetcode 90. 子集 II dfs
    Leetcode 83. 删除排序链表中的重复元素 链表操作
    《算法竞赛进阶指南》 第二章 Acwing 139. 回文子串的最大长度
    LeetCode 80. 删除有序数组中的重复项 II 双指针
    LeetCode 86 分割链表
    《算法竞赛进阶指南》 第二章 Acwing 138. 兔子与兔子 哈希
    《算法竞赛进阶指南》 第二章 Acwing 137. 雪花雪花雪花 哈希
  • 原文地址:https://www.cnblogs.com/greyzeng/p/15385402.html
Copyright © 2011-2022 走看看