Leetcode-只出现一次的数系列

Leetcode题库中,关于数组中元素出现次数的题目有以下几题,重点考察的是对运算符的运用,现在统一归纳,方便后续复习查看。

位运算符简介:

异或运算符(^): 两个数相同则为0,否则为0。(又称无进位相加)
与运算符(&):两个数都为1则为1,否则为0。
或运算符( |):两个数只要有一个为1则为1,否则就为0。
非运算符(~):位为0,结果是1,如果位为1,结果是0。

如果位为0,结果是1,如果位为1,结果是0,下面看一个简单例子
左移运算符(<<):value << num,num 是要向左左移动的位数,丢弃最高位,0补最低位。
右移运算符(>>):value << num,num 是要向右 移动的位数,符号位不变,左边补上符号位(正数0负数1)。
无符号右移运算符(>>>):无符号右移规则和右移运算是一样的,只不过忽略了符号位扩展,0补最高位。

根据运算符异或的特性可以推导出:
a ^ a = 0
a ^ 0 = a
a ^ b ^ c = a ^ c ^ b
a & ~a = 0
a & ~0 = a

由易到难:

第一题:136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

题解:根据异或运算的规律,由于其满足交换定律,所以a ^ b ^ a = a ^ a ^ b = b,所以针对该题,只需要把所有的元素进行异或运算,最终结果就是要找到元素。

class Solution {
    public int singleNumber(int[] nums) {
      
        int ret = 0;
        for(int i=0;i

第二题: 137. 只出现一次的数字 II
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

题解:把数组中的元素当做32位的二进制码处理,每一位非0即1,由于除了一个特殊元素外,其他每个元素均出现了三次,所以所以该位总和为3N或者3N+1,其中1就是那个只出现一次的元素贡献的。
所以只需要数组遍历1到32位,找到每个位置上那个特殊元素贡献的值,就能找到最终的元素。

class Solution {
    public int singleNumber(int[] nums) {

        int ret = 0;
        for(int i=0;i<32;i++)
        {
            int mask = 1 << i;
            int count = 0;
            for(int num : nums)
            {
                //判断该位是否是1
                if((num & mask) != 0 )
                {
                    count++;
                }
            }

            //说明想要的那个元素贡献了一位1
            if(count % 3 != 0)
            {
                ret |= mask;
            }
        }

        return ret;
    }
}

如果对该题进行拓展,比如其余元素出现2次、3次、4次、5次、k次等等,均可套用该方法,只需要将最后count模k即可。

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

题解:该题和上面的第一次类似,不同的是数组中存在两个出现一次的数,如果要使用异或运算找出这两个元素的话,就要想办法把这个两个元素划分到不同区域,比如用x、y的第一位不同位来区分。

class Solution {
    public int[] singleNumbers(int[] nums) {

        //该步骤为了得到m^n,因为相同的元素异或运算=0
        int a = 0;
        for(int num : nums)
        {
            a ^= num;
        }
        
       //找到m^n 第一位是1,用该标识划分数组
        int k = 1;
        while((a & k) == 0)
        {
            k <<= 1;
        }
        
        int m =0;
        int n=0;
        for(int i=0;i

第四题:645. 错误的集合
集合 S 包含从1到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个元素复制了成了集合里面的另外一个元素的值,导致集合丢失了一个整数并且有一个元素重复。
给定一个数组 nums 代表了集合 S 发生错误后的结果。你的任务是首先寻找到重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。
示例 1:
输入: nums = [1,2,2,4]
输出: [2,3]

题解:对于一个正常的集合S1,S1包含从1到n,而题目中一场的S集合丢失了一个元素并且被其他的元素代替,根据这个思路可以将两个集合异或处理,这样就能得到那两个特殊的元素x^y,比如
S1:a , b, c, d , d
S : a , b, c, d , e
其中元素e被元素d代替,S1 ^ S = (a ^ b ^ c ^ d ^ d) ^ (a ^ b ^ c ^ d ^ e)=(a ^ b ^ c) ^ (a ^ b ^ c ^ d ^ e) = d ^ e
借助第三题中划分元素的思想,可以把S1和S也同步划分,这样S中划分出的区域要比S1划分出的区域中多一个d或者e,这样再进行异或处理便能拿到最终的两个元素。

class Solution {
    public int[] findErrorNums(int[] nums) {

        //使用位运算符
        int x = 0;

        int a = 0;
        int b = 0;

        //该步骤得到x = c ^ d, 其中c:重复的,d:丢失的
        for(int num : nums)
        {
            x ^= num;
        }

        for(int i=1;i<=nums.length;i++)
        {
            x ^= i;
        }

        //找到第一个不为0的位,对c ^ d进行分组
        int k = 1;
        while((x & k) == 0)
        {
            k <<= 1;
        }

        //通过下面的两个循环,拿到最终的a和b
        for(int num : nums)
        {
            if((num & k) == 0)
            {
                a ^= num;
            }else
            {
                b ^= num;
            }
        }

        for(int i=1;i<=nums.length;i++)
        {
            if((i & k) == 0)
            {
                a ^= i;
            }else
            {
                b ^= i;
            }
        }

        //确定重复的元素
        for(int i=0;i

你可能感兴趣的:(Leetcode-只出现一次的数系列)