仅有一个数出现了一次,其余出现三次 Single Number II

问题:

Given an 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?

解决:

① 使用map解决。时间复杂度O(n),空间复杂度O(n)。

class Solution { //13ms
    public int singleNumber(int[] nums) {
        int res = -1;
        Map<Integer,Integer> map = new HashMap<>();
        for (int n : nums) {
            map.put(n,map.getOrDefault(n,0) + 1);
        }
        for (Map.Entry<Integer,Integer> entry : map.entrySet()) {
            if(entry.getValue() != 3){
                res = entry.getKey();
                break;
            }
        }
        return res;
    }
}

② 对数组排序后,使用一个变量记录其出现的次数。时间复杂度O(nlogn),空间复杂度O(1)。

class Solution { //5ms
    public int singleNumber(int[] nums) {
        int res = -1;
        int count = 1;
        Arrays.sort(nums);
        if(nums.length == 1) return nums[0];
        for (int i = 1;i < nums.length ;i ++ ) {
            if (nums[i] == nums[i - 1]) {
                count ++;
            }else{
                if(count < 3){
                    res = nums[i - 1];
                    break;
                }
                count = 1;
            }
        }
        if(count < 3 && res == -1) res = nums[nums.length - 1];
        return res;
    }
}

③ 位操作,我们可以建立一个32位的数字,来统计每一位上1出现的个数,我们知道如果某一位上为1的话,那么如果该整数出现了三次,对3取余为0,我们把每个数的对应位都加起来对3取余,最终剩下来的那个数就是单独的数字

class Solution { //3ms
    public int singleNumber(int[] nums) {
        int res = 0;
        for (int i = 0;i < 32 ;i ++ ) {
            int sum = 0;
            for (int j = 0;j < nums.length ;j ++ ) {
                sum += (nums[j] >> i) & 1;
            }
            res |= (sum % 3) << i;
        }
        return res;
    }
}

④ 进位,掩码清零法。用3个整数来表示INT的各位的出现次数情况,one表示出现了1次,two表示出现了2次。当出现3次的时候该位清零。最后答案就是one的值。时间复杂度O(32n),空间复杂度O(1).【注意】位操作是对32位整型数!!!

class Solution { //1ms
    public int singleNumber(int[] nums) {
        int one = 0;
        int two = 0;
        int three = 0;
        for (int i = 0;i < nums.length ;i ++ ) {
            two |= one & nums[i];//two 积累值 
            one ^= nums[i];//one不断求反  
            three = one & two;//第三次的时候one和two都保留了该位的值  
            one &= ~ three;//清零出现三次的该位的值  
            two &= ~ three;
        }
        return one;
    }
}

⑤ 

我们把数组中数字的每一位累加起来对3取余,剩下的结果就是那个单独数组该位上的数字,由于我们累加的过程都要对3取余,那么每一位上累加的过程就是0->1->2->0,换成二进制的表示为00->01->10->00,那么我们可以写出对应关系:

00 (+) 1 = 01

01 (+) 1 = 10

10 (+) 1 = 00 ( mod 3)

那么我们用ab来表示开始的状态,对于加1操作后,得到的新状态的ab的算法如下:

b = b xor r & ~a;

a = a xor r & ~b;

我们这里的a,b就是上面的三种状态00,01,10的十位和个位,刚开始的时候,a和b都是0,当此时遇到数字1的时候,b更新为1,a更新为0,就是01的状态;再次遇到1的时候,b更新为0,a更新为1,就是10的状态;再次遇到1的时候,b更新为0,a更新为0,就是00的状态,相当于重置了;最后的结果保存在b中。

class Solution { //1ms
    public int singleNumber(int[] nums) {
        int a = 0;
        int b = 0;
        for (int i = 0;i < nums.length ;i ++ ) {
            b = (b ^ nums[i]) & ~a;
            a = (a ^ nums[i]) & ~b;
        }
        return b;
    }
}

【解析】 http://www.cnblogs.com/yanliang12138/p/4536189.html

一 逻辑运算符

1. &位与运算符

 1) 运算规则 

位与运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑与运算。例如:int型常量4和7进行位与运算的运算过程如下:

4=0000 0000 0000 0100 &7 =0000 0000 0000 0111= 0000 0000 0000 0100

对于负数,按其补码进行运算。例如:例如:int型常量-4和7进行位与运算的运算过程如下: -4=1111 1111 1111 1100 &7 =0000 0000 0000 0111= 0000 0000 0000 0100

2) 典型应用 

(1) 清零 

清零:快速对某一段数据单元的数据清零,即将其全部的二进制位为0。例如整型数a=321对其全部数据清零的操作为a=a&0x0。

321=0000 0001 0100 0001 &0=0000 0000 0000 0000 = 0000 0000 0000 0000

(2) 获取一个数据的指定位 

获取一个数据的指定位。例如获得整型数a的低八位数据的操作为a=a&0xFF。321=

0000 0001 0100 0001 & 0xFF =0000 0000 1111 11111

= 0000 0000 0100 0001

获得整型数a=的高八位数据的操作为a=a&0xFF00。a&0XFF00==

321=0000 0001 0100 0001 & 0XFF00=1111 1111 0000 0000

= 0000 0001 0000 0000

(3)保留数据区的特定位  

保留数据区的特定位。例如获得整型数 a 的第7-8位(从0开始)位的数据操作为: 110000000

321=0000 0001 0100 0001 & 384=0000 0001 1000 0000

=0000 0001 0000 0000

2. | 位或运算 

1) 运算规则 

位或运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑或运算。例如:int型常量5和7进行位或运算的表达式为5|7,结果如下:

5= 0000 0000 0000 0101 | 7= 0000 0000 0000 0111=0000 0000 0000 0111

2) 主要用途 

(1) 设定一个数据的指定位。例如整型数a=321,将其低八位数据置为1的操作为a=a|0XFF。321= 0000 0001 0100 0001 | 0000 0000 1111 1111=0000 0000 1111 1111

逻辑运算符||与位或运算符|的区别 

条件“或”运算符 (||) 执行 bool 操作数的逻辑“或”运算,但仅在必要时才计算第二个操作数。 x || y , x | y 不同的是,如果 x 为 true,则不计算 y(因为不论 y 为何值,“或”操作的结果都为 true)。这被称作为“短路”计算。

3. ^ 位异或 

 1) 运算规则 

位异或运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑异或运算。只有当对应位的二进制数互斥的时候,对应位的结果才为真。例如:int型常量5和7进行位异或运算的表达式为5^7,结果如下:5=0000 0000 0000 0101^7=0000 0000 0000 0111 = 0000 0000 0000 0010

2) 典型应用 

 (1)定位翻转 

定位翻转:设定一个数据的指定位,将1换为0,0换为1。例如整型数a=321,,将其低八位数据进行翻位的操作为a=a^0XFF;

(2)数值交换 

数值交换。例如a=3,b=4。在例11-1中,无须引入第三个变量,利用位运算即可实现数据交换。以下的操作可以实现a,b两个数据的交换:

a=a^b;

b=b^a;

a=a^b;

4位非 

位非运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑非运算。

二.位移运算符

1.位左移

左移运算的实质是将对应的数据的二进制值逐位左移若干位,并在空出的位置上填0,最高位溢出并舍弃。例如int a,b;

a=5;

b=a<<2;

则b=20,分析过程如下:

(a)10=(5)10=(0000 0000 0000 0101)2

b=a<<2;

b=(0000 0000 0001 0100)2=(20)10

从上例可以看出位运算可以实现二倍乘运算。由于位移操作的运算速度比乘法的运算速度高很多。因此在处理数据的乘法运算的时,采用位移运算可以获得较快的速度。

提示 将所有对2的乘法运算转换为位移运算,可提高程序的运行效率。

2.位右移

位右移运算的实质是将对应的数据的二进制值逐位右移若干位,并舍弃出界的数字。如果当前的数为无符号数,高位补零。例如:

int (a)10=(5)10=(0000 0000 0000 0101)2

b=a>>2;

b=(0000 0000 0000 0001)2=(1)10

如果当前的数据为有符号数,在进行右移的时候,根据符号位决定左边补0还是补1。如果符号位为0,则左边补0;但是如果符号位为1,则根据不同的计算机系统,可能有不同的处理方式。可以看出位右移运算,可以实现对除数为2的整除运算。

提示 将所有对2的整除运算转换为位移运算,可提高程序的运行效率。

3.复合的位运算符

在C语言中还提供复合的位运算符,如下:

&=、!=、>>=、<<=和^=

例如:a&=0x11等价于 a= a&0x11,其他运算符以此类推。

不同类型的整数数据在进行混合类型的位运算时,按右端对齐原则进行处理,按数据长度大的数据进行处理,将数据长度小的数据左端补0或1。例如char a与int b进行位运算的时候,按int 进行处理,char a转化为整型数据,并在左端补0。

补位原则如下:

1) 对于有符号数据:如果a为正整数,则左端补0,如果a 为负数,则左端补1。

2) 对于无符号数据:在左端补0。

转载于:https://my.oschina.net/liyurong/blog/1545310

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值