前言:

本题解整理了一位大佬在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. Docker系列之实战:3.安装MariaDB

    环境 [root@centos181001 ~]# cat /etc/centos-release CentOS Linux release 7.6.1810 (Core) [root@centos1 ...

  2. 单点登录CAS系列第06节之客户端配置单点登录

    原理 纯web.xml 借助Spring 注意 代码 测试 原理 这里用的是:cas-client-core-3.4.0.jar(2015-07-21发布的) 下载地址为:http://mvnrepo ...

  3. Python-控制语句及函数

    if-elif-else for while 函数 函数定义 空函数 pass 返回多个值 可变参数 * 关键字参数 ** 控制语句 if - elif - else 比如,输入用户年龄,根据年龄打印 ...

  4. 5G时代,什么将会消失?

    ​​5G时代说着说着就来了,当然,它不可能一撮而就,但正如4G.移动互联网和WIFI这些东西基本上是日益精进的水平,现如今饭馆的生意是否火爆,不仅仅在于其菜品和服务的质量,更在于他们有没有WIFI以及 ...

  5. 百度地图API:使用百度定位

    准备工作: 1.申请百度地图API 2.下载百度地图的SDK 3.将SDK包中的BaiduLBS_Android.jar文件放到,项目里的app/libs里面 4.在src/main目录下创建一个名为 ...

  6. Go语言基础篇(1) —— 编写第一个Go程序

    创建文件hello_world.go package main //包,表名代码所在的包 import "fmt" //引入依赖 //main方法 func main(){ fmt ...

  7. sql01

    1.简介 2.外键 1)主键: 3.表间关联与外键 上面的两个表格通过厂家编号联系在一起,彼此相对独立.厂家编号就称为外键.厂家编号是厂家表的主键. 4.SQL 1)数据库登录 服务器名称选择:①机器 ...

  8. criteria.setCacheable(true);这个方法是干什么用的

    criteria.setCacheable(true); 一下是Criteria的底层源代码 /** * Enable caching of this query result, provided q ...

  9. Java基础--数组的定义

    1.数组的定义 数组:一组能够储存相同数据类型值的变量的集合. 2.数组的赋值方式 (1)使用默认的初始值来初始化数组中的每一个元素 语法:数组元素类型[]数组名 = new数组元素类型[数组中元素的 ...

  10. RestTemplate 负载均衡原理

    RestTemplate 是通过拦截器改变请求的URI的方式来指定服务器的,此处将通过一个自定义LoadBalanced的方式来进行说明 1.导入jar包 <parent> <gro ...