一、只出现一次的数字 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;
}
};
参考: