前言:

本题解整理了一位大佬在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. idea如何使用git

    1.安装好git(我下载的2.23.0版本百度网盘分享)  提取码  7ie1 2.配置git环境变量  Path   路径是你安装的git 目录下的bin目录   安装好后窗口命令输入git 可以测 ...

  2. 任务框架--Quartz 配置文件

    配置文件 Quartz 有一个叫做quartz.properties的配置文件,它允许你修改框架运行时环境.缺省是使用 Quartz.jar 里面的quartz.properties 文件.你应该创建 ...

  3. iOS自建分发

    1.首先满足具有https证书的域名和空间.2.通常使用github或者国内第三方托管平台.3.上传ipa文件到空间内,获取ipa文件的下载地址.4.然后编辑plist文件(注意:ipa文件和plis ...

  4. 4K手机能拯救索尼手机吗?

    智能手机屏幕分辨率究竟达到多少才是极限,一直是业内争论不休的问题.从低分辨率一路走来,直到iPhone 4搭载视网膜屏,业内才有了一个较为统一的认知:屏幕起码要在合适距离下看不到文字.图像虚影,才称得 ...

  5. H5新增特性之语义化标签

    H5新增特性之语义化标签 语义化标签顾名思义标签有自己的含义,浏览器或者程序员一看就知道是什么.在HTML 5出来之前,我们用div来表示页面章节,但是这些div都没有实际意义.(即使我们用css样式 ...

  6. IDEA打包web项目为war,通过本地Tomcat启动war

    1.打包 ①idea的打包很简单,网上教程也很多,简单说下:project struct-->artifact-->+-->Web Application:Archive--> ...

  7. CF 1305E. Kuroni and the Score Distribution

    题目大意:题目给定两个数n和m(1<=n<=5000,0<=m<=1e9)要求构造一个数列A,A中元素 大于等于1,小于等于1e9且满足严格递增 满足ai+aj=ak的(i,j ...

  8. 浅析TCP/IP协议

    浅析TCP/IP协议 0x00 什么是TCP/IP协议? ​ 想一想人与人之间交流需要什么?我们是不是要掌握一种我们都能体会到对方意思的语言.那么计算机与网络设备之间进行通信,是不是不同设备之间是不是 ...

  9. 关于响应式web设计

    手机网站+电脑网站+平版网站 = 响应式网站 在没有足够经费跟精力的做一个手机网站的情况下,响应式网站是个不错的选择.它有以下的优点: 减少工作量(网站代码只要一份,只需要做js方面的改动及可以了) ...

  10. 前端每日实战:1# 视频演示如何用纯 CSS 创作一个按钮文字滑动特效

    效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/zhang-ou/pen/GdpPLE 可交互视频教程 此视频 ...