转自我的搜狐博客:http://jluhlh.blog.sohu.com/302184333.html
leetcode上的题: 所有题都要求线性时间复杂度(最好O(n)),及尽量少的线性空间复杂度(最好O(1))
原题Single Number1: 一个整数数组中有n个元素,其中只有一个只出现一次,其他恰好都出现两次,求出现一次的那个元素; 这个很简单, 用所有元素异或操作,最后的值就是所要的只出现一次的元素。 int singleNumber(int A[], int n) {
for(int i=1;i<n;i++)
A[0]=A[0]^A[i];
return A[0];
}
原题Single Number 2: 一个整数数组中有n个元素,其中只有一个只出现一次,其他恰好都出现三次,求出现一次的那个元素;
出现三次,异或就没意义了,左思右想没有好的答案。(当然,可以先排序,在遍历,时间复杂度O(nlogn))
看discuss,第一种方法:比特位的方法,假设整数最大32位,那么所有元素中,对于第j位为1的个数要不是3的倍数(包括0),要不就是3的倍数+1,"+1"就是那个出现一次的元素贡献的,所以,对32位,求出所有元素在这位%3后为1的个数。时间复杂度 k*n,k为整数位数。
K元素的数组X[],x[j]表示比特位出现j次,对数组中每个元素a计算int singleNumber(int A[], int n) { int count[32] = {0}; int result = 0; for (int i = 0; i < 32; i++) { for (int j = 0; j < n; j++) if ((A[j] >> i) & 1) count[i]++; result |= ((count[i] % 3) << i); } return result; }
有评论的人提出可以不用count数组,改用一个变量代替,降低空间复杂度。可行! 第二种方法,一种没看懂,说是用三个掩码变量分别表示第j位出现的次数? 掩码是什么? 突然想不起来, 百度后,“掩码是一串二进制代码对目标字段进行位与运算,屏蔽当前的输入位。” 例如IP掩码255.255.255.0。int singleNumber(int A[], int n) { int ones = 0, twos = 0, threes = 0; for(int i = 0; i < n; i++) { threes = twos & A[i]; //已经出现两次并且再次出现 twos = twos | ones & A[i]; //曾经出现两次的或者曾经出现一次但是再次出现的 ones = ones | A[i]; //出现一次的 twos = twos & ~threes; //当某一位出现三次后,我们就从出现两次中消除该位 ones = ones & ~threes; //当某一位出现三次后,我们就从出现一次中消除该位 } return ones; //twos, threes最终都为0.ones是只出现一次的数 } 详细解释 另一个人又提出另一个易于理解也易于扩展的版本:(java)
public class Solution { public int singleNumber(int[] A) { if (A == null) return 0; int x0 = ~0, x1 = 0, x2 = 0, t; for (int i = 0; i < A.length; i++) { t = x2; x2 = (x1 & A[i]) | (x2 & ~A[i]); x1 = (x0 & A[i]) | (x1 & ~A[i]); x0 = (t & A[i]) | (x0 & ~A[i]); } return x1; } } 并由此推导出更一般化的变形问题:除了一个元素出现L次,其他元素都出现K次,找出出现L次的那个元素,
x[j] = (x[j-1] & a) | (x[j] & ~a)
; 但
x[0] = (x[k] & a) | (x[0] & ~a)。
“
In the equation, the first part indicates the the carries from previous one.The second part indicates the bits not carried to next one.” ???没看明白
public class Solution {
public int singleNumber(int[] A, int k, int l) {
if (A == null) return 0;
int t;
int[] x = new int[k];
x[0] = ~0;
for (int i = 0; i < A.length; i++) {
t = x[k-1];
for (int j = k-1; j > 0; j--) {
x[j] = (x[j-1] & A[i]) | (x[j] & ~A[i]);
}
x[0] = (t & A[i]) | (x[0] & ~A[i]);
}
return x[l];
}
}
第二种方法的其他文章: 文章1, 文章2
综合思考:
如果K为偶数,L为奇数,那么用异或就可很快解决,但如果K为奇数,那就只能用第二题的方法了!