leetcode 之 Single Number II
问题来源:Single Number II
问题描述:给定一个整数数组,除了一个整数出现一次之外,其余的每一个整数均出现三次,请找出这个出现一次的整数。
大家可能很熟悉另一个题目(Single Number):除了一个数出现一次之外,其余的均出现两次,找到出现一次的数。该问题很简单,大家肯定都知道解法:将所有的数异或,最后的结果即是出现一次的数。用到的知识是A^A=0,两个相同的数异或变为0,然后0^B=B,这样即可找到出现一次的数。
新问题有了变化,出现两次变成出现三次,整个问题的解法就不一样了。如何做到时间复杂度O(n),空间复杂度O(1)保持不变呢?最笨的方法就是计数,将每一个整数都看成一个长度位32的数组,然后统计32位中每一位出现1的次数。如果一个数出现3次,则其出现1的位肯定也是3次,这时如果某位出现4次,则意味着出现一次的数在该位也为1。通过分析所有的位,我们即可以找到这个出现一次的数。代码如下:
int singleNumberII(int* A,int len)
{
int count[32],result=0;
memset(count,0,sizeof(int)*32); for (int i=0;i<len;i++)
{
for (int j=0;j<32;j++)
{
count[j]+=A[i]>>j&0x1;
}
} for (int i=0;i<32;i++)
{
result|=(count[i]%3<<i);
} return result;
}
很多人对上面的代码会有一个疑问:上面的代码分配了一个长度为32的数组,这样空间复杂度还算是O(1)吗?答案是肯定的,只要分配的空间是已知的固定值,空间复杂度都是O(1)。另一个例子,统计每个char字符出现的次数分配的长度为128的空间也属于O(1)。
上面的方法可以解决问题,但是显得不够优雅,是否存在一个和原始问题一样优雅的解法呢?答案是肯定的,但是理解起来会比较困难。今天我们就深入剖析一下这种优雅的解法,等你掌握之后就可以很容易地解决一系列的问题。
新解法用到了大学阶段大家都学过的数字逻辑电路知识,莫慌,用到的知识非常浅显,很容易就回忆起来。第一个概念真值表(truth table),是用0和1表示输入和输出之间全部关系的表格,异或的真值表如下:
| A | B | P |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
其中,A和B是输入,共有四种组合,P是输出。给定一个真值表,我们还需要将其逻辑函数表达式写出来。共有两种方法,最小项推导法和最大项推导法。第二个概念最小项推导法(两个概念都很简单,我们只关注最小项),把输出为1的输入组合写成乘积项的形式,其中取值为1 的输入用原变量表示,取值为0的输入用反变量表示,然后把这些乘积项加起来。例如,上面异或真值表的逻辑函数表达式可以写为:
是不是很简单。有了这两个概念,我们就可以介绍新解法了。
一个32位int型整数可以看成32个独立的位,每一位都可以独立考虑,所以后面的描述都单指一个位。当一个数最多出现两次时,我们可以只用1 bit来描述,但是当一个数最多出现三次时,我们必须要用2 bit来描述。针对该问题,可以用00表示一个数未出现,01表示一个数出现一次,10表示一个数出现两次,当出现三次的时候按理应该是11,但是我们将其重置为00表示该数已经达到上限,肯定不是要找的数可以丢掉。所以给定一个数,其出现次数变化规律为00→01→10→00。针对这个变化规律,我们可以得到一个真值表:
| high_bit | low_bit | input | high_bit_output | low_bit_output |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 | 1 |
| 1 | 0 | 0 | 1 | 0 |
| 0 | 0 | 1 | 0 | 1 |
| 0 | 1 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 | 0 |
其中,high_bit表示计数过程中的高位,low_bit是对应的低位,input表示下一个输入,high_bit_output是高位对应的输出,low_bit_output是低位对应的输出。前三行对应输入为0的情况,此时输出不变化;后三行对应输入为1的情况,需要注意的就是最后一行,10加1变成00。输入和输出为11的情况不会出现,未列出。
有了这个真值表,我们就可以针对高低两位利用最小项分别写出逻辑表达式:
最终的结果中low就表示出现一次的整数,因为0次、2次、3次对应的low值都是0。从这里也解释了为什么需要将11重置为00,否则就会出现两种情况low值为1。此外,这里还需要注意一点,两个公式中的输入都是利用旧值计算新值,所以当我们在计算出low之后,不能用该low值计算high值,需要用旧的low值计算high值。新的代码如下:
int singleNumberII(int* A,int len)
{
int low = 0, high = 0;
for(int i = 0; i < len; i++){
int temp_low = (low ^ A[i]) & ~high;
high =(high&~low&~A[i])|(~high&low&A[i]);
low=temp_low;
}
return low;
}
上述代码较最原始的代码优化很多,空间复杂度和时间复杂度都有明显下降,但是利用了一个局部变量,显得非常不美观。这个代码很像交换两个数时的代码,为了交换两个数,常规方法是引入一个局部变量,然后三次赋值操作。为了避免引入局部变量,一种优化是通过三次直接的位运算实现。在此我们也对上面的代码进行类似的优化,将局部变量删除。该怎么优化呢?我要放大招了!
low的计算保持不变,当我们计算完low之后,high的计算公式依赖的是旧的low值,我们设法将依赖旧low值改为依赖新low值。修改方法就是修改真值表,将low的输出重新作为输入,构造新的真值表:
| high_bit | low_bit | input | high_bit_output |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 0 |
| 1 | 0 | 0 | 1 |
| 0 | 1 | 1 | 0 |
| 0 | 0 | 1 | 1 |
| 1 | 0 | 1 | 0 |
上述真值表唯一的修改就是把之前真值表最后一列的输出拷贝到第二列中。然后我们针对修改后的真值表求逻辑表达式:
看到没,直接利用low的输出计算high会简化公式,同时也无需引入局部变量,最终代码如下:
int singleNumberII(int* A,int len)
{
int low = 0, high = 0;
for(int i = 0; i < len; i++){
low = (low ^ A[i]) & ~high;
high = (high ^ A[i]) & ~low;
}
return low;
}
上面的代码是不是非常简洁和优雅!背后其实有非常坚实的理论基础。采用真值表的方法不光优雅,还具有非常好的扩展性。假设问题改为:只有一个数出现两次,其余出现三次,我们只需要返回high即可。又假如:有一个数出现一次或者两次,其余出现三次,我们只需要返回low|high即可。此外,不只是出现三次,出现五次、七次也可以用构造真值表的方法来解决,只需要增加输入位数即可。即使是最原始的问题,我们也可以用这种方法解决,只需要构造low和input的真值表即可,你会发现构造的真值表正好就是异或的真值表!
leetcode 之 Single Number II的更多相关文章
- LeetCode 137. Single Number II(只出现一次的数字 II)
LeetCode 137. Single Number II(只出现一次的数字 II)
- Leetcode 137 Single Number II 仅出现一次的数字
原题地址https://leetcode.com/problems/single-number-ii/ 题目描述Given an array of integers, every element ap ...
- 【题解】【位操作】【Leetcode】Single Number II
Given an array of integers, every element appears three times except for one. Find that single one. ...
- LeetCode 137 Single Number II(仅仅出现一次的数字 II)(*)
翻译 给定一个整型数组,除了某个元素外其余的均出现了三次. 找出这个元素. 备注: 你的算法应该是线性时间复杂度. 你能够不用额外的空间来实现它吗? 原文 Given an array of inte ...
- [LeetCode] 137. Single Number II 单独数 II
Given a non-empty array of integers, every element appears three times except for one, which appears ...
- [LeetCode] 137. Single Number II 单独的数字之二
Given a non-empty array of integers, every element appears three times except for one, which appears ...
- 【Leetcode】 - Single Number II
Problem Discription: Suppose the array A has n items in which all of the numbers apear 3 times excep ...
- 详解LeetCode 137. Single Number II
Given an array of integers, every element appears three times except for one, which appears exactly ...
- 【leetcode】Single Number II (medium) ★ 自己没做出来....
Given an array of integers, every element appears three times except for one. Find that single one. ...
随机推荐
- 原来你是这样的Promise
1. Promise简介 promise是异步编程的一种解决方案,它出现的初衷是为了解决回调地狱的问题. 打个比方,我需要: --(延迟1s)--> 输出1 --(延迟2s)--> 输出2 ...
- spring boot / cloud (四) 自定义线程池以及异步处理@Async
spring boot / cloud (四) 自定义线程池以及异步处理@Async 前言 什么是线程池? 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务.线 ...
- Leetcode 804. Unique Morse Code Words 莫尔斯电码重复问题
参考:https://blog.csdn.net/yuweiming70/article/details/79684433 题目描述: International Morse Code defines ...
- [LeetCode] License Key Formatting 注册码格式化
Now you are given a string S, which represents a software license key which we would like to format. ...
- [Codeforces 100633J]Ceizenpok’s formula
Description 题库链接 求 \[C_n^m \mod p\] \(1\leq m\leq n\leq 10^{18},2\leq p\leq 1000000\) Solution 一般的 \ ...
- [NOIp 2014]解方程
Description 已知多项式方程: a0+a1x+a2x^2+..+anx^n=0 求这个方程在[1, m ] 内的整数解(n 和m 均为正整数) Input 输入文件名为equation .i ...
- [Ahoi2005]LANE 航线规划
题目描述 对Samuel星球的探险已经取得了非常巨大的成就,于是科学家们将目光投向了Samuel星球所在的星系——一个巨大的由千百万星球构成的Samuel星系. 星际空间站的Samuel II巨型计算 ...
- TopCoder SRM 566 Div 1 - Problem 1000 FencingPenguins
传送门:https://284914869.github.io/AEoj/566.html 题目简述: 平面上有中心在原点,一个点在(r,0)处的正n边形的n个顶点.平面上还有m个企鹅,每个企鹅有一个 ...
- python中str常用操作
1. 字符串的操作 字符串的连接操作 符号: + 格式:str1 + str2 例如:str1 = 'I Love' str2 = 'You!' print(str1 + str2) >> ...
- C语言程序设计第四次作业——选择结构(二)
(一)改错题 错误信息: 错误原因:第13行sqrt数学函数缺少")",导致编译器无法将括号正确配对 改正方法:补齐缺少的")" 错误信息: 错误原因:if语句 ...