【剑指 Offer】第 22 天

151 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情

一、前言

刷题啊!!!

开始刷 “剑指 Offer” 31天。刷完时间:2022.3.6 ~ 2022.3.20。

位运算:

符号说明运算规则
&两个位均为1,则为1
``两个位都为0, 则为0
异或两个位相同为0, 相异则为1
~取反0变1, 1变0
<<左移各二进位全部左移若干位,高位丢弃,低位补0
>>右移各二进位全部右移若干位,对于无符号数,高位补0; 有符号数,有的补符号位

小技巧:

  • 异或运算: x ^ 0 = x
  • 与运算:x & 0 = 0



二、题目

题目:

  • 数组中数字出现的次数 I
  • 数组中数字出现的次数 II

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

题目描述


一个整型数组 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

题解


题意:明确要求,时间复杂度是 O(n),空间复杂度是 O(1),那么就不能使用额外存储 和 暴力法(2次循环)。

问题一:如何找到只出现一次的数?

用异或 ^ :两个相同的数异或后为 0

# 1 异或 1 = 0
1 ^ 1 == 0

# 根据这个特性,就能过滤出只出现过一次的数。
# 根据题目,会有两个数只出现过一次。
# 那么结果就是那两个数的异或值。

问题二:如何从异或值中分解出两个数?

# 假设两个只出现过一次的数为 x, y
# 因为 x != y, 所以 x 和 y 的二进制至少有一位不同
# 那么就要找到这位。
# 通过这个位可以将数组 nums 划分为两个数组 nums1 和 nums2 (相同的数,一定在同一组内)
# 再通过异或两个数组,就能得出 x 和 y

思路:假设两个只出现过一次的数为 x, y

  1. 遍历数组 nums ,异或得到结果 a
  2. 找到数 a 中最低位为 1 的数 b
  3. 通过数 b 将数组 nums 划分为两个数组 nums1nums2
  4. 分别异或这两个数组,得到 x 和 y

AC 代码如下:

class Solution {
    public int[] singleNumbers(int[] nums) {
        int a = 0;
        // 1. 异或得到 a
        for (int num : nums) {
            a ^= num;
        }
        // 2. 求最低数 b
        int b = 1;
        while ((a & b) == 0) b <<= 1;
        // 3. 分组 异或
        int x = 0, y = 0;
        for (int num : nums) {
            if ((num & b) != 0) x ^= num;
            else y ^= num;
        }
        return new int[] {x, y};
    }
}



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

题目描述


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

示例 1:

输入:nums = [3,4,3,3]
输出:4
示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

限制:

  • 1 <= nums.length <= 10000
  • 1 <= nums[i] < 2^31

题解


AC 代码如下:

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int num : nums){
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }
}