您的位置:首页 > 编程语言 > C语言/C++

[C++]LeetCode: 67 Single Number II

2015-01-04 22:18 381 查看
题目:

Given an array of integers, every element appears three times except for one. Find that single one.

Note:

Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

找出没有出现3次的数字,同样要求线性时间,不使用额外空间。

Answer 1: 统计每位1的个数

思路:

考虑数字在计算机中都是按照比特存储。

如果所有数字的第i位比特位求和并且模3,结果是1或者0,因为每个数字只可能出现3次或者1次,所以这第i位比特位可以代表“single number”, 即我们可以通过最后模3,来确定single number每一个比特位的值是0还是1.。和Single
Number的异或有异曲同工之妙。

复杂度:由于外层循环统计32位,所以复杂度是O(32*N) 即 O(N)

Attention:

1. 一定要初始化结果为0,函数体内的内置变量值随机(编译器可能初始化为0,但千万别依赖这种可能行为,它可能为程序带来未定义行为)。如果是全局范围内的内置类型变量均被编译器自动初始化为0值。

<span style="font-size:14px;">int ret = 0;</span>


error:

<span style="font-size:14px;">int ret;</span>


1 / 11 test cases passed.
Status:


Wrong Answer

Submitted: 10 minutes ago
Input:[1]
Output:966166951
Expected:1
2. 如何每一位统计1的个数,需要设置d = 1并通过移动 i 位,来得到想要的结果。

<span style="font-size:14px;">int d = 1 << i;  //计算第i位,其余位清零
//统计第i位的情况
 for(int j = 0; j < n; j++)
{
     if(A[j] & d) c++;   
}
            
 //当第i位,1的个数不是3的倍数时,应该将该位置1
 if(c % 3) ret |= d;</span>
AC Code:

<span style="font-size:14px;">class Solution {
public:
    int singleNumber(int A[], int n) {
        int ret = 0;
        
        for(int i = 0; i < 32; i++)
        {
            int c = 0;
            int d = 1 << i;  //计算第i位,其余位清零
            //统计第i位的情况
            for(int j = 0; j < n; j++)
            {
                if(A[j] & d) c++;   
            }
            
            //当第i位,1的个数不是3的倍数时,应该将该位置1
            if(c % 3) ret |= d;
        }
        
        return ret;
    }
};</span>


Answer 2: 掩码法
思路:ones记录到目前为止计算的变量为止,二进制1出现“1次”(mod 3 为1)的数位。twos记录到目前为止计算的变量为止,二进制出现“2”次(mod 3 为2)的数位。当ones和twos中的某一位同时为1则表示,二进制出现1为3次,此时将该位清零。就是用二进制来模拟三进制计算。最终ones记录的就是最终结果。

ones
代表第ith 位只出现一次的掩码变量

twos
代表第ith 位只出现两次次的掩码变量

threes
代表第ith 位只出现三次的掩码变量

当第 i 个比特位出现了第三次,清空ones和twos的第 i 个比特位都为0.最后的答案就是ones.

例子:

加入数组前三个为5,则各个变量变化如下:

ones = 101, twos = 0, threes = 0;

ones = 0, twos = 101, threes = 0;

ones
= 101, twos = 101, threes = 0;(执行到清ones ^= A[i]) => ones = 0, twos = 0, threes = 101;(执行到清零后)

Attention:

这种位的操作方法十分巧妙,需要好好学习,刚开始会比较难理解,但是从模拟三进制方法入手,就会容易些,并且每个变量都代表32比特位的一个数组。

AC Code:

class Solution {
public:
    int singleNumber(int A[], int n) {
        int ones = 0, twos = 0, threes = 0;
        
        for(int i = 0; i < n; i++)
        {
            twos |= ones & A[i];  //twos积累值
            ones ^= A[i];  //ones不断求反,3次异或和1一次异或结果一样
            threes = ones & twos; //当该数出现三次时,ones和twos都保留了该位的值
            ones &= ~threes;  //清零出现三次的该位的值
            twos &= ~threes;
        }
        
        return ones;
        
    }
};


看到另外一篇博客中,还有该方法的推广应用,可以用于开拓思路。


leetcode Single Number II - 位运算处理数组中的数

扩展一:

给定一个包含n个整数的数组,除了一个数出现二次外所有的整数均出现三次,找出这个只出现二次的整数。ones记录1出现一次的数,twos记录1出现2次的数,容易知道twos记录的即是最终结果。

扩展二:

给定一个包含n个整数的数组,有一个整数x出现b次,一个整数y出现c次,其他所有的数均出现a次,其中b和c均不是a的倍数,找出x和y。使用二进制模拟a进制,累计二进制位1出现的次数,当次数达到a时,对其清零,这样可以得到b mod a次x,c mod a次y的累加。遍历剩余结果(用ones、twos、fours...变量表示)中每一位二进制位1出现的次数,如果次数为b mod a 或者 c mod a,可以说明x和y的当前二进制位不同(一个为0,另一个为1),据此二进制位将原数组分成两组,一组该二进制位为1,另一组该二进制位为0。这样问题变成“除了一个整数出现a1次(a1
= b 或 a1 = c)外所有的整数均出现a次”,使用和上面相同的方式计算就可以得到最终结果,假设模拟a进制计算过程中使用的变量为ones、twos、fours...那么最终结果可以用ones | twos | fours ...表示。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: