位运算+异或+Not+and+xor 136. 只出现一次的数字+137. 只出现一次的数字 II +面试题56 - I. 数组中数字出现的次数

136. 只出现一次的数字

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

解题
位运算(时间复杂度N,空间复杂度1)
异或:位相同则0,相异则1;
所以对一个数自身异或,返回0;

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        for(int num:nums)
            res^=num;
        return res;
    }
};

从0开始对每个num逐个异或,若异或到相同元素,会抵消异或效果,若异或到单个元素,即变为单个元素;

137. 只出现一次的数字 II

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,3,2]
输出: 3

示例 2:

输入: [0,1,0,1,0,1,99]
输出: 99

解题
数字分为出现1次和三次的,有限状态自动机: 每一位分为三种状态——无数字(0),有一次数字(1),有两次数字(0),有三次数字(0);

注意观察 once 只有两种情况下转移后为 1 。一种是 once=0, twice=0, x=1 ,另一种是 once=1, twice=0, x=0 。其他所有情况下 once 都转移为 0 。这两种情况都满足 x^once=1 并且 twice=0 ,所以 once 的转移就是 once = (x^once) & (~twice) 。

同理,观察 twice 只有两种情况下转移后为 1 。一种是 once=1, twice=0, x=1 ,另一种是 once=0,
twice=1, x=0 。其他所有情况下 twice 都转移为 0 。这两种情况都满足 x^twice=1 并且 once^twice=1
,所以 twice 的转移就是 twice = (x^twice) & (once^twice) 。但是!!! once
已经抢先一步转移过了,所以值已经变掉了,一个解决方法就是用临时变量保存一下前一个状态的 once 值。另一个方法就是,这两种情况下,once
都会转移到 0 ,所以判断条件直接用转移后的 once=0 就行了,随后转移就是 twice = (x^twice) & (~once) 。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int once=0,twice=0;
        
        for(auto x:nums)
        {
            once = (once^x)&(~twice);
            twice = (twice^x)&(~once);
        }
        return once;
    }
};

第一次出现:放入once,twice=非once&once=空;
第二次出现:once抵消为空,放入twice;
第三次出现:once=~twice&twice=空,twice抵消为空;
故为三次循环,保留第一次出现的到once,第二次第三次出现全部抵消,第四次又归为第一次出现;

解法2
逐位计算,计算int 32位每一位的1的个数,余3即为单个的数的那一位的位数;

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res=0;
        int r;
        for(int i=0;i<32;i++)
        {   
            r=0;
            for(auto num:nums)
            {
                r+=1&(num>>i);
            }
            res|=(r%3)<<i;
        }
        return res;
    }
};

注意点
判断num第i位是否为1,需要1&(num>>i),若写(1<<i)&num),则会影响i后面的位数,因为1<<i相当于第i位为1,其他位为0!

面试题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 <= 10000

解题
一组数字中找到两个单个出现的数;
将数据分为两部分——一部分包含第一个单个数字:另一部分包含另一个单个数字,然后用位运算即可分别找到两个单个数字;

如何将两个单个数字分开?
找到两个数不同的一位;
res=0,与nums中每一位进行异或操作,得到任何一位为1,则说明两个单个数在该位不相同!

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int res=0;
        for(int num:nums)
            res ^=num;
        int h=1;
        while((h&res)==0)      //找到两个数不等的那一位
            h<<=1;
        int a=0;
        int b=0;
        for(int num:nums)
        {
            if(num&h)        
            //大于0就行,说明该位置有数
                a ^=num;
            else b ^=num;
        }
        vector<int> result={a,b};
        return result;
    }

};

num&h为一类;!(num&h)为一类,则一类含一个数;
且相同数字都在同一组中;

注意点
位运算分优先级,记得加括号();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值