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<nums.length;i++)
        {
            ret = ret ^ nums[i];
        }
        
        return ret;
    }
    
}

第二题: 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<nums.length;i++)
        {
            if((nums[i] & k) == 0)
            {
                m ^= nums[i];
            }else
            {
                n ^= nums[i];
            }
            
        }
        
        int[] res = {m,n};
        return res;
        
    }
}

第四题: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<nums.length;i++)
        {
            if(nums[i] == a)
            {
                return new int[]{a,b};
            }
        }
        return new int[]{b,a};

    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354