关于位运算

说正事前,先说下我的感悟:不要忘了位运算符这个东西,有时候还真挺好用的,你要玩得好,这玩意跟傅里叶变换一样神奇,啥都能给你整出来(还不是题目出的巧~)(我承认我很狭隘,之前只会用常见的运算符T.T)

今日刷LC的每日一题,接触到了位运算,题目如下(对应260. 只出现一次的数字III):

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。
请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例 1:
输入:nums = [4,1,4,6]
输出:[1,6][6,1]

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var singleNumber = function(nums) {

};

这题因为要找只出现一次的,那就是异或咯!

异或规则及规律

规则:
  • 将操作数当作32位的比特序列(由0和1组成),按位操作符操作数字的二进制形式,但是返回值依然是标准的JavaScript数值。(就说这个透明化的二进制操作,是不是就很赞)
  • 异或^:两位数相比,有且只有一个1,返回1;否则,返回0。
规律:
  1. 任何数字x^0=xx^x=0

  2. 如果c=a^b,那么a===c^bb===c^a(是不是很神奇,像加减法而胜过加减法,因为不用管先后顺序)

let a = 1;//001
let b = 2;//010
let c = a^b;//011
console.log(a ^ c);//2,即010
  1. a^b^c^a^c===b(就问你神不神奇)
  2. 异或结果位为1的位置正是ab不一样的地方。故规律2中,c表示ab的最后两位不同
  3. 任意数值x^-1=~x(即x的反码,但这个反码表示不是正数)
let a = -1;//111
let b = 2;//010
console.log(a ^ b);//-3,即101=101=>100=>011
  1. 不论奇偶,x&-x结果都只有一位1同时这也代表着x对应的位置也为1,若x为偶数,则结果是一串只有一个1的二进制,若x为奇数,则结果必为1.
//x为偶数
 x=0001011010
-x=1110100110
 &=0000000010
//x为奇数
 x=1010011011
-x=0101100101
 &=0000000001

下面开始解题思路:

  1. 使用规律1、2、3、4,把整个数组中的数相异或,得到结果c(表示两个仅出现一次的数字的异或结果);
  2. 结合规律4和规律6的启发,我们让diff = c^-c,把数组中的所有数与diff与操作一遍(只有0或非零两种可能),就可以区分出两组数,同时ab因为diff对应位的数字不同,被分别分在了两组中;将其中一组数全部异或,就能得到ab了;
  3. 通过c^步骤2的结果就能得到另一个结果,然后return即可。
    完整代码如下:
/**
 * @param {number[]} nums
 * @return {number[]}
 */
var singleNumbers = function(nums) {
    let c = 0;
    for(let num of nums){
        c ^= num;
    }
    const diff = c&-c;
    let nums1 = 0;
    for(let num of nums){
    	//这里如果要用等于0的情况请务必这么写,因为比较操作符的优先级大于位运算符
    	//if((diff & num) === 0) nums1 ^= num;
        if(diff & num) nums1 ^= num;
    }
    return [nums1,nums1^c];
};

在这里插入图片描述
效率很可观,本菜鸟第一次写不知道位运算这么个方法,用的遍历,执行用时280ms,内存100%,low爆了!

啥?你说规律5没用到?我先写着啊!万一以后有用呢~

这个写过后,解决136.只出现一次的数字I简直就是小case,一行代码搞定了~

再去看看137.只出现一次的数字III,这个更妙了。

给定一个非空整数数组,除了某个元素只出现1次以外,其余每个元素均出现了3次。找出只出现了1次的元素。
要求:时间复杂度O(kN),空间复杂度O(1)

你是不是觉得上述规律都不好使了?还不是因为你位运算符玩的不遛。其实我也想不出来,看的官方解答

//满足题目要求,用时72ms,内存36.3M
var singleNumber = function(nums) {
    let seen_once = 0, seen_twice = 0;
    for(let num of nums){
    	//`seen_once`只会把当下自己没有且`seen_twice`也没有的数纳入
        seen_once = ~seen_twice & (seen_once ^ num);
        //`seen_twice`只会把当下自己没有且`seen_once`也没有的数纳入
        seen_twice = ~seen_once & (seen_twice ^ num);
    }
    return seen_once;
};
  • seen_twice没碰到出现2次的数时,seen_twice===0;
  • seen_once发现自己存的数出现第2次时,立马把该数丢掉,扔给了seen_twice
  • 数字只有出现的第1次(存下)和第2次(抵消),才会被seen_once存下,当数字第3次出现,此时seen_twice中已经存入该数,故seen_once直接忽略此数;
  • 数字只有出现的第2次(存下)和第3次(抵消),才会被seen_twice存下,当数字第3次出现时,seen_twice立马丢掉该数
			[1,		2,		1,		1]
seen_once	0001	0011	0010	0010
seen_twice	0000	0000	0001	0000

大致就是这个意思啦~ 打死我都想不到~
我写的两种都是循环

//这个空间复杂度不符合,用时88ms,内存35.2M,我个人认为这方法还不错
var singleNumber = function(nums) {
    let unique = new Set();
    for(let i=0;i<nums.length;i++){
        if(!unique.has(nums[i]) && nums.lastIndexOf(nums[i])===i) return nums[i];
        unique.add(nums[i]);
    }
};
//这个时间复杂度不符合,用时100ms,内存36.4M
var singleNumber = function(nums) {
    nums.sort((a,b)=>a-b);
    let c = nums[0], i=0;
    while(i<nums.length){
        c ^= nums[i+1];
        if(c===0){
            i += 3;
            c = nums[i];
            continue;
        }
        else{
            break;
        }
    }
    return nums[i];
};

看了题解,竟然还有人写只出现k次的数,其他数出现p次的算法…小生不才,就不多打扰大佬们了~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值