LCP137 LeetCode 137. Single Number II

本文探讨了一道中等难度的算法题——在数组中找出仅出现一次的数字,其余数字均出现三次。通过位运算的方法实现了线性时间复杂度的解决方案,并详细解释了实现过程中的陷阱及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

位运算。机器码。这里有个坑。

题目

Acceptance : 38% Difficulty : Medium

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?

TagsBit Manipulation

题解

之前说过Single Number I 和 Single Number III。这个II 直到昨晚还是没能想出一个利用 Bit Manipulation 的方法。想到了两种方法,但不符合 Note 的提示。
1. 利用hash表,对每个数字建表,value = 出现次数。然后从头遍历hash表找到value = 1的那个key。
2. 排序,然后遍历。if (nums[i] = nums[i+1]) i = i + 3;else return nums[i]。这种算法复杂度就是排序的复杂度+n/3。

后来百度了一下找到了利用位运算解题的方法。
一句话概括就是:按位找到孤异数字的二进制数。

这种方法可以推广到其他数字都出现了n次的情况。
以其他数字是3,孤异数字是2为例。
右数第一位是1的数字共出现了n次,那么可以得出孤异数字的右数第一位数字是0。
右数第二位是1的数字共出现了n+1次,那么可以得出孤异数字的右数第二位数字是1。
右数第三位是1的数字共出现了0次,那么可以得出孤异数字的右数第一位数字是0。
。。。。
。。。。
于是,这种方法的时间复杂度就是 32n(粗略算,认真算的话,是遍历32次,每次需要n次与操作,n/2次自加操作,和1次或操作)。其实跟使用快排排序后再遍历的方法。只有当32 < log2n,也就是说,n > 232(粗略算)时,这种位运算的方法才体现出优越性。
OK,上代码:

AC Code

class Solution
{
public:
    int singleNumber(vector<int>& nums)
    {
        if (nums.size() == 1)
            return nums[0];
        int mask = 1, ans = 0;
        while(mask)
        {
            int cnt = 0;
            for (int j = 0; j < nums.size(); j++)
            {
            //  if(mask & nums[j] > 0) // There is a cheating pit   //#code1
            //  if ((mask & nums[j]) > 0) // It is a cheating pit, too.  //#code2
                if (mask & nums[j])   //#code3
                    cnt++;
            }
            if (cnt % 3 > 0)
                ans |= mask;
            mask <<= 1;
        }
        return ans;
    }
}

嗯。当我写完#code1时,用了个Custom Testcase [1, 1, 1, 2]。Run,出错了。于是在VS里单步调试。发现它一直在跳入cnt++语句。即使nums[j] = 2。
所以,如果这里不加括号使之变成#code2形式的话,那么这句if的判断过程是,先判断 nums[j] > 0,然后用mask & 刚刚的结果true。相当于true&true。所以计算出来的结果是错的。
必须强调的是,当判断句里用了位运算的时候,为确保答案正确,尽量把该有的括号加上去。

然而,当我用#code2进行Submit时,WA。又是一个值得注意的事情当nums[j]为负数的时候,计算结果就混乱了。

bool b = 2 & -2;
bool c = 1 & -2;
bool d = (1 << 31) & -2;
int ib = 2 & -2;
int ic = 1 & -2;
int id = (1 << 31) & -2;

把以上代码compile,debug,发现
b = true;
c = false;
d = true;
ib = 2;
ic = 0;
id = -2147483648。(0xffff ffff 8000 0000)
而 int ie = 1 << 32 。= 0.
也就是d和id的不同,(id < 0),而(d)为true。所以会导致WA。

那么,具体的更深入的原因是什么呢?

再看 bool e = (1 << 23) & -2, e = true;
int ie = (1 << 23) & -2, ie = 8388608;

-2的32位机器码表示为0xffff fffe。
那么原因就是,当使用#code2时,从第1位(右数第一位记为第0位)到第30位都将被 |=,而第31位(左数第一位)则因为 id < 0而未被 |=。
于是结果就成了 0x7fff fffe。 十进制的2147483646。

而当使用#code3时,从第1位到第31位全会因true而被 |=。将得到正确答案 0xffff fffe。即十进制的 -2。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海Enoch

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值