算法札记——4.27

今天记录三道有关位运算的有趣的算法题,都是找出数组中存在的仅出现一次的数字:

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


按照题目要求是,除一个元素外,其余元素均出现了两次,然后让我们找到那个仅出现一次的元素。看到题目立即可以想到哈希表,统计所有的数据出现次数,最后将仅出现一次的数据返回即可,当然接下来的两题均可使用此方法解决。

但题目存在一个需要使用常量空间的限制条件(当然上述方法是可以过题的),若要满足该要求,显然是不能使用容器来统计数据个数的方法的。此时,便需要运用到位运算的知识:对于两个相同的数进行异或,结果为0;任何数与0异或,数值不变 。那么将数组中所有的元素进行异或到一起呢,显然最后仅出现一次的数会留下来 ,而其余两两相同的数则会因为异或而消除

以下是题解:

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (auto x : nums)
            ret ^= x;
        return ret;
    }
};

137. 只出现一次的数字 II - 力扣(LeetCode)


这题与上道的不同仅仅是其余的元素的出现次数增加到了3。但显然异或的方法是不能用的,需要另辟蹊径。此时,可想到有关数据的二进制存储:对于二进制,数位非1即0,若将n(n > 1)个相同元素,与一个不同的元素的固定比特位相加,再对和模n,不就可以得到那个不同元素在该数位上的值,因此可以:

每次,将数组中所有的数二进制的固定数位相加,最后模3,得到的结果如果为1,ret在该数位就为1;为0,ret在该数位上为0。

因为除结果元素在数组中仅出现1次,其余元素均出现了3次,所有元素的固定比特位相加模3,即可消除除结果元素的数据痕迹,只留下结果元素在该比特位的值。

以下是题解:

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; ++i)
        {
            int bit = 0;
            for (auto x : nums)
                bit += ((x >> i) & 1);
            ret |= (1 << i) * (bit % 3);
        }
        return ret;
    }
};

显然,n也可以取2,此时便于前一题的条件相同,所以前一题也可以这样解决:

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; ++i)
        {
            int bit = 0;
            for (auto x : nums)
                bit += ((x >> i) & 1);
            ret |= (1 << i) * (bit % 2);
        }
        return ret;
    }
};

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


此题与第一题的不同就是仅出现一次的元素增加到了2个。显然,前面的方法并不能直接套用解决。但运用第一题的第一个解法与二进制的存储方式(原码,反码和补码)可以提供解题思路:

(以题中例一举例)对数组所有元素进行异或得到tmp = 6,-tmp = -6,对两数的二进制表示:

tmp:> 00000000 00000000 00000000 00000110

-tmp:> 11111111 11111111 11111111 11111010

此时对两数进行&运算,可以发现除了第二数位为1,其余数位都为0。

tmp & -tmp:> 00000000 00000000 00000000 00000010

这样就可以得到原数组中结果元素相异的最小相异数位,通过该值对原数组所有元素相与来进行分组,相同的组进行异或运算,相同元素均会处在相同的组而消除,而结果两元素由于在该数数位相异而分开,最后仅有两结果元素会留下。

以下为题解:

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        long long tmp = 0;
        for (auto val : nums)
            tmp ^= val;
        int x = 0, y = 0;
        /*
        二进制表示(以示例一举例):
         tmp(6):  00000000 00000000 00000000 00000110
        -tmp(-6): 11111111 11111111 11111111 11111010
        想与可以得到结果的两数二进制中最小一位相异的数位
        */
        tmp &= -tmp;
        for (auto val : nums)
            (tmp & val) != 0 ? x ^= val : y ^= val;
        return {x, y};
    }
};
相关推荐
数据牧羊人的成长笔记2 小时前
逻辑回归与Softmax回归
算法·回归·逻辑回归
郑州光合科技余经理3 小时前
同城O2O海外版二次开发实战:从支付网关到配送算法
开发语言·前端·后端·算法·架构·uni-app·php
d111111111d5 小时前
STM32-UART封装问题解析
笔记·stm32·单片机·嵌入式硬件·学习·算法
Jiangxl~7 小时前
IP数据云如何为不同行业提供精准IP查询与风险防控解决方案?
网络·网络协议·tcp/ip·算法·ai·ip·安全架构
李伟_Li慢慢7 小时前
wolfram详解山峦算法
前端·算法
counting money8 小时前
prim算法最小生成树(java)
算法
澈2078 小时前
C++面向对象:类与对象核心解析
c++·算法
用户690673881928 小时前
基于无人机的单目测距系统,平均误差仅2.12%
算法
dinl_vin8 小时前
LangChain 系列·(四):RAG 基础——给大模型装上“外脑“
人工智能·算法·langchain