【每日算法Day 72】谷歌面试题:又双叒叕是位运算,最详细的自动机推导过程

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

题目链接


LeetCode 137. 只出现一次的数字 II[1]

题目描述


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

说明:

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

示例1

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

示例2

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

题解


逐位考虑


我们单独看二进制某一位,先不看单独的那个数,其他所有数字都出现了 3 次,所以那一位是 1 的个数一定是 3 的倍数。

再考虑这个出现一次的数,如果这一位是 1 ,那么这一位 1 的次数模 3 为 1 ,否则的话模 3 就是 0 。

那么就很简单了,统计一下有多少个数这一位上是 1 ,然后模 3 取余数,结果就是这个单独的数这一位上的值了。

遍历 32 位整数的每一位,就可以得到这个单独的数是多少了。

推广到一般情况:

image.png

位运算


我们还可以用自动机来做这题,根据某一位 1 的个数,我们可以得到如下的状态自动机:image.png

初始的时候在状态 0 (有 0 个 1),然后如果下一个数这一位是 1,就进入状态 1(有 1 个 1),接着如果下一个数这一位是 1,就进入状态 2(有 2 个 1),接着如果下一个数这一位是 1,就进入状态 3(有 3 个 1),最后如果再来了一个数这一位还是 1,就说明是一个新的数了,等价于回到了状态 1。而每个状态如果来的数这一位是 0 ,都会保持状态不变。

当然这个自动机还可以简化,注意观察可以发现状态 3 和状态 0 是等价的(输入 0 都保持不变,输入 1 都会进入状态 1)。所以我们将状态 1 和状态 3 合并为一个状态 0 ,得到如下的状态自动机:image.png

因为一共有三个状态,所以我们需要用两个变量来表示状态。用 once 表示是否在状态 1,用 twice 来表示是否在状态 2 。那么两个变量都为 0 就表示在状态 0 。然后可以得到如下的状态转移表:image.png

注意观察 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)

代码


逐位考虑(c++)(代码有误,正确代码见留言第一条)

classSolution {
public:    
vector<int>missingTwo(vector<int>&nums) {   
intn=nums.size();   
intx=0;   
for (autoi : nums) x^=i;   
for (inti=1; i<=n+2; ++i) x^=i;   
intlb=x&-x;    
inty=0;    
for (autoi : nums) {    
if (i&lb) y^=i;    
        }     
for (inti=1; i<=n+2; ++i) {  
if (i&lb) y^=i;   
        }     
return {y, y^x};  
    }
};

位运算(c++)

classSolution {
public: 
intsingleNumber(vector<int>&nums) {   
intonce=0, twice=0;   
for (autox : nums) {   
once= (once^x)&(~twice);   
twice= (twice^x)&(~once);   
        }      
returnonce;  
    }
};

相关题目


LeetCode 136. 只出现一次的数字[2]

LeetCode 面试题56 - I. 数组中数字出现的次数[3]

LeetCode 面试题 17.19. 消失的两个数字[4]

参考资料


[1]

LeetCode 137. 只出现一次的数字 II: https://leetcode-cn.com/problems/single-number-ii/

[2]

LeetCode 136. 只出现一次的数字: https://leetcode-cn.com/problems/single-number/

[3]

LeetCode 面试题56 - I. 数组中数字出现的次数: https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/

[4]

LeetCode 面试题 17.19. 消失的两个数字: https://leetcode-cn.com/problems/missing-two-lcci/

image.png

作者简介:godweiyang知乎同名华东师范大学计算机系硕士在读,方向自然语言处理与深度学习喜欢与人分享技术与知识,期待与你的进一步交流~

相关文章
|
3天前
|
算法
刘谦春晚纸牌魔术背后的数学—海明码原理简介
刘谦春晚纸牌魔术背后的数学—海明码原理简介
|
3天前
日拱一卒,月进一步(6)(杨辉三角2)
119. 杨辉三角 II - 力扣(LeetCode)
13 0
|
9月前
|
存储 C++
魔幻而精妙:探秘杨辉三角的奥秘
在这篇文章中,我们将深入研究题目 杨辉三角的内涵与解决方法。杨辉三角是数学领域的一颗璀璨明珠,通过对该问题的解析,笔者将揭示它独特的规律与生成方式。
75 0
|
10月前
|
存储 算法
1【百度之星】基础算法讲解—穷举、贪心(下)
1【百度之星】基础算法讲解—穷举、贪心(下)
|
10月前
|
存储 算法 搜索推荐
1【百度之星】基础算法讲解—穷举、贪心(上)
1【百度之星】基础算法讲解—穷举、贪心(上)
|
12月前
|
算法 C++
【每日算法Day 72】谷歌面试题:又双叒叕是位运算,最详细的自动机推导过程
【每日算法Day 72】谷歌面试题:又双叒叕是位运算,最详细的自动机推导过程
|
12月前
|
算法 C++ Python
【每日算法Day 68】脑筋急转弯:只要一行代码,但你会证吗?
【每日算法Day 68】脑筋急转弯:只要一行代码,但你会证吗?
四道好题分享(看似简单,但是棘手)
四道好题分享(看似简单,但是棘手)
71 0
|
算法 Java
洛谷新手村算法题分析
本文汇总洛谷新手村算法题的分析解答,题目代码基本由Java实现。
87 0
洛谷新手村算法题分析
算法每日一题——第七天——消除游戏
算法每日一题——第七天——消除游戏
算法每日一题——第七天——消除游戏