【位运算】【LeetCode】剑指offer-56 数组中数字出现的次数 II

剑指 Offer 56 - II. 数组中数字出现的次数 II

在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]
输出:4

示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

限制:

1 <= nums.length <= 10000
1 <= nums[i] < 231



解题思路

大佬的解题思路是三进制,也就是用两位来模拟出现三次的情况,一旦三次了就置为0,这样最后只剩下的数即为只出现一次的数。

为了更清楚地分析,我们可以列出一个真值表

two_one_input(num)------>twoone
00000
01001
10010
00101
01110
10100

其中 two_和one_的组合只会有00、01、10三种情况,num有两种情况,因此总共是6种情况,分别计算对应的结果two、one。
由真值表可得:

two = (two_)(!one_)(!num) + (!two_)(one_)num
one = (!two_)(one_)(!num) + (!two_)(!one_)num

one可化简为:

one = (!two_)(one_ ⊕ num)

此时我们其实已经可以写出初步的代码了

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int num : nums){
            //因为下一步ones就要更新为新值
            //这里我们用一个临时变量tmp保存ones
            int tmp = ones;
            ones = ones ^ num & ~twos;
            twos = twos & ~tmp & ~num | ~twos & tmp & num;
        }
        return ones;
    }
}

还可以更简洁吗?我们可以试一试。
还记得刚才的真值表吗?如果是one_先更新变成了one,此时two还没有计算,那如果这个时候用新的one来代替旧的one_可以吗?
图1
答案是肯定的,这个时候我们只去看two_到two的变化就可以了

two_更新后的oneinput(num)------>two
0000
0100
1001
0110
0011
1010

可以得到:

two = (two_)(!one)(!num) + (!two_)(!one)num
//提出共同的(!one) 发现剩下的也是一组异或
two = (!one)(two_ ⊕ num)

到这里,答案就呼之欲出了
------------------------------最终答案在这里↓-------------------------------------

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int num : nums){
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }
}

-----------------------------最终答案在这里↑------------------------------------
到此结束! 诶,还没结束呢, 那为什么不能用two来表示one呢?
如果你想,可以试试。但这个时候你会发现,前后的two_和two 在输出one为1的行内,是完全一致的,
图2

也就是说你想用two来代替one表达式中的two_,结果只是单纯把变量换个名字而已,其他的没区别。。。

one = (!two_)(one_)(!num) + (!two_)(!one_)num
one = (!two_)(one_ ⊕ num)
					||
					v
one = (!two)(one_)(!num) + (!two)(!one_)num
one = (!two)(one_ ⊕ num)

体现在代码上就是这样的:

class Solution {
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int num : nums){
            int tmp = twos;
            twos = twos & ~ones & ~num | ~twos & ones & num;
            ones  = ones ^ num & ~tmp;
        }
        return ones;
    }
}

ok到此结束,一口气拉到底的码友们,这个不是最简洁的答案,第二个代码块才是。。。

后记

大概是我比较笨,看题解啊什么的,代码直接甩出来我也不太懂,导致的结果就是这道题明明做过,但是隔一段时间之后再让我去做,可能关于最佳解法的思路还是不通透,说白了就是都是靠背而不是靠理解的。自己这么过一遍之后可以说是理解了,膜拜一下大佬的思路。另外这篇博客也给了我很大的启发,也谢谢这位码友。

记录一下 备忘

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值