《剑指offer》 -day22- 位运算(中等)

剑指 Offer 56 - I. 数组中数字出现的次数

题目描述

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

限制:

  • 2 < = n u m s . l e n g t h < = 10000 2 <= nums.length <= 10000 2<=nums.length<=10000

哈希表

思路

  • 比较简单的思路是利用 map 统计每个元素出现的次数,然后取次数只出现一次的元素,即可。
  • 但是这样空间复杂度为 O ( n ) O(n) O(n),不满足题目中空间复杂度为 O ( 1 ) O(1) O(1) 的要求。

位运算

异或运算

  • x ⊕ x = 0 x \oplus x = 0 xx=0
  • x ⊕ 0 = x x \oplus 0 = x x0=x
  • x ⊕ y = y ⊕ x x \oplus y = y \oplus x xy=yx

思路 🤔

  1. 一遍遍历,求出所有元素异或和 sum
  2. sum 二进制中不为 1 1 1 的位置 index
  3. 根据二进制第 index 不同,将 n u m s nums nums 中所有元素分为 2 组,分别求 异或和,最终的异或和结果 xy 即为所求结果。
class Solution {
    public int[] singleNumbers(int[] nums) {
        int sum = 0; 
        // 1、求所有元素异或和
        for (int i : nums) sum ^= i;
        // 2、求sum二进制中不为1的位置
        int index = -1;
        for (int i = 0; i < 32; i++) { // int32位
            if ((sum & 1) == 1) {
                index = i;
                break; // 找到即退出
            }
            sum >>= 1; // 右移一位
        }
        System.out.println("index = " + index);
        // 3、分两组
        int x = 0;
        int y = 0;
        for (int i : nums) {
        	// 这里根据二进制第index为是否为 0(而不能是1),来分为两组
        	// if ((i & (1 << index)) == 1) x ^= i; // error
            if ((i & (1 << index)) == 0) x ^= i;
            else y ^= i;
        }
        return new int[] {x, y};
    }
}

🔥 注意:((i & (1 << index))结果为 0 0 0 或者 2 i n d e x 2^{index} 2index(而不是0或者1)

  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    位运算

剑指 Offer 56 - II. 数组中数字出现的次数 II

题目描述

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

限制:

  • 1 < = n u m s . l e n g t h < = 10000 1 <= nums.length <= 10000 1<=nums.length<=10000
  • 1 < = n u m s [ i ] < 2 31 1 <= nums[i] < 2^{31} 1<=nums[i]<231

哈希表

思路

  • 利用 map 统计每个元素出现的次数,然后取次数只出现一次的元素,即可。
class Solution {
    public int singleNumber(int[] nums) {
        // 统计每个元素出现次数
        Map<Integer, Integer> map = new HashMap<>();
        for (int i : nums) map.put(i, map.getOrDefault(i, 0) + 1);
        System.out.println(map);
        // 找到只出现一次的元素
        int res = 0;
        for (int key : map.keySet()) {
            if (map.get(key) == 1) res = key;
        }
        return res;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( n ) O(n) O(n)
    hash

位运算

参考:K佬题解

思路 🤔

  • 对于出现三次的数字,各二进制位 出现的次数都是 3 的倍数
  • 因此,统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字
    在这里插入图片描述

步骤 💡

  1. 统计nums所有元素 对应二进制每个位置上1的个数;
  2. 对count中每个位置1的个数 %3,并将 count[i] % 3 后对应的二进制转化为十进制
class Solution {
    public int singleNumber(int[] nums) {
        int[] counts = new int[32];
        // 1、统计nums所有元素 对应二进制每个位置上1的个数
        for (int num :nums) {
            for (int i = 0; i < 32; i++) { // int 32bit
                if ((num & 1) == 1) {
                    counts[i]++;
                }
                num >>>= 1; // 这里不要用有符号右移 >>,如果存在负数时就gg了
            }
        }
        System.out.println(Arrays.toString(counts));
        // 2、对count中每个位置1的个数 %3,并将 count[i] % 3 后对应的二进制转化为十进制
        int res = 0;
        for (int i = counts.length - 1; i >= 0; i--) { // 高位 --> 低位
            res <<= 1;
            res |= (counts[i] % 3);
        }
        return res;
    }
}

在这里插入图片描述

K佬 用的是更优的解法是 位运算 + 有限状态自动机,暂时还没看

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值