本文属于算法leetcode系列。
singleNumber1: easy 模式。
Given a non-empty array of integers, every element appears twice except for one. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
Example 1:
Input: [2,2,1]
Output: 1
Example 2:
Input: [4,1,2,1,2]
Output: 4
60%的通过率。
有通常的双重for循环处理的。还可以用hashmap处理。
class Solution {
public int singleNumber(int[] nums) {
Map<Integer,Integer> map = new HashMap();
for(int i=0;i<nums.length;i++){
if(map.containsKey(nums[i])){
map.remove(nums[i]);
}else{
map.put(nums[i], nums[i]);
}
}
Integer tmp = map.values().iterator().next();
return tmp;
}
}
Runtime: 7 ms, faster than 34.35% of Java online submissions for Single Number.
Memory Usage: 39.7 MB, less than 96.60% of Java online submissions forSingle Number.
Complexity Analysis
-
Time complexity : O(n \cdot 1) = O(n)O(n⋅1)=O(n). Time complexity of
for
loop is O(n)O(n). Time complexity of hash table(dictionary in python) operationpop
is O(1)O(1). -
Space complexity : O(n)O(n). The space required by hash\_tablehash_table is equal to the number of elements in \text{nums}nums.
我想到就这样,看到有个复杂度类似的。
上面表了是方法3 math
用set保存数字,2∗(a+b+c)−(a+a+b+b+c)=c,
看看方法4 bit 操作吧。也就是最优解:
class Solution {
public int singleNumber(int[] nums) {
int num =0;
for(int i=0;i<nums.length;i++){
num ^= nums[i];
}
return num;
}
}
Runtime: 0 ms, faster than 100.00% of Java online submissions for Single Number.
Memory Usage: 38.2 MB, less than 99.68% of Java online submissions forSingle Number.
用到了异或 XOR的原理:x^x =0,x^0=x.
用一个数记录每个bit出现的次数,如果一个bit出现两次就归0.跟前面的hashmap去删除原理一样。
这里就是使用^ 操作。
Complexity Analysis
-
Time complexity : O(n)O(n). We only iterate through \text{nums}nums, so the time complexity is the number of elements in \text{nums}nums.
-
Space complexity : O(1)O(1).
singleNumber II: Medium模式
Given a non-empty array of integers, every element appears three times except for one, which appears exactly once. Find that single one.
Note:
Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
Example 1:
Input: [2,2,3,2]
Output: 3
Example 2:
Input: [0,1,0,1,0,1,99]
Output: 99
从2个重复,变成三个重复。本质上还是要计数。
先看下稍微改动的hahsmap版本。
class Solution {
public int singleNumber(int[] nums) {
Map<Integer,Integer> map = new HashMap();
for(int i=0;i<nums.length;i++){
if(map.containsKey(nums[i])){
int tmp = map.get(nums[i]);
if(tmp ==2){
map.remove(nums[i]);
}else{
map.put(nums[i], 2);
}
}else{
map.put(nums[i], 1);
}
}
Integer res = map.keySet().iterator().next();
return res;
}
}
Runtime: 5 ms, faster than 28.01% of Java online submissions for Single Number II.
Memory Usage: 36.8 MB, less than 99.28% of Java online submissions forSingle Number II.
这种速度就是太慢了。
怎么用上原来的bit操作呢?上题 Single Number 用到了二进制中异或的运算特性,这题给出的元素数目为3*n + 1
,因此我们很自然地想到如果有种运算能满足3进制运算就好了。当然有大神用1位跟2位的分别记录来模拟3进制。
以下是原地址:https://www.nowcoder.com/questionTerminal/1097ca585245418ea2efd0e8b4d9eb7a
真看不懂。贴一下瞻仰下。
链接:https://www.nowcoder.com/questionTerminal/1097ca585245418ea2efd0e8b4d9eb7a
来源:牛客网
public int singleNumber(int[] A) {
int ones = 0;//记录只出现过1次的bits
int twos = 0;//记录只出现过2次的bits
int threes;
for(int i = 0; i < A.length; i++){
int t = A[i];
twos |= ones&t;//要在更新ones前面更新twos
ones ^= t;
threes = ones&twos;//ones和twos中都为1即出现了3次
ones &= ~threes;//抹去出现了3次的bits
twos &= ~threes;
}
return ones;
}
相比而言:找规律的更容易理解些:
- 对于三个相同的数来说,其相加的和必然是3的倍数。
- 相加结果二进制位上的每一位也能被3整除(二进制数每一位相加必为3或0,不考虑进位)
- 因此我们只需要一个和
int
类型相同大小的数组记录每一位累加的结果即可(若为3的倍数,所求数的该二进制位对3取余为0,否则为1。当结果为1的时候,也就是这个位上出现了只出现一次的数字)
再看代码:
public static int singleNumber(int[] nums) {
int result=0;
for(int i=0;i<32;++i){
int bits=0;
for(int j=0;j<nums.length;++j){
System.out.println("j="+j+"nums[j]="+nums[j]+":binary:"+Integer.toBinaryString(nums[j]));
int tmp = (nums[j]>>i)&1;
System.out.println("num["+j+"]="+nums[j]+":i="+i+":tmp="+tmp);
bits+= tmp;//依次获取元素的每一位,并将数组元素相同位相加
}
//取余得到第i位上的数字,更新result
result|=(bits%3)<<i;
System.out.println("result="+result+":i="+i);
}
return result;
}
核心代码就是相同位数求和,用到了右移及截取1位的操作。
bits+= (nums[j]>>i)&1;
后面每位的结果都按照之前分析的对3取余,判断是否本位有出现一次的数字,再吧这结果用或操作恢复成对应的数字。
result|=(bits%3)<<i;
为了方便理解,我打印了中间变量:这里以[2,2,3,2]举例
j=0nums[j]=2:binary:10
num[0]=2:i=0:tmp=0
j=1nums[j]=2:binary:10
num[1]=2:i=0:tmp=0
j=2nums[j]=3:binary:11
num[2]=3:i=0:tmp=1
j=3nums[j]=2:binary:10
num[3]=2:i=0:tmp=0
result=1:i=0
j=0nums[j]=2:binary:10
num[0]=2:i=1:tmp=1
j=1nums[j]=2:binary:10
num[1]=2:i=1:tmp=1
j=2nums[j]=3:binary:11
num[2]=3:i=1:tmp=1
j=3nums[j]=2:binary:10
num[3]=2:i=1:tmp=1
result=3:i=1
j=0nums[j]=2:binary:10
num[0]=2:i=2:tmp=0
j=1nums[j]=2:binary:10
num[1]=2:i=2:tmp=0
j=2nums[j]=3:binary:11
num[2]=3:i=2:tmp=0
j=3nums[j]=2:binary:10
num[3]=2:i=2:tmp=0
result=3:i=2
j=0nums[j]=2:binary:10
num[0]=2:i=3:tmp=0
j=1nums[j]=2:binary:10
num[1]=2:i=3:tmp=0
j=2nums[j]=3:binary:11
num[2]=3:i=3:tmp=0
j=3nums[j]=2:binary:10
num[3]=2:i=3:tmp=0
result=3:i=3
。。。
j=0nums[j]=2:binary:10
num[0]=2:i=31:tmp=0
j=1nums[j]=2:binary:10
num[1]=2:i=31:tmp=0
j=2nums[j]=3:binary:11
num[2]=3:i=31:tmp=0
j=3nums[j]=2:binary:10
num[3]=2:i=31:tmp=0
result=3:i=31
3
再看结果:
Runtime: 1 ms, faster than 72.94% of Java online submissions for Single Number II.
Memory Usage: 37.1 MB, less than 99.17% of Java online submissions forSingle Number II.
比原来快多了,当然还不是最优解。
真的,如果对于位操作不熟悉,看代码是很比较晦涩的,得自己先画出一串二进制先翻译下。对于第二种方式,虽然我在上一题的基础上想到位操作,还是写不出来。弱爆了。
3 SinglenumberIII Medium 模式
Given an array of numbers nums
, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.
Example:
Input:[1,2,1,3,2,5]
Output:[3,5]
Note:
- The order of the result is not important. So in the above example,
[5, 3]
is also correct. - Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity?
Accepted 111,425 Submissions 193,252
之前那种hashmap模式不再重复了。
Runtime: 4 ms, faster than 31.92% of Java online submissions for Single Number III.
Memory Usage: 38.3 MB, less than 98.90% of Java online submissions forSingle Number III.
再看位操作。
这跟number1 不一样,异或完结果剩余两个数,假设是x1,x2,即 x1^x2.
可是咋区分啊?没招了。真实感受到能力有限。
看看大神的办法吧。具体方法则是利用了x1 ^
x2不为0的特性,如果 x1^x2不为0,那么 x1^x2的结果必然存在某一二进制位不为0(即为1),我们将最低位的1提取出来.因为是异或的结果为1,x1 与x2必然在此位不相等。我们以此分组,又因为其他的数据成对出现,必然会抵消掉。这样就分别剩下x1,x2.
class Solution {
public int[] singleNumber(int[] nums) {
int[] result=new int[2];
int num =0;
for(int i=0;i<nums.length;i++){
num ^= nums[i];
}
int index =0;
for(int i=0;i<32;i++){
int tmp = (num>>i)&1;
if(tmp==1){
index =i;
break;
}
}
for(int j=0;j<nums.length;j++){
if((nums[j]&(1<<index))==0 ){
result[0] ^= nums[j];
}else{
result[1] ^= nums[j];
}
}
return result;
}
}
Runtime: 1 ms, faster than 99.74% of Java online submissions for Single Number III.
Memory Usage: 38.2 MB, less than 98.90% of Java online submissions forSingle Number III.
两次遍历数组,时间复杂度 O(n)+O(n).