位运算(只出现一次的数字|||)(5)

一.题目

260. 只出现一次的数字 III - 力扣(LeetCode)

二.思路解决

2.1 思路讲解

首先,我们可以通过异或运算 把数组中的所有元素异或起来,那么最终得到的结果就是两个只出现一次的元素 的异或结果。这个结果有什么用呢?关键在于,异或结果中的每一个 1 都代表着这两个元素在该二进制位上的值不同(一个是0,一个是1)。因此,我们可以利用这个结果来区分这两个元素。

接下来,我们需要获取异或结果的最右边的 1 (也可以是任意一个1),这个1标志着这两个元素的第一个不同之处。如何获取最右边的1?这里有一个经典的位运算技巧:xor & -xor

  • 公式推导 :在计算机中,负数采用补码表示。对于一个整数 x-x 等于 ~x + 1(取反加一)。当我们将 x-x 进行按位与运算时,结果恰好保留了 x 最右边的那个1,而其他位全部变为0。例如,x = 1010 1100(二进制),则 -x = 0101 0100,相与得到 0000 0100,即最右边的1所在的位置。这个技巧非常高效,常用于树状数组等算法中。

为什么要找最右边的1?因为我们可以通过这个位将原数组分成两组:一组是该位为 1 的元素,另一组是该位为 0 的元素。这样,两个只出现一次的元素就会被分到不同的组中,而其他成对出现的元素也会被分到同一组中(因为成对出现的元素在该位相同)。

然后,我们分别对这两组进行异或运算。由于每组中除了一个只出现一次的元素外,其他元素都出现两次,因此异或的结果就是该组中那个只出现一次的元素。这样,我们就得到了这两个数。

三.代码演示

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> v1;
        vector<int> v2;
        //找到两个元素的差异
        int ret = 0;
        for(const auto& x:nums)
        {
            ret = ret ^ x;
        }
        ret = (ret == INT_MIN ? ret :ret & -ret);//获取最右边的1
        //把和差异相同的放在一起(即最右边是1的放一起,0的放一起)
        for(const auto& x:nums)
        {
            if(x & ret)
                v1.push_back(x);
            else
                v2.push_back(x);
        }
        //清除各自数组相同元素
        int sum1 = 0;
        int sum2 = 0;
        for(const auto& x:v1)
        {
            sum1 = sum1 ^ x;
        }
        for(const auto& x:v2)
        {
            sum2 = sum2 ^ x;
        }
        return {sum1,sum2};
    }
};

四.代码讲解

一、整体异或得到两个目标数的异或值

首先,我们需要找到数组中两个只出现一次的数字 。由于其他数字都出现两次,我们可以利用异或的自反性 ,将数组中所有元素进行异或。遍历数组,依次执行 ret = ret ^ x,最终得到的 ret 就是这两个只出现一次的数字的异或结果。这个结果中,为 1 的位表示这两个数字在该位上不同。

二、提取最右边的 1 作为区分位

为了将这两个数字分到不同的组中,我们需要从 ret 中找到一个为 1 的位。通常选择最右边的 1 ,因为它容易提取且不影响结果。提取最右边 1 的经典方法是**ret & -ret。**其原理是:负数的补码表示是原数取反加一,与运算后恰好保留最低位的 1。但这里有一个特殊情况:ret 等于 INT_MIN 时,-ret 在补码中等于自身(因为溢出),此时 ret & -ret 仍然等于 INT_MIN,不会出错。因此代码中使用了 ret = (ret == INT_MIN ? ret : ret & -ret),确保安全。

三、根据区分位将原数组分为两组

接下来,我们再次遍历原数组,根据每个元素与区分掩码 ret 的与运算结果,将其分到两个不同的数组中:

  • 如果 x & ret 不为 0,说明该元素在区分位上为 1 ,放入 v1

  • 否则,说明该元素在区分位上为 0 ,放入 v2

  • 这样,两个只出现一次的数字必然被分到不同的组(因为它们在区分位上不同),而其他成对出现的数字由于相同,在区分位上也相同,因此会被分到同一组,且成对出现。

四、分别对两组进行异或得到答案

现在,每一组中除了一个只出现一次的数字外,其余都是成对出现的。因此,我们分别对 v1v2 中的所有元素进行异或,就能得到该组中那个只出现一次的数字。 定义 sum1sum2 并初始化为 0,分别遍历两个数组,执行异或操作。最终 sum1sum2 就是我们要找的两个数字。

相关推荐
A923A2 小时前
【洛谷刷题 | 第九天】
算法·二分·洛谷
AIpanda8882 小时前
数字员工是什么?熊猫智汇在提高销售转化率中的作用有哪些?
算法
源码之家2 小时前
计算机毕业设计:基于Python的美食数据采集可视化系统 Django框架 Scrapy爬虫 可视化 数据分析 大数据 机器学习 食物 食品(建议收藏)✅
python·算法·机器学习·信息可视化·课程设计
简简单单做算法2 小时前
基于Q-Learning强化学习的小车倒立摆平衡控制系统matlab性能仿真
算法·matlab·强化学习·qlearning·小车倒立摆平衡控制
kyle~2 小时前
ROS2---客户端服务(rclcpp::Client)
c++·物联网·机器人·ros2
Book思议-2 小时前
【数据结构】循环与递归:C 语言实现求和与斐波那契数列的两种思路
数据结构·算法·循环与递归
Mr_Xuhhh2 小时前
LeetCode 热题 100 刷题笔记:从数组到字符串的经典解法(续)
java·数据结构·算法
VelinX2 小时前
【个人学习||算法】贪心算法
学习·算法·贪心算法
轻口味2 小时前
HarmonyOS 6 自定义人脸识别模型8:MindSpore Lite框架介绍与使用
c++·华为·ai·harmonyos