137. 只出现一次的数字 II

1.题目介绍

2.题解

2.1 哈希表

思路

同本系列题I,不过多赘述

代码

class Solution {
public:
    int singleNumber(std::vector<int>& nums) {
        std::unordered_map<int,int> map;
        for (int num:nums){
            map[num]++;
        }
        for (auto pair:map){
            if (pair.second == 1) return pair.first;
        }
        return 0;
    }
};

结果展示

空间代价不是很理想

2.2 依次确定每一个二进制位

思路(分治算法)

这里的思路要类比上一题中的异或运算(排除所有出现偶次的数,使用了异或运算的性质)
这里重复出现三次,是一个三的倍数,如何利用好这个性质?
1.首先思考整体求和除三或者对其求和用3求余,发现了一个问题,对总和用3求余的时候,确实能求出0,1,2,但是这里的数可能是n3 + (0,1,2),无法确定这个n?
2.我们就在想能不能消除这个n,最好把这个n拆成n次一位一位的运算就好了,那么每一位我都能确定是0,1,2(或者更少的数),之后统一再变回来。
3.我们再看看这个表达式:n
3 + (0,1,2),是不是感觉很像一种类似三进制的公式;
4.换句话说,如果这里的进制位数小于3,比如像二进制,就可以直接将数组中所有元素每一位求和后对3求余,得到的结果定是在(0,1)之间取一个,是一个确定的值(即单独出现的这个数的该位),之后再通过二进制的数确定该数即可
5.可是我并不知道单独出现的这个数的位数?有限定:-2^31 <= nums[i] <= 2^31 - 1,数组中的元素都在 int(即 32 位整数)范围内

代码

class Solution {
public:
    int singleNumber(std::vector<int>& nums) {
        int result = 0, temp = 0, pos = 0;
        for (int i = 0; i < 32; i++)
        {
            for (int num : nums)
            {
                temp += (num >> i) & 1;
            }
            result += (temp % 3) << i;
            temp = 0;
        }
        return result;
    }
};

结果展示

这里遍历每个二进制位使得时间代价乘上了一个logC,能否进一步优化?

2.3 数字电路设计

思路

我们在计算机组成原理中,或许学过一课:简单的加法器(半加器和全加器),

那我们是否能模仿这里面的思路使用(整体位运算)代替(第i位相加综合%3的for循环操作)呢?
答案是可以的,这也就是我们为何在该系列I中使用异或运算是最优解的原因,那里均为两个一组,正好也满足二进制的进位制。但本题是三个一组,就需要进行一定的修改了。
思考一下我们这里如何解决这个三次一进位的问题呢?
我们在I中只使用了temp一个中间变量,temp每一位(i)的值正是(0,1)中取一个,足够,那我们现在能否使用两个中间变量来表示(0,1,2)呢?可以的
请看下图



我们来分析一下对于ai,bi的公式怎么来的吧

1.对于ai

ai=aibixi+aibixi

1.当 ai=0ai, 只有当 bixi时(同时为1),ai1,0
2.当 ai=1ai, 只有当 bixi*(同时为0) , ai01

2.对于bi

b=abx+abx=a(bx)

bi=aibixi+aibixi=ai(bixi)

3.总结

当我们遍历完数组中的所有元素后,(aibi) 要么是 (00),表示答案的第 i 位是 0; 要么是(01),表示答案的第 i 位是 1。因此我们只需要返回 b 作为答案即可。

{a=(a&b&x)|(a&b&x)b=∼a&(bx)

代码

1.pair

表达式 pair(a&b&num)|(a&b&num),a&(bnum) 会计算两个整数值,然后创建一个匿名 pair 对象并将这两个值传递给 pair 的构造函数

2.tie

tie相当于是一个解包pair的作用,将pair中的两个值分别赋值给a,b
相当于a=(a&b&num)|(a&b&num),b=∼a&(bnum)

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int a = 0, b = 0;
        for (int num: nums) {
            tie(a, b) = pair{(~a & b & num) | (a & ~b & ~num), ~a & (b ^ num)};
        }
        return b;
    }
};

2.4 数字电路设计优化

我们发现方法三中计算 b 的规则较为简单,而 a 的规则较为麻烦,因此可以将「同时计算」改为「分别计算」,即先计算出 b,再拿新的 b 值计算 a

根据真值表可以设计出电路:ai=aibixi+aibixi=bi(aixi)
这样就与 bi 的电路逻辑非常类似了。最终的转换规则即为:

{b=∼a&(bx)a=∼b&(ax)

代码

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int a = 0, b = 0;
        for (int num: nums) {
            b = ~a & (b ^ num);
            a = ~b & (a ^ num);
        }
        return b;
    }
};

作者:力扣官方题解
链接:https://leetcode.cn/problems/single-number-ii/solutions/746993/zhi-chu-xian-yi-ci-de-shu-zi-ii-by-leetc-23t6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
posted @   DawnTraveler  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示