【C++】题解:三道只出现一次的数字问题

2023-12-26 16:45:05


本文介绍了三道只出现一次的数字问题的解法,分别是使用异或运算的方法、使用位运算的方法和使用异或运算和位运算相结合的方法。这三种方法都满足了题目中要求的线性时间复杂度和常数额外空间的条件。

只出现一次的数字i

https://leetcode.cn/problems/single-number/description/

给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
在这里插入图片描述

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (auto num : nums)
        {
            ret ^= num;
        }
        return ret;
    }
};

代码中使用了一个变量ret来记录结果,初始值为0。然后通过一个循环遍历整个nums数组,对ret和当前遍历到的数字进行异或操作(ret ^= num)。由于异或运算的性质,相同的数字异或结果为0,任何数字与0异或仍然为它本身。因此,最终ret的值将会是那个只出现了一次的元素。

只出现一次的数ii

https://leetcode.cn/problems/single-number-ii/description/
给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。

你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。
在这里插入图片描述

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ans = 0;
        for (int i = 0; i < 32; ++i) {
            int cnt = 0;
            for (auto& num : nums) {
                cnt += (num >> i) & 1;
            }
            if (cnt % 3) ans |= 1 << i;
        }
        return ans;
    }
};

这段代码是针对 “除某个元素仅出现一次外,其余每个元素都恰出现三次” 的情况,使用位运算来找出那个只出现了一次的元素。

首先定义一个变量ans来记录结果,初始值为0。然后通过两层循环来进行处理。

外层循环遍历32位二进制数的每一位,即从低位到高位逐位处理。内层循环遍历整个nums数组,统计当前二进制位上1出现的次数(cnt += (num >> i) & 1)。因为除了那个只出现一次的元素外,其他元素都出现了三次,所以1出现的次数必然是3的倍数或者0,不会是1或2。

接着判断cnt % 3是否等于1,如果是,则说明那个只出现了一次的元素在当前二进制位上为1,需要将ans的相应二进制位也设为1,即ans |= 1 << i

最终处理完32位二进制数的每一位后,ans就记录了那个只出现了一次的元素的值。

这段代码的时间复杂度为O(32n),其中nnums数组的长度,因为需要对32位二进制数的每一位进行处理;而且没有使用额外的空间,满足了题目中要求的线性时间复杂度和常数级空间的条件。

只出现一次的数iii

https://leetcode.cn/problems/single-number-iii/description/

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

你必须设计并实现线性时间复杂度的算法且仅使用常量额外空间来解决此问题。
在这里插入图片描述

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int xor2 = 0;
        for (auto& num : nums) {
            xor2 ^= num;
        }
        int mask = 1;
        while (!(mask & xor2)) {
            mask <<= 1;
        }
        vector<int> ret(2);
        for (auto& num : nums) {
            if (mask & num) ret[0] ^= num;
            else ret[1] ^= num;   
        }
        return ret;
    }
};

这段代码是用于找出一个整数数组中只出现一次的两个元素。

首先,通过对数组中的所有元素进行异或运算,得到一个结果xor2。由于异或运算满足交换律和结合律,所以所有出现两次的元素都会被抵消为0,最终xor2的值就是两个只出现一次的元素的异或结果

接下来,使用一个变量mask来标记xor2中不为0的最低位。初始时,mask的值为1。通过循环不断左移mask的位置,直到找到xor2中不为0的最低位,即mask & xor2不为0

然后需要找到开始不相同的那个标志位mask,初始化 mask1,表示二进制数的最低位为 1。然后通过一个循环来逐次左移 mask 的位置,直到找到 xor2 中不为 0 的最低位。循环条件 !(mask & xor2) 意味着 maskxor2 进行与运算后结果不为 0,即 maskxor2 的某一位上的比特位为 1

然后,创建一个大小为2vector ret来记录结果。再次遍历nums数组,根据mask与当前数字的与运算结果来将数组中的所有元素分为两组。具体来说,如果mask与当前数字的与运算结果为0,则说明该数字在mask位置上为0,属于第二组;否则,说明该数字在mask位置上为1,属于第一组。

最后,对两个分组分别进行异或运算,得到两个只出现一次的元素,并存入ret数组中。

最终,返回ret数组即可。

这段代码的时间复杂度为O(n),其中nnums数组的长度,因为只对数组进行了两次遍历;而且没有使用额外的空间,满足了题目中要求的线性时间复杂度和常数额外空间的条件。

总结

在第一道题目中,我们使用异或运算来找出只出现一次的元素。由于异或运算的性质,相同的数字异或结果为0,任何数字与0异或仍然为它本身。因此,最终的结果就是那个只出现了一次的元素。

在第二道题目中,我们使用位运算来找出只出现一次的元素。我们遍历32位二进制数的每一位,统计当前二进制位上1出现的次数。由于除了那个只出现一次的元素外,其他元素都出现了三次,所以1出现的次数必然是3的倍数或者0,不会是1或2。最终处理完32位二进制数的每一位后,就能得到只出现一次的元素。

在第三道题目中,我们结合使用异或运算和位运算来找出只出现一次的两个元素。首先,通过对数组中的所有元素进行异或运算,得到一个结果xor2。由于异或运算满足交换律和结合律,所以所有出现两次的元素都会被抵消为0,最终xor2的值就是两个只出现一次的元素的异或结果。然后,使用一个变量mask来标记xor2中不为0的最低位。再次遍历数组,根据mask与当前数字的与运算结果来将数组中的所有元素分为两组。最后,对两个分组分别进行异或运算,得到两个只出现一次的元素。

总之,这三道题目的解法都是比较巧妙的,需要对异或运算和位运算有一定的了解。掌握了这些技巧后,我们可以很快地解决类似的问题。

文章来源:https://blog.csdn.net/Colorful___/article/details/135222987
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。