进阶:空间复杂度
// discuss.leetcode.com/topic/22821/an-general-way-to-handle-all-this-sort-of-questions/1
| 上一个a(ta) | 上一个b(tb) | 现在的c | 现在的a | 现在的b |
| 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 |
| r | a | b |
| 0 | 0 | 0 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
public class Solution {
public int singleNumber(int[] A) {
int a = 0 , b = 0;
for(int c : A){
int ta,tb;
ta = a;
tb = b;
a = (ta&(~tb)&(~c))|((~ta)&tb&c);
b = ~ta&((~c&tb)|(~tb&c));
}
return a|b;
}
} //由于只有一个出现一次的数字,其余都是出现三次,那么有如下思路
// 针对于序列中出现三次的所有数字的每一位来说,相加的结果只有两种
//1+1+1==3 或者0+0+0=0
//那么再加上只出现一次的数字的对应位数字的话,也有两种0或者4
//所以我们可以对上述的每一位求和之后对3取余,结果将会是1或者0
//也就是代表了出现一次的的这个数字对应位置上是1或者0
public class Solution {
public int singleNumber(int[] A) {
if(A == null||A.length == 0){
return -1;
}
//得到出现一次的数字的值】
int result = 0;
//int为4个字节,那么一共有4*8=32位
for(int i = 0;i < 32;i++){
//保存每一位求和值
int sum = 0;
for(int j = 0;j < A.length;j++){
//累加所有数字上第i位上的数字
sum+=(A[j]>>i)&1;
}
//取余得到第i位上的数字,之后更新result
result |= (sum % 3) << i;
}
return result;
}
} 😁解法一复杂度(n) 解法较复杂 经典!!(内含详细推导,包括真值表,卡诺图)
😀解法二复杂度(32n) 解法较容易 时间复杂度会随数据类型不同增减
解法一解析: 将数据转换为二进制,对数组元素每一位的二进制的1或0累加起来必然是3N或者3N+1。
考虑使用模3加法: sum = (sum + c) % 3;
对数组元素循环应用模3加法后,最后的结果中,每一位的sum为0或1,该二进制数表示的即为只出现一次的数组元素。
sum可取得值为0、1、2,但是二进制数的每一位只能表示0和1,因此考虑使用两个二进制数(a, b)去表示sum中的高,低位。则0(00), 1(01),2(10)
最后结果串中,高位字符串全为0,低位字符串即为正确结果。
真值表:
| a (旧) | b(旧) | c | a(更新后) | b(更新后) |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 1 | 0 | 1 |
| 0 | 1 | 0 | 0 | 1 |
| 0 | 1 | 1 | 1 | 0 |
| 1 | 0 | 0 | 1 | 0 |
| 1 | 0 | 1 | 0 | 0 |
公式: (ab(旧) + c) % 3 = ab(新) // a是高位 b是低位
例:二进制: (10 + 1) % 3 = 00
十进制: (2 + 1) % 3 = 0
| c\ab(旧) | 00 | 01 | 10 |
| 0 | 0 | 0 | 1 |
| 1 | 0 | 1 | 0 |
卡诺图 b(新):
| c\ab(旧) | 00 | 01 | 10 |
| 0 | 0 | 1 | 0 |
| 1 | 1 | 0 | 0 |
逻辑表达式:
a(新) = (~c & a(旧) & ~b(旧)) | (c & ~a(旧) & b(旧))
b(新) = (~c & ~a(旧) & b(旧)) | (c & ~a(旧) & ~b(旧))
将数组A中每个元素应用上述表达式累加后,得到的结果即为:
高位 : 一个全为0的字符串 (逢3消0,只剩下出现1次的那个数)
低位 : 一个零一串,它表示的正是只出现1次的数的二进制表示
代码:
class Solution {
public:
int singleNumber(int A[], int n) {
if (n == 0) return 0;
int a = 0;
int b = 0;
int c;
int ta, tb;
for (int i = 0; i < n; i++) {
c = A[i];
ta = a;
tb = b;
a = (~c & ta & ~tb) | (c & ~ta & tb);
b = (~c & ~ta & tb) | (c & ~ta & ~tb);
}
return b;
}
};
解法二解析:
将数据转换为二进制,对数组元素每一位的二进制的1或0累加起来必然是3N或者3N+1。Int类型是4字节32位,循环32次,每次循环记录数组元素当前位的二进制累加和,然后将模三后的结果(0或1)放置在对应位,循环结束即可得到只出现一次的数的二进制表示。
代码:
class Solution {
public:
int singleNumber(int A[], int n) {
int sum;
int res = 0;
for (int i = 0; i < 32; i++) {
sum = 0;
for (int j = 0; j < n; j++) {
sum += (A[j] >> i) & 1;
}
res |= ((sum % 3) << i);
}
return res;
}
};
思路都差不多,但感觉这样改一下顺序会更加直观:
three完全不依赖前一个three,只依赖于前一个two,所以可以把three先确定下来;
实际上只是维护了one和two,three等同于zero,只是用作辅助,用于把one中的three的部分归零。
public class Solution {
public int singleNumber(int[] A) {
int one=0,two=0;
for(int a:A){
int three=two&a;
two=(two&~a)|(one&a);
one=one^a&(~three);
}
return one|two;
}
}
public class Solution {
public int singleNumber(int[] A) {
/**
* ones:出现1次的bits
* twos:出现2次的bits
* threes:出现3次的bits
* ones = threes & ~twos
* twos = threes & ~ones
*
* */
int ones = 0, twos=0;
for(int i = 0; i < A.length; i++) {
//xxxs ^ A[i] = threes
ones = (ones ^ A[i]) & ~twos;
twos = (twos ^ A[i]) & ~ones;
}
return ones;
}
}
参考了:https://leetcode.com/problems/single-number-ii/discuss/ | a | b | c | 结果a | 结果b |
| 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 |
public class Solution {
public int singleNumber(int[] A) {
int a = 0, b = 0;
for(int c:A){
int resultA = (a&~b&~c)|(~a&b&c);
int resultB = (~a&b&~c)|(~a&~b&c);
a = resultA;
b = resultB;
}
return b;
}
} class Solution {
public:
int singleNumber(int A[], int n) {
//int size = sizeof(A);
set<int> s1;
set<int> s2;
if(n<=0)
return 0;
for(int i=0;i<n;i++)
{
if(!s1.insert(A[i]).second)
s2.insert(A[i]);
}
for(int i=0;i<n;i++)
{
if(s2.insert(A[i]).second)
return A[i];
}
return 0;
}
}; class Solution {
public:
int singleNumber(int A[], int n) {
int first = 0;
int second = 0;
for(int i = 0; i < n ; i++){
int s = first & A[i] ; //得出第二次出现位
int t = second & s ; //得出第三次出现位
first |= A[i]; //first保留第一次出现位
second |= s; //second保留第二次出现位
first ^= t; //first 清空第三次出现位
second ^= t; //second清空第三次出现位
}
return first ;
}
};
将每个数字都看成是二进制数,first记录第一次出现的二进制位,second记录第二次出现的二进制位,当二进制位出现三次的时候清空first和second的对应记录位。
看了你们的都用了位运算,所以发下非位运算的
import java.util.*;
public class Solution {
/**
*
* @param A int整型一维数组
* @return int整型
*/
public int singleNumber (int[] A) {
// write code here
Arrays.sort(A);
for(int i=0;i<A.length-2;i+=3){
if(A[i]!=A[i+1]){
return A[i];
}
}
return A[A.length-1];
}
}
idea:
code
class Solution {
public:
int singleNumber(int A[], int n) {
if (n == 1) return A[0];
if (n < 4) return -1;
unsigned two=0, one=0, appr = 0;
for (int i = 0; i < n; i++){
appr = two & A[i]; //在two与A[i]同时出现的位要约掉
two = (one & A[i]) | (two ^ appr); //在one和A[i]同时出现的位要成为two,two中出现appr(约掉)中没出现的位也要成为two
one = one ^ A[i] ^ appr; //A[i]中出现appr中没出现的位成为one的候选者,将候选者与one异或即得迭代后的one。
}
return one;
}
}; class Solution {
public:
/*
思路:参考来源:https://leetcode.com/problems/single-number-ii/discuss/43296/an-general-way-to-handle-all-this-sort-of-questions
这是一个通用的思路,对于类似的一个数组里除了一个数之外其他的数都重复了N次这样的问题,这样的思想可以通吃。
设置a和b来记录bit操作,a是高位,b是低位,来数为c,对于每一位有:
a b c comingA comingB
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
注:这里因为是记录三进制的数,因此,不考虑a=1,b=1的情况
由上图可知以下逻辑表达式:
comingA=~a&b&c+a&~b+&~c;
comingB=~a&b&~c+~a&~b&c;
所以可以得到相应的C语言表达:
comingA=(~a&b&c)|(a&~b&~c);
comingB=(~a&b&~c)|(~a&~b&c);
这样对于结果来说,每一位:
a b res
0 1 1
1 0 2
0 0 3
由于题目中确定只有res=1和3的情况,因此,结果集每一位的表达简化为:
a b res
0 1 1
0 0 0
即res=a|b;
以上。
*/
int singleNumber(int A[], int n) {
int a=0,b=0;
for(int i=0;i<n;i++){
int c=A[i];
int tempA=(a&~b&~c)|(~a&b&c);
b=(~a&b&~c)|(~a&~b&c);
a=tempA;
}
return a|b;
}
};
public class Solution {
public static void main(String[] args) {
int a[] = {2,2,3,2};
System.out.println(singleNumber(a));
}
public static int singleNumber(int[] A) {
if (A == null || A.length == 0) {
return new Integer(null);
}
int bitSum[] = new int[32];
for (int i = 0; i < A.length; i++) {
int bitMask = 1;
for (int j = 0; j < 32; j++) {
int bit = A[i] & bitMask;
if(bit != 0)
bitSum[j] += 1;
bitMask = bitMask << 1;
}
}
int result = 0;
int bitMask = 1;
for (int i = 0; i < 32; i++) {
int bit = bitSum[i] % 3;
result += bitMask * bit;
bitMask = bitMask << 1;
}
return result;
}
}
import java.util.HashMap;
public class Solution {
public int singleNumber(int[] A) {
HashMap<Integer,Integer> map=new HashMap<Integer,Integer>();
for(int i=0;i<A.length;i++){
if(map.containsKey(A[i])){
map.put(A[i],map.get(A[i])+1);
}else{
map.put(A[i],1);
}
}
for(int key:map.keySet()){
if(map.get(key)!=3){
return key;
}
}
return 0;
}
}
思路:参考了菜鸟葫芦娃,学工程真是太好了,两位老哥的思路。
single numer本质就是用一个数记录每个bit出现的次数,当数的某一位出现了指定次数就归0(本题是3次)。
假设当出现2次就归0时,很容易想到可以使用异或的方法。
然而,当出现2次以上时,我们就需要增加记录的数,因为一个数每个bit,只有0,1两种可能,只能记录两种。
此处我们就需要使用两个数分别secondbit,firstbit来进行记录,可以表示4种可能,本题只需要3种00,01,10。
然后,使用卡诺图来得到secondbit,firstbit的表达式,分别简化secondbit,firstbit为符号f,s则有:
f卡诺图:
| sf\A[i] | 0 | 1 |
|---|---|---|
| 00 | 0 | 1 |
| 01 | 1 | 0 |
| 10 | 0 | 0 |
因此有: f = (~s&~f&A[i])|(~s&f&~A[i])
s卡诺图:
| sf\A[i] | 0 | 1 |
|---|---|---|
| 00 | 0 | 0 |
| 01 | 0 | 1 |
| 10 | 1 | 0 |
因此有: s = (~s&f&A[i])|(s&~f&~A[i])
此外当except one 可能出现1次,也可能出现2次。最后的 s f 可能为 00,01,10 结果就是 a|b,有1出现则此位置为1否则为0
int singleNumber(int A[], int n) {
int firstbit = 0,secondbit = 0;
int one = 0;
for(int i = 0;i < n; i++){
one = firstbit;
firstbit = (~secondbit&~firstbit&A[i])|(~secondbit&firstbit&~A[i]);
secondbit = (~secondbit&one&A[i])|(secondbit&~one&~A[i]);
}
return firstbit|secondbit;
}
//one代表只出现了一次的位,two代表出现了两次的位,注意one置一的位two一定不为1,
//反过来一样,然后当出现三次时清零。class Solution {
public:
int singleNumber(int A[], int n) {
int one=0,two=0;
for(int i=0;i<n;i++){
int tmp=two&A[i];
two^=tmp;//这两行将之前已经出现两次的位
A[i]^=tmp;//清零
tmp=one&A[i];
one^=tmp;//这两行将已经出现了一次的位
A[i]^=tmp;//清零
two^=tmp;
one^=A[i];
}
return one;
}
};
class Solution {
public:
int singleNumber(int A[], int n)
{
if(A==nullptr || n<=0)
return 0;
vector<int> res(32,0);
for(int i=0;i<n;i++)
{
int bitMask = 1;
for(int j=31;j>=0;j--)
{
int bit = A[i]&bitMask;
if(bit!=0)
res[j] += 1;
bitMask <<= 1;
}
}
int result = 0;
for(int i=0;i<32;i++)
{
result = result<<1;
result += res[i]%3;
}
return result;
}
};
class Solution {
public:
int singleNumber(int A[], int n) {
int bitnum[32]={0};
int res=0;
for(int i=0;i<32;i++){
for(int j=0;j<n;j++){
bitnum[i]+=(A[j]>>i)&1;
}
res|=(bitnum[i]%3)<<i;
}
return res;
}
};
int 整型共有32位,用bitnum[32]存储这n个数据每个二进制位上1出现的个数,再%3,如果为1,那说明这一位是要找元素二进制表示中为
1 的那一位。
public int singleNumber(int[] A) {//discuss.leetcode.com/topic/22821/an-general-way-to-handle-all-this-sort-of-questions/1//这里面有详解,这里主要说一下思想//因为出现的是三次,所以需要设计成一个数出现3次之后自动会变为0,这就代表不影响后面的数//现在只用bit来表示,a,b可能是0,1的组合,c(next bit)可能是0或者1//采用两位a,b来表示. a ,b 的值可能为0 0 ,0 1 ,10//分别代表0次,1次,2次。不可能出现1 1。因为3次之后就清0了//当对应不同的数字时,无非就是把一位的情况扩展到了32位。但逻辑运算(对于每一位的仍然一样)int a = 0;int b = 0;int ta = 0;for(int c : A){ta = (~a & b & c) | (a & ~b & ~c); //这里之所以需要使用ta,是因为计算b的时候需要a的旧值b = (~a & ~b & c) | (~a & b & ~c);a = ta;}//we need find the number that is 01,10 => 1, 00 => 0.//return a|b 意思是返回出现一次或者出现两次的那个数(假设不知道一个数是出现了一次还是两次)//不管只有一个数出现了一次还是两次,另一个数一定为0;相或即可return a | b;}