前言:

本题解整理了一位大佬在leetcode中的代码的方法,该博文致力于让所有人都能够能够看懂该方法。为此,本题解将从统计数字出现次数的解题方式开始讲起,再推导出逐位统计的解题方式,期望以循序渐进的方式得出最终代码的思想。

相关知识关键字:

二进制、位运算、真值表、逻辑表达式、状态机

题目:

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

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

示例 1:

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

输出:4

示例 2:

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

输出:1

题解:

对于数组nums,其只有一个数字出现了一次,其余数字均出现了三次,一种直观的想法是直接采用一个map统计各个字符出现的次数,最后再遍历map中的各个键值对,直到找到只出现了一次的数字。其代码如下

    public int singleNumber(int[] nums) {
        //统计各个数字出现的次数,键为数字,值为出现的次数
        Map<Integer,Integer> map =new HashMap<Integer,Integer>();
        for(int i:nums){
            if(!map.containsKey(i)){
                map.put(i,1);
                continue;
            }
            map.put(i,map.get(i)+1);
        }
        //遍历map中的键值对,查看值出现次数为1的键,即为答案
        int result = 0;
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            if(entry.getValue()==1){
                result = entry.getKey();
                break;
            }
        }
        return result;
    }

对于该解题方法,其空间复杂度为O(n),时间复杂度为O(n),这显然不会是该题的最优解。

在得出逐位运算的解题方式之前,我们需要研究下该数组中的数字用二进制的方式进行表示的特点。

以题干给出的示例1为例,nums=[3,4,3,3],将数组中各个数字采用二进制的方式写出,

3 = (0011)2

4 = (0100)2

3 = (0011)2

3 = (0011)2

通过对数组中各个数的二进制表示形式逐位进行观察,我们可以发现,当数组中只出现一次的那个数字(用k表示)在二进制的对应位为0时,该对应位为1在数组各个数字中出现的总次数应当为3^n ,当k的对应位为1时,该对应位为1在数组各个数字中出现的总次数应当为 3^n + 1,为此,我们可以统计数字中的各个位中1出现的次数,当为3^n 次时,只出现一次的数字的对应位应当为0,当为3^n + 1次时,只出现一次的数字的对应位应当为1。由此,我们可以得到如下代码:

    public int singleNumber(int[] nums) {
        if(nums==null||nums.length==0) return 0;
        int result = 0;
        for(int i = 0;i<32;i++){
            //统计该位1的出现次数情况
            int count = 0;
            int index = 1<<i;
            for(int j:nums){
                //该位与操作后的结果不为0,则表示该位为1的情况出现了
                if((index&j)!=0){
                    count++;
                }
            }
            //该位上出现1的次数mod3后为1,表示出现一次的数字该位为1
            if(count%3==1){
                result|=index;
            }
        }
        return result;
    }

对于该解题方法,其时间复杂度为O(n),空间复杂度为O(1)。在某种程度上,这是最优解了。但是,该题解仍有改进的空间(其时间复杂度的常系数为32)。

有了对数组中数字的各二进制位进行逐一统计分析出现次数的相关基础后,我们便可以推导出那个击败100%的答案的解法了。回顾上面的解题方法的分析部分,其需要我们对数字的二进制位逐位进行统计,对于int数据类型,我们需要遍历32次数组(int占4字节),以便统计出各个二进制位出现的次数。那我们有没有办法只遍历一次数组便得出答案呢?当然有,我们可以一次分析32bit的int的各个位在数组的各个数字中出现的次数。在分析上面的代码我们可以发现,实际上,我们只需要记录对应位出现的次数为0、1、2次的情况,当对应位出现次数为3的时候,我们便可以将该位出现的次数置为0,重新开始进行计数。由于int型中的各个二进制位出现的次数为3进制的,为此我们需要两个位来记录各个位出现的次数,由此我们需要引入两个变量a,b来统计对应位出现的次数。由ab两个变量组合起来来记录各个二进制位出现为1的情况。变量a表示高位的情况,变量b表示低位的情况,而在遍历数组运算完成之后,遍历b的值便是答案。

变量ab组合的各个二进制位组合的形式有如下三种,考虑进新引入的变量c的各二进制位的情况,我们可以得到如下真值表:

由以上真值表,我们便可得出变量a,b的逻辑表达式,其表示如下

a = a’(!b’)(!c)+(!a’)b’c

b = (!a’)b’(!c)+(!a’)(!b’)c = (!a’)[b’(!c)+(!b’)c] = (!a’)[b’^c]

由此,我们可以得到如下代码

    public int singleNumber(int[] nums) {
        //a对应位为1表示出现2次的记录,b对应位表示出现1次或0次的记录,ab共同组成该位出现的次数
        int a = 0,b =0;
        for(int i:nums){
            int temp = a;
            a = (~a&b&i)|(a&~b&~i);
            b = ~temp&(b^i);
        }
        return b;
    }

实际上,我们还能对a的逻辑表达式进行简化,先得到b的逻辑表达式,之后用b代替b’作为输入,由此可以简化a为

a = (!a’)(!b)c+a’(!b)(!c) = (!b)[(!a’)c+a’(!c)] = (!b)[a’^c]

由此,我们可以得到如下代码

    public int singleNumber(int[] nums) {
        //a为对应位的1出现2次的记录,b为对应位出现1次的记录,ab共同组成该位出现的次数
        int a = 0,b =0;
        for(int i:nums){
            b = ~a&(b^i);
            a = ~b&(a^i);
        }
        return b;
    }

至此,我们得到了最终的代码。


这个是本人的公众号,致力于写出绝大部分人都能读懂的技术文章。欢迎相互交流,我们博采众长,共同进步。

K:剑指offer-56 题解 谁说数字电路的知识不能用到算法中?从次数统计到数字电路公式推导,一文包你全懂的更多相关文章

  1. 剑指 Offer 56 - II. 数组中数字出现的次数 II + 位运算

    剑指 Offer 56 - II. 数组中数字出现的次数 II Offer_56_2 题目详情 解题思路 java代码 package com.walegarrett.offer; /** * @Au ...

  2. 剑指 Offer 56 - I. 数组中数字出现的次数 + 分组异或

    剑指 Offer 56 - I. 数组中数字出现的次数 Offer_56_1 题目描述 解题思路 java代码 /** * 方法一:数位方法 */ class Offer_56_1_2 { publi ...

  3. 《剑指offer》题解

    有段时间准备找工作,囫囵吞枣地做了<剑指offer>提供的编程习题,下面是题解收集. 当初没写目录真是个坏习惯(-_-)||,自己写的东西都要到处找. 提交的源码可以在此repo中找到:h ...

  4. 【剑指Offer面试编程题】题目1349:数字在排序数组中出现的次数--九度OJ

    题目描述: 统计一个数字在排序数组中出现的次数. 输入: 每个测试案例包括两行: 第一行有1个整数n,表示数组的大小.1<=n <= 10^6. 第二行有n个整数,表示数组元素,每个元素均 ...

  5. 剑指 Offer 30. 包含min函数的栈 + 双栈实现求解栈中的最小值

    剑指 Offer 30. 包含min函数的栈 Offer_30 题目描述: 题解分析: 题目其实考察的是栈的知识,本题的目的是使用两个栈来求解最小值. 第二个栈主要用来维护第一个栈中的最小值,所以它里 ...

  6. 剑指Offer的学习笔记(C#篇)-- 数字在排序数组中出现的次数

    题目描述 统计一个数字在排序数组中出现的次数. 一 . 题目分析 该题目并不是难题,但该题目考察目的是正确的选择合适的查找方法.题目中有一个关键词是:排序数组,也就是说,该数组已经排好了,我一开始直接 ...

  7. 剑指offer——56在排序数组中查找数字

    题目描述 统计一个数字在排序数组中出现的次数.   题解: 使用二分法找到数k然后向前找到第一个k,向后找到最后一个k,即可知道有几个k了 但一旦n个数都是k时,这个方法跟从头遍历没区别,都是O(N) ...

  8. 剑指offer 56.删除有序链表中的重复结点

    56. 删除有序链表中的重复结点 题目描述 在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针. 例如,链表1->2->3->3-> ...

  9. 【位运算】剑指offer 56. 数组中数字出现的次数

    这是一系列位运算的题目,本文将由浅入深,先从最简单的问题开始: 问题1: 一个数组中只有一个数字出现过1次,其余数字都出现过两次,请找到那个只出现1次的数字.要求时间复杂度是 \(O(n)\),空间复 ...

随机推荐

  1. 使用 Hexo 在 GitHub 上建立博客 · Utopia's Daily Note

    使用 Hexo 在 GitHub 上建立博客 # 写在前面 其实我在一月份的就开始写了三篇博客文章,你没有看错,只是写了三篇,然后,就没有然后了.我还在其中一篇文章中写着,不知道自己能够坚持多久.事实 ...

  2. Serverless 每周小报-20190610

    微软和 Red Hat 开源 KEDA:Fission 发布 1.3.0 博客精选 10 THINGS TO KNOW ABOUT SERVERLESS COMPUTING - BEFORE YOU ...

  3. 测试LFI WITH PHPINO过程中的一些记录

    原理:以往LFI漏洞都是需要满足两个条件:1.攻击者上传一个含PHP代码的的文件,后缀名任意,没有后缀名也可以:2.需要知道上传后的文件路径及文件名,然后包含之. 后来有国外研究者发现了新的攻击方式, ...

  4. api安全规范

    1. API签名的目的 校验API调用者的身份,是否有权访问    校验请求的数据完整性,防止被中间人篡改    防止重放攻击 2.基本概念 AccessKey: API使用者向API提供方申请的Ac ...

  5. word2vec的简单理解

    word2vec研究如何将词用向量表示,使用的两个重要模型--CBOW模型(Continuous Bag-of-Words Model)和Skip-gram模型(Continuous Skip-gra ...

  6. HDU 5894 hannnnah_j’s Biological Test【组合数学】

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5894 题意: 一个圆桌上有$n$个不同的位置,$m$个相同的人安排到这$n$个位置上,要求两人相邻的 ...

  7. Ubuntu 16.04 apt 国内源

    一.推荐几个 Ubuntu 16.04 国内的 apt 源 1. 阿里源 # deb cdrom:[Ubuntu 16.04 LTS _Xenial Xerus_ - Release amd64 (2 ...

  8. 图解MySQL索引(上)—MySQL有中“8种”索引?

    关于MySQL索引相关的内容,一直是一个让人头疼的问题,尤其是对于初学者来说.笔者曾在很长一段时间内深陷其中,无法分清"覆盖索引,辅助索引,唯一索引,Hash索引,B-Tree索引--&qu ...

  9. 【译文连载】 理解Istio服务网格(第六章 可观测性)

    全书目录 第一章 概述 第二章 安装 第三章 流控 第四章 服务弹性 第五章 混沌测试 ​本文目录 第6章 可观测性 6.1 分布式调用链跟踪(tracing) 6.1.1 基本概念 6.1.2 Ja ...

  10. 【OO第三次课下讨论】农场主的饲料分配问题

    需求分析与项目设计   本思考题的设计需求是力图找到一个简单且可行的饲料分配方案,由于不涉及到饲料价格或者是营养均衡之类的优化问题,因此在假设总的饲料量必能满足所有动物的热量需求的前提下,我们只需要采 ...