【leetcode日记】面试题56-数组中出现的数字次数

题目

一个整型数组 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. 没有出现过的结果,申请空间并将该位置1
  2. 出现过的结果,释放该空间

如此看来,最后申请的空间只有两个没有重复出现过的数字被申请了空间,其余数字的空间都被释放了。这种算法对这类找出若干个出现次数不同的题目都适用。
但是很可惜,这种算法对空间复杂度的开销可能到达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;
}

优化后的运行结果好看不少:
运行结果2

以上就是今天的leetcode日记了,看到这里的朋友点个赞吧,这对我真的很重要~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邵政道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值