算法入门系列:singleNumber

本文属于算法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) operation popis 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:

  1. The order of the result is not important. So in the above example, [5, 3]is also correct.
  2. 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).

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值