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

简介: 【每日算法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 位整数的每一位,就可以得到这个单独的数是多少了。

推广到一般情况:

如果其他数都出现了  次,一个数出现了一次。那么如果  是偶数,还是把所有的数异或起来就行了。如果  是奇数,那么统计每一位是 1 的个数,然后模  取余数就能得到那个单独的数了。

位运算

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


初始的时候在状态 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 ,得到如下的状态自动机:

因为一共有三个状态,所以我们需要用两个变量来表示状态。用 once 表示是否在状态 1,用 twice 来表示是否在状态 2 。那么两个变量都为 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)

代码

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

class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        int n = nums.size();
        int x = 0;
        for (auto i : nums) x ^= i;
        for (int i = 1; i <= n+2; ++i) x ^= i;
        int lb = x & -x;
        int y = 0;
        for (auto i : nums) {
            if (i&lb) y ^= i;
        }
        for (int i = 1; i <= n+2; ++i) {
            if (i&lb) y ^= i;
        }
        return {y, y^x};
    }
};

位运算(c++)

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;
    }
};
相关文章
|
1月前
|
消息中间件 算法 Java
抖音面试:说说延迟任务的调度算法?
Netty 框架是以性能著称的框架,因此在它的框架中使用了大量提升性能的机制,例如 Netty 用于实现延迟队列的时间轮调度算法就是一个典型的例子。使用时间轮调度算法可以实现海量任务新增和取消任务的时间度为 O(1),那么什么是时间轮调度算法呢?接下来我们一起来看。 ## 1.延迟任务实现 在 Netty 中,我们需要使用 HashedWheelTimer 类来实现延迟任务,例如以下代码: ```java public class DelayTaskExample { public static void main(String[] args) { System.ou
30 5
抖音面试:说说延迟任务的调度算法?
|
12天前
|
存储 自然语言处理 算法
位运算入门及简单算法题的应用
位运算入门及简单算法题的应用
15 1
|
12天前
|
算法 Java
Java数据结构与算法:位运算之位移操作
Java数据结构与算法:位运算之位移操作
|
12天前
|
算法 Java
Java数据结构与算法:位运算之与、或、异或运算
Java数据结构与算法:位运算之与、或、异或运算
|
16天前
|
存储 算法 Java
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
|
2月前
|
机器学习/深度学习 算法 固态存储
深度学习算法工程师面试问题总结| 深度学习目标检测岗位面试总结
本文给大家带来的百面算法工程师是深度学习目标检测岗位面试总结,文章内总结了常见的提问问题,旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中,我们还将介绍一些常见的深度学习目标检测面试问题,并提供参考的回答及其理论基础,以帮助求职者更好地准备面试。通过对这些问题的理解和回答,求职者可以展现出自己的深度学习目标检测领域的专业知识、解决问题的能力以及对实际应用场景的理解。同时,这也是为了帮助求职者更好地应对深度学习目标检测岗位的面试挑战,提升面试的成功率和竞争力。
|
26天前
|
数据采集 算法 数据挖掘
LeetCode 题目 80:删除排序数组中的重复项 II【算法面试高频题】
LeetCode 题目 80:删除排序数组中的重复项 II【算法面试高频题】
|
2月前
|
算法 C++
c++算法学习笔记 (10) 位运算
c++算法学习笔记 (10) 位运算
|
2月前
|
存储 机器学习/深度学习 人工智能
【算法基础】基础算法(三)--(双指针算法、位运算、离散化、区间合并)
【算法基础】基础算法(三)--(双指针算法、位运算、离散化、区间合并)
|
2月前
|
算法 搜索推荐 索引
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题(下)
数据结构与算法⑥(第二章OJ题,下)后八道链表面试题
29 0