题目
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
leetcode
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
解题思路
题目要求找到只出现一次的两个数字,并规定了时间复杂度是O(n),空间复杂度是O(1);这么快,显然只能用数学的方法解决。
一.位运算
- & 与 两者都为1,结果才为1;
- (1).清零 a&0=0
- (2).判断某一位是否为1 10000111 & 10000000= 10000000 ; 00000111 & 10000000 = 0
- (3).取一个数字的指定位 a&0000 1111,取a都后四位 (2)(3)一个意思
- (4). (a&b)<<1 二进制加法的进位
- | 或 两者都为0,结果才为0
- (1).对某些位设置位1 a | 0000 1111,后四位设置位1
- ^ 异或 两位相同位1,相异为1
- (1). x^x = 0, x^0 = x; a^b^b=a^0=a;
- (2).反转指定位 a^0000 1111,后四位翻转
- (3).二进制无进位相加, a^b
- (4). 交换两个数 a^=b;b^=a;a^=b;
- ~ 取反 0变1,1变0
- (1).最低位变0 a~1 111~001=110
二、本题的解法是利用与&的(2)和异或^的(1)
- 如果只有一个数只出现一次,那么用异或的(1)就可,所有数字全部异或就可以得到答案
- 但本题有两个,所有数字异或出来的结果k是这两个数字的异或;
- 利用&(2) 某一位不同可以把这两个数字分开,同时可以让其他只出现两次的数字在一起。
- 所以要找到一个数字可以让num1和num2分开,因为k是num1和num2的异或,k中所有为1的位都是二者的不同,找到一个1就可以
10000111 & 10000000= 10000000 ; 00000111 & 10000000 = 0
代码
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
//先将所有元素异或。最后剩下的数是只出现一次的数字num1,num2的异或
int k = 0; // 0^x = x
for(int i : nums) //所有数字异或
k ^= i;
int mask = 1;
while((k&1)!=1)
{
//从后往前找到最低位的1;
k= k>>1;
mask = mask<<1;
}
int num1=0,num2=0;
//用这个mask就可以把两个数区分开,
for(int i : nums)
{
if((i&mask)!=0) num1^=i; // mask指定的那一位是num1,num2的不同
else num2^=i;
}
return {num1,num2};
}
};
补充题:不用加减乘除做加法
**题目:**写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
算法思路:利用&的(4)和^的(3)
class Solution {
public int add(int a, int b) {
int sum,carry;
//和与进位
do{
sum = (a^b);
carry = (a&b) << 1;
a = sum;
b = carry;
}while(b!=0);
return a;
}
}