位运算的实践

一、只出现一次的数字 III

1.1题目

给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。

进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?

 1.2、分析

如果使用hash表,就太简单了,这里使用位运算的方式来解答一下。

让我们先来考虑一个比较简单的问题:

如果除了一个数字以外,其他数字都出现了两次,那么如何找到出现一次的数字?

答案很简单:全员进行异或操作即可。考虑异或操作的性质:对于两个操作数的每一位,相同结果为 0,不同结果为 1。那么在计算过程中,成对出现的数字的所有位会两两抵消为 0,最终得到的结果就是那个出现了一次的数字。

那么这一方法如何扩展到找出两个出现一次的数字呢?

如果我们可以把所有数字分成两组,使得:

1、两个只出现一次的数字在不同的组中

2、而相同的数字会被分到相同的组中。

那么对两个组分别进行异或操作,即可得到答案的两个数字。那么如何实现这样的分组呢?

现在,我们假设这两个只出现了一次的数字为 a 和 b,那么所有数字异或的结果就等于 a 和 b 异或的结果,我们记为 x。如果我们把 x 写成二进制的形式,比如“1011010”,我们考虑一下这个x的每一位的含义是什么?它意味着如果我们把 a和 b 写成二进制的形式,因为是异或运算,所以当x的某一位为0,表示a与b的这一位是相等的,当x的某一位为1,表示a与b的这一位是不等的。假如我们在x中,任选一个不为 0 的位i,我们可以根据第 i位的值,给原来的序列分组,如果该位为 0 就分到第一组,否则就分到第二组,因为数组中其他元素在这里必然为0,而a与b有一个会不为0,这样就能满足以上两个条件。

1.3 代码

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int n : nums) // 对所有元素异或
            ret ^= n;
        int div = 1;
        while ((div & ret) == 0) // 在异或结果中找到任意为 1 的位,这里选的最低位
            div <<= 1;
        int a = 0, b = 0;
        for (int n : nums) // 根据这一位对所有的数字进行分组
            if (div & n) // 在每个组内进行异或操作,得到两个数字
                a ^= n;
            else
                b ^= n;
        return vector<int>{a, b};
    }
};


​ 二、组合总和 III​

2.1 题目

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字

说明:

    所有数字都是正整数。
    解集不能包含重复的组合。

 2.2 分析

组合中只允许含有 1−9的正整数,并且每种组合中不存在重复的数字」意味着这个组合中最多包含 9个数字。我们可以把原问题转化成集合 S={1,2,3,4,5,6,7,8,9},我们要找出 S 的当中满足如下条件的子集:

    大小为 k
    集合中元素的和为 n

因此我们可以用子集枚举的方法来做这道题。即原序列中有 9个数,每个数都有两种状态,「被选择到子集中」和「不被选择到子集中」,所以状态的总数为 2^9。我们用一个9 位二进制数 mask来记录当前所有位置的状态,从第到高第 i 位为 0 表示 i 不被选择到子集中,为 1表示 i 被选择到子集中。当我们按顺序枚举 [0, 2^9 - 1]中的所有整数的时候,就可以不重不漏地把每个状态枚举到,对于一个状态 mask,我们可以用位运算的方法得到对应的子集序列,然后再判断是否满足上面的两个条件,如果满足,就记录答案。

如何通过位运算来得到 mask 各个位置的信息?对于第 i个位置我们可以判断 (1 << i) & mask 是否为 0,如果不为 0 则说明 i 在子集当中。当然,这里要注意的是,一个 9 位二进制数 i 的范围是  [0, 8],而可选择的数字是 [1, 9],所以我们需要做一个映射,最简单的办法就是当我们知道 i位置不为 0 的时候将 i + 1 加入子集。

2.3 代码

class Solution {
public:
  vector<int> temp;
    vector<vector<int>> ans;

    bool check(int mask, int k, int n) // 确认mask 各个位置的信息,是否满足条件
    {  
        bool res = false;
        temp.clear();
        for(int i=0;i<9;i++){
            if ((1 << i) & mask) {
                temp.push_back(i + 1); // 将mask中代表的数字存入数组
            }

        }
        res = temp.size() == k && accumulate(temp.begin(),temp.end(),0) == n;
        return res;
    }

    //大小为 k
    //集合中元素的和为 n
    vector<vector<int>> combinationSum3(int k, int n)
    {
        for (int mask = 0; mask < (1 << 9); mask++) {
            if (check(mask, k, n)) {
                ans.push_back(temp);
            }
        }
        return ans;
    }
};

参考:

力扣

力扣

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值