标签:线性时间 ems 状态 com 进制 code 限制 本质 整数
题目链接
我的掘金地址:https://juejin.im/post/5cb962dbf265da03761e8601
这题起初根本想不到.直到翻了leetcode.com的discuss.....然后我整个人都"awesome!!!!"地瞎叫了.
下面讲讲我学来的思路.
这题本质上来讲,就是让我们做一个有限状态机(finite state machine)(瞎翻的).
首先
: 一般看到这种题,我们第一反应是做状态记录,比如数字x出现了y次.然后从记录里查询出现了一次的数字.但是现在我们的空间复杂度被限制成了O(1),不可能对所有的64位二进制表示的整数进行统计次数.
a为高位,b为低位.两个一起表示一个两位二进制表示的数字(一个计数器)
计数器代表值 | a | b | 目标出现次数 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 0 | 1 | 1 |
2 | 1 | 0 | 2 |
0 | 0 | 0 | 3 |
1 | 0 | 1 | 4 |
2 | 1 | 0 | 5 |
... | ... | ... | ... |
上面是我们计数器需要达成的目标,一个计数循环.学过数字电路的同学看到这里应该就知道怎么做了.
我们再进一步. 我们遍历数据,每来一个数据c(这里一个二进制)我们的a与b就要开始为c计数,那么有以下情况
a | b | c | 结果a | 结果b |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
上面比较容易明白,来的是0,那么我们不统计,原来a,b的统计结果不变
a | b | c | 结果a | 结果b |
---|---|---|---|---|
0 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 0 |
这里也比较容易明白,当来的是1,那么只要为其计数增加1就好.
接下来就是我们的重头戏了.开始设计逻辑门能让我们的计数器保持3个状态记录循环.
我们观察结果a,可以看到会使结果a=1的状态仅有 ( a为1 && b为0 && c为0 ) || (a为0 && b为1 && c为1),同理可以推出b的表达式.
然后对实际int型数据做以上处理,就可完成我们的目标.(实际上可以把int看成是在维护一个二进制组成的数组a[i],b[i],c[i],i表示第i位.我们每次位运算操相当于对每一个i所在的三个元素进行操作,也就变成了上述单独描述一个二进制位时的情况.))
/**
* 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
* 题目要求:你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
*
* 思路有点像是数字电路设计一个计数器.此题的情况就是为每一位二进制进行计数,计数三次归零,那么最后没有归零的数就是出现一次的那个数.设计思路与逻辑门电路设计一样.
*
* @author jxwww
* @date 2018/8/30
*/
public class No137 {
/**
* 如上所推
*/
public static int singleNumber(int[] nums) {
int a = 0, b = 0;
for (int c : nums) {
int tempA = (~a & b & c) + (a & ~b & ~c);
b = (~a & ~b & c) + (~a & b & ~c);
a = tempA;
}
return b;
}
/**
* 如上所推,你也可以写成这样
*/
public int singleNumber(int[] nums) {
int a=0,b=0;
for (int c : nums) {
b = b ^ c & ~ a;
a = a ^ c & ~ b;
}
return b;
}
public static void main(String[] args) {
int[] nums = new int[]{0, 1, 0, 1, 0, 1, 99};
System.out.println(singleNumber(nums));
}
}
标签:线性时间 ems 状态 com 进制 code 限制 本质 整数
原文地址:https://www.cnblogs.com/jiangxiewei/p/12979996.html