算法题位运算-数组中数字出现的次数

数组中数字出现的次数

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

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

输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]

来源:力扣(LeetCode)
题目链接:https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof

解题思路

说实话,对于这种题我第一眼看见的感觉脑子里会直接闪过暴力遍历的方法,但是用这个方法先不说不满足题意,而且显得自己像个呆鸡。总结之前的题目经验,第二时间想到使用哈希表来做,但是开辟哈希表的话空间复杂度就不满足O(1)的要求。所以暂时的最佳解法是使用位运算的方法来解决。因为之前位运算使用的比较少,所以思路上并不开阔,理解起来比较绕,所以在这里把理顺的思路写出来,记录一下。
解这个题需要知道异或(XOR) ^ 的运算规则:
1、交换律。 A ^ B = B ^ A
2、结合律。 (A ^ B) ^ C = A ^ (B ^ C)
3、对于任何数都有 A ^ 0 = A, A ^ A = 0
4、所以 A ^ B ^ B = XOR ^ B -> A = XOR ^ B

再知道运算规则过后,假设数组内的不重复的数为A和B,那么对数组元素全部进行异或即 nums[0] ^ nums[1] ^ … ^nums[n-1] = A ^ B。接下来就是取巧的地方了,获取AB异或值最低位为1的数,即获取AB两个数位不相同的最低位div。用这个最低位对AB进行分组,A&div 和 B&div 会等于0或者1,同时还可以对数组内的元素进行分组,相同的数值会分到同一组,那么对这个A组和B组的元素分别进行异或,那么两个组最后剩下的就只有A 和 B了。

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int xorAB = 0; // 定义AB的异或值,即整个数组的异或值
        for(int num:nums) { //求异或值
            xorAB ^= num;
        }

        int div = xorAB & (-xorAB); //定义分组方法,这种方法还有别的办法,只要能区分出AB两个组就可以。
        int A = 0; //
        for(int val:nums) { 
            if((div&val) != 0) { // 对A组内的所有数进行异或,求的A的值
                A ^= val;
            }
        }
        return vector<int>{A, xorAB^A}; // xorAB^A 就是B
    }
};

数组中数字出现的次数 II

题目描述:在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

示例 1:

输入:nums = [3,4,3,3]
输出:4
示例 2:

输入:nums = [9,1,7,9,7,9,7]
输出:1

题目链接:https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof

解题思路

1、hash表
使用hash表来做的话空间复杂度会变高

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        unordered_map<int, int> umap;
        for(int i=0; i<nums.size(); i++) {  //将数值值存入hash表中,并且统计出现过的次数
           umap[nums[i]]++;
        }
        int index;
        for(index=0; index<nums.size(); index++) { //在表中查找出现过的次数,如果不等于3就找到了
            if(umap[nums[index]] != 3) break; 
        }
        return nums[index];
    }
};

2、排序遍历
将数组进行排序,然后遍历数组找到不重复的数,由于重复次数是3,所以不重复数出现的位置一定是0,3,6,9位置上

class Solution {
public:
    int singleNumber(vector<int>& nums) { 
        sort(nums.begin(), nums.end());
        for(int i=0; i<nums.size()-1; i+=3) {  // 按照步长为3进行遍历
            if(nums[i] != nums[i+1]) { //在步长内存在不相同的数,那么直接返回。
                return nums[i];
            }
        }
        return nums[nums.size()-1]; //数组的长度对3取模一定会余1,如果之前没找到,那么一定回事最后一个
    }
};

3、位运算
在二进制的每个位上只会存在0,1两种状态,而一个3进制的状态是0,1,2即00->01->10->00。也就代表着一个位上加3次就会变回原有位的数。那么数组中的三个相同的数相加对应为会变成0,进行位运算,就能从一位推到整个数上。所以使用a,b来代替00,01,10的首位和第二位 ab = 01 即 a = 0, b = 1。
在这里插入图片描述

首先计算第二位即b的状态推移公式:
if a == 0:
   if n == 0: b = b;
   if n == 1: b = ~b;
if a == 1:
   b == 0;
那么进行位运算简化就可得:
if a == 0:
   b^n;
if a == 1:
   b == 0;
继续就有:
b = b^n&(~a);
在计算完b状态后,a状态需要根据转换后的b进行推导:
a = a^n&(~b);
将公式代入整个数组中,由于初始状态只能是00,01,所以最后每一位上存在的状态也只能是00,01,这个两个状态是由b决定的,并且值和b是相同的。代码如下。

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int a = 0, b = 0;
        for(int val:nums) {
            b = b ^ val & (~a);
            a = a ^ val & (~b);
        }
        return b;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值