LeetCode 只出现一次的数字 I、II、III

戳这里:LeetCode 136: 只出现一次的数字 I

题目要求: 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:
具有线性时间的复杂度,因此直接使用双层for循环的方法排除了
不使用额外空间,因此hash表的方法也排除了

这里使用的是位异或的方法

按位异或的特点:两个相同的数字异或得到的是0、一个数字与位全0的数字异或得到的是数字本身、一个数字与位全1的数字异或得到的是每一位取反

例如:
(a ^ a)^(b ^ b) == a ^ b ^ a ^ b == 0
(a ^ 0) == a
(0110 ^ 1111) == 1001

根据题意:所有数字只有一个数字出现一次,其余的数字都出现了两次,由上面例一可知,异或的顺序可以随意交换

题解:定义一个数字 ret = 0、循环所有数字与之异或,最终得到的就是只出现一次的数字

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < nums.size(); ++i) {
            ret = ret^nums[i];
        }
        return ret;
    }
};

戳这里:LeetCode 137: 只出现一次的数字 II

题目要求:给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。

解法一: 首先这道题不能使用上一题的解法

第一种方法介绍一种数学方法

所有的数字去重后相加的三倍减去所有数字相加除以二

这一长串公式很好理解、公式表示为:
对于 a b a c a b b :3 * (a+b+c) - (a+b+a+c+a+b+b) = 2 * c 然后除以2得到的就是 c

代码实现:

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        set<int> s; // 去重
        
        long long sum1 = 0; // 使用long long防止溢出
        for (int i = 0; i < nums.size(); ++i) {
            sum1 += nums[i];
            s.insert(nums[i]);
        }
        
        long long sum2 = 0;
        auto it = s.begin();
        while (it != s.end()) {
            sum2 += *it;
            ++it;
        }
        
        int ret = (3*sum2-sum1)>>1;
        return ret;
    }
};

上面这种方法虽然可以,但是效率是非常低的
在这里插入图片描述

那么如何,让我们的算法具有线性时间复杂度,并且不使用额外空间来实现呢?

第二种方法: 位运算方法

举例:5 1 9 5 1 9 5 1 9 4

将它们都转化为二进制观察

十进制 二进制
5 0 1 0 1
1 0 0 0 1
9 1 0 0 1
5 0 1 0 1
1 0 0 0 1
9 1 0 0 1
5 0 1 0 1
1 0 0 0 1
9 1 0 0 1
4 1 0 0 0

仔细观察发现,每一列 1 的个数一定是3的倍数,如果不是一定是因为只出现一次的数字贡献了1

跟随着这个规律,就可以开始编写代码了

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; ++i) {
            int count_1 = 0; // 统计第i位1的个数
            for (int j = 0; j < nums.size(); ++j) {
                 if ((nums[j] & (1<<i)) != 0) {
                     count_1++;
                 }
            }
            // 1个数不为三的倍数,说明只出现一次的数字贡献了1
            if (count_1 % 3 != 0)
                ret |= (1 << i);
        }
        return ret;
    }
};

戳这里:LeetCode 260: 只出现一次的数字 III

题目要求: 给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。

这里只介绍异或方法

只出现一次的数字 I中我们将所有的元素进行异或得到了结果,这道题有两个只出现一次的数字,因此将所有元素异或之后得到的是两个只出现一次的数字的异或值
例如:1 5 3 1 3 9 只出现一次的数字是 5 9 所有元素异或,得到结果 12

那么我们如何将12重新拆分成为5 9呢?
从二进制来分析,为了便于观察,我直接将元素进行排序后展示(实际并不排序)

十进制 二进制
1 0 0 0 1
1 0 0 0 1
3 0 0 1 1
3 0 0 1 1
5 0 1 0 1
9 1 0 0 1

很抽象,我直接给方法吧!
12 的二进制 1 1 0 0 其中1的位置是5 和 9 不同的位,0的位置是5和9相同的位,利用的就是5和9从低开始第一个不同的位来将12重新拆分为5和9(也就是第三位

从表格中观察所有数字的第三位,1 和 3 的第三位都是成对出现,只有5和9的第三位不是成对出现,并且不同(一个是1,另一个是0)

那么我们将所有第三位为1的数字进行异或(除了5之外其他都是成对出现),因此得到的就是5喽(第一题的思想),然后将12和5异或得到9,==> 完毕!

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        if (nums.size() < 2)
            return {};
        // num是将所有元素的异或值
        int num = 0;
        for (auto e : nums) {
            num ^= e;
        }
        // pos是从低开始第一个不同的位
        int pos = 0;
        for (int i = 0; i < 32; ++i) {
            if (num & (1 << i)) {
                pos = i;
            }
        }
        // ret1和ret2 ⇒ 存储返回结果
        // 将所有低pos位为1的数字进行异或得到其中一个结果
        // 将之与num异或得到第二个结果
        int ret1 = 0;
        int ret2 = 0;
        for (auto e : nums) {
            if (e & (1 << pos)) {
                ret1 ^= e;
            }
        }
        ret2 = num^ret1;
        return {ret1,ret2};
    }
};
发布了146 篇原创文章 · 获赞 82 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_40860852/article/details/102809479