题目
一个整型数组 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]
限制:
2 <= nums <= 10000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
本题简化版:leetcode136 只出现一次的数字
今天这题我想了好久,只想到了HASH表法。思路就是对每一个数进行hash,根据hash值分两种情况讨论:
- 没有出现过的结果,申请空间并将该位置1
- 出现过的结果,释放该空间
如此看来,最后申请的空间只有两个没有重复出现过的数字被申请了空间,其余数字的空间都被释放了。这种算法对这类找出若干个出现次数不同的题目都适用。
但是很可惜,这种算法对空间复杂度的开销可能到达n/2,也就是会达到O(N)的量级,不符合题目要求。
还有一种做法也是常见的:将数组元素对应的下标的元素进行改动。这种方法适用于数组元素中最大值小于数组元素个数的情况(leetcode有做到这样的题,改天看到回来在这里给个题目链接)。很遗憾,这题并没有这样的限制,所以这种做法不适合这道题。
苦思冥想之下,我依然没有想出这道题目的合理解。遂去翻看了官方题解。题解采用的方法是我从未见过的——位运算。基本思路是相同数字异或后对应结果一定为0.如果数组中只有一个出现过一次的元素,所有数字异或以后的最终结果就是答案。问题是这道题告知有两个数只出现过一次,因此就必须要将问题分组。那么分组又带来了新问题,相同的数字必须要在同一组,且两个只出现了一次的数字必须在不同的组。那么这里应该怎么进行操作呢?我依然没有想出,这里就是题解的第二个神来之笔了——根据所有数字最终异或的结果,结果里必然有一些位是1,那么就取那一位当作标准。这位有这么两个性质:
- 两个只出现一次的数字,在这一位必定不一样
- 相同的数字在这一位一定相同
于是,就可以以此为依据进行分组。该位为1的进入组1进行异或,该位为0的进入组2进行异或。
申请两个空间,每个空间最终异或的结果就是两个目标值。返回这两个目标值就可以了。
代码
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* singleNumbers(int* nums, int numsSize, int* returnSize){
int mask = 0;
int i;
for(i = 0; i < numsSize; i++)
{
mask ^= nums[i];
}
mask = mask & (-1*mask);
int m1 = 0, m2 = 0;
for(i = 0; i < numsSize; i++)
{
if((nums[i] & mask) == 0)//位运算优先级低于关系运算
{
m1 ^= nums[i];
}
else
{
m2 ^= nums[i];
}
}
*returnSize = 2;
int *a = (int *)malloc(2*sizeof(int));
a[0] = m1;
a[1] = m2;
return a;
}
// 作者:imane
// 链接:https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/solution/wei-yun-suan-tql-by-imane/
// 来源:力扣(LeetCode)
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
运行结果
反思优化
因为我之前没有接触过位运算的题解,所以本题的代码我是从题解区复制的。我认为这段代码思路清晰,结构简洁,但是运行速率只能说是一般,仍然存在优化空间,我希望可以在此基础上进行优化。
然而,不得不说,思路上的优化空间已经不多了,只能从细节上入手。leetcode的话有时候1ms的差距就是好几个百分点。我想到可以把m1,m2的变量一起申请,也就是说提前申请返回数组的空间,减少了一次申请和两次赋值操作。还可以采用memset对数组进行置0,这样速度会比直接置0快一些。优化后的代码如下:
int* singleNumbers(int* nums, int numsSize, int* returnSize){
int *res_nums = (int *)malloc(sizeof(int) * 2);
int i, flag, xor_sum = 0;
memset(res_nums, 0, 2 * sizeof(int));
for (i = 0; i < numsSize; i++) {
xor_sum ^= nums[i];
}
flag = (-xor_sum) & xor_sum;
for (i = 0; i < numsSize; i++) {
if ((flag & nums[i]) == 0) {
res_nums[0] ^= nums[i];
} else {
res_nums[1] ^= nums[i];
}
}
*returnSize = 2;
return res_nums;
}
优化后的运行结果好看不少:
以上就是今天的leetcode日记了,看到这里的朋友点个赞吧,这对我真的很重要~~