[剑指offer刷题56 - I] - 数组中数字出现的次数

5 篇文章 0 订阅
本文探讨了如何在保证时间复杂度为O(N)且空间复杂度为O(1)的前提下,寻找整型数组中仅出现一次的两个数字。首先介绍了使用哈希表统计出现次数的方法,然后通过异或运算和分组技巧巧妙地解决了问题。最后,展示了两种解决方案并解释了原理。
摘要由CSDN通过智能技术生成

题目[题目及部分题解来自LeeCode]

在这里插入图片描述


solution one
注意题目要求时间复杂度 O(N) ,空间复杂度 O(1) ,因此首先排除 暴力法 和 哈希表统计法 。但如果没有要求时间、空间复杂度,就可以用着两者方法,下面给出使用哈希表代码:
class Solution {
    public int[] singleNumbers(int[] nums) {
        Map<Integer,Integer> set = new HashMap<Integer, Integer>();
        boolean[] arr1 = new boolean[nums.length];
        int[] arr = new int[2];
        for(int i = 0; i < nums.length; ++i){
            if( set.get(nums[i]) == null ) set.put(nums[i], i);
            else{
              arr1[set.get(nums[i])] = true;
              arr1[i] = true;  
            } 
        }
        for(int j = 0, k = 0; j < arr1.length; ++j){
            if(arr1[j] == false){
                arr[k] = nums[j];
                ++k;
            }
        }
        return arr;
    }
}
solution two
在了解第二种方法前,我先改下题目。如果将除两个数字以外改成一个数字以外,你会怎么做?如:设整型数组 nums 中出现一次的数字为 x ,出现两次的数字为 a, a, b, b,... x,即: nums = [a, a, b, b,... x]。think about it for one minute !


注意,异或有个重要的性质,两个相同数字异或为 0,不同数字异或为1,即对于任意整数 a 有 a ⊕ a = 0 。因此,若将 nums 中所有数字执行异或运算,留下的结果则为 出现一次的数字 x ,即:
a ⊕ a ⊕ b ⊕ b ⊕ ··· ⊕ x
= 0  ⊕  0  ⊕ ···  ⊕  x 
=  x

异或运算满足交换律 a ⊕ b = b ⊕ a ⊕ b = b ⊕ a ,即以上运算结果与 nums 的元素顺序无关。代码如下:

public int[] singleNumber(int[] nums) {
    int x = 0;
    for(int num : nums)  // 1. 遍历 nums 执行异或运算
        x ^= num;
    return x;            // 2. 返回出现一次的数字 x
}

在这里插入图片描述
而本体难点在于,数组nums有 两个 只出现一次的数字,因此无法通过异或直接得到这两个数字。

本体采用 分组异或 的方法:

  • 首先用初始化为0的res对每个元素进行全员异或(0与任意非0数异或结果等于其本身),这样得到的结果就是两个不同的数 a 和 b 的异或结果。
  • 题目中给定是两个不同的数 a 和 b ,则 a 和 b 在相同的二进制位必有一个1和一个0。(如果都相同,a 和 b就是同一个数了)
  • 利用这个区别来对nums数组中的数进行分组,假设第二位a为1,b为0,则用2(2的二进制位中的第二位为1)来与nums数组中进行与运算(与运算相同为1,不同为0),与运算结果为1,则与a进行异或运算,结果为0则与b进行异或运算。这样就起到分组的效果。

在这里插入图片描述

class Solution {
    public int[] singleNumbers(int[] nums) {
        int group1 = 0, group2 = 0,res = 0, position = 1;
        // 全员异或遍历,得到两个不重复的数字异或结果
        for(int num : nums){
            res ^= num;
        }
        // 找到两个数字所在位不同的位置
        while( ( position & res) == 0 ){
            position <<= 1;
        }
        // position位是0分成一组, position位是1分成一组
        for(int num : nums){
            if( (position & num) == 0 )
                group1 ^= num;
            else
                group2 ^= num;
        }
        return new int[] {group1, group2};

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值