笔试算法 -位运算篇(二):从唯一字符到消失数字

目录

🎬 云泽Q个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列》《笔试算法

⛺️遇见安然遇见你,不负代码不负卿~


前言

大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~

一、判定字符是否唯一

面试题 01.01.判定字符是否唯一

解法(位图的思想)
算法思路

利用「位图」的思想,每一个「比特位」代表一个「字符,一个 int 类型的变量的 32 位足够表示所有的小写字母。比特位里面如果是 0,表示这个字符没有出现过。比特位里面的值是 1,表示该字符出现过。

那么我们就可以用一个「整数」来充当「哈希表」。

cpp 复制代码
class Solution {
public:
    bool isUnique(string astr) {
        //鸽巢原理
        if(astr.size() > 26) return false;
        
        //位图
        int bitMap = 0;
        for(auto& ch : astr)
        {
            //将字符映射到位图
            int i = ch - 'a';
            //判断字符之前是否出现过
            if(((bitMap >> i) & 1) == 1) return false;
            //当前字符存入位图
            bitMap |= (1 << i);
        }
        return true;
    }
};

二、丢失的数字

268.丢失的数字

解法一(哈希表 + 暴力枚举)

cpp 复制代码
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> mp(n + 1);
        for(auto& e : nums) mp[e]++;
        for(int i = 0; i <= n; i++)
        {
            if(mp[i] == 0)
                return i;
        }
        return 0;
    }
};

解法二:高斯公式求和

cpp 复制代码
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int n = nums.size();
        int sum = 0;
        for(auto& e : nums) sum += e;
        //((首项 + 末项) * 理论项数)/ 2
        int ret = (((0 + n) * (n + 1)) / 2) - sum;
        return ret;
    }
};

该解法虽然优化了空间复杂度,但是有一个小坑:如果n很大(比如接近1e5),n*(n+1)可能会超过int的范围(int最大约 2 × 109),导致溢出。解决方法:把总和用long long类型存储:

解法三(最推荐):异或运算的运算律

解法(位运算):

算法思路:

设数组的大小为 n,那么缺失之前的数就是 [0, n],数组中是在 [0, n] 中缺失一个数形成的序列。

如果我们把数组中的所有数,以及 [0, n] 中的所有数全部「异或」在一起,那么根据「异或」运算的「消消乐」规律,最终的异或结果应该就是缺失的数~

cpp 复制代码
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int n = nums.size();
        int ret = 0;
        for(auto& e : nums) ret ^= e;
        for(int i = 0; i <= n; i++) ret ^= i;
        return ret;
    }
};

三、两整数之和

371.两整数之和

解法(位运算)
算法思路

  • 异或 ^ 运算本质是「无进位加法」;
  • 按位与 & 操作能够得到「进位」;
  • 然后一直循环进行,直到「进位」变成 0 为止。
cpp 复制代码
class Solution {
public:
    int getSum(int a, int b) {
        while(b != 0)
        {
            //先算出无进位相加的结果
            int x = a ^ b;
            //算出进位
            //int carry = (a & b) << 1;
            unsigned int carry = (unsigned int)(a & b) << 1;
            a = x; b = carry;
        }
        return a;
    }
};

这里代码的写法主要是为了防止一个隐患:
C++ 标准规定:有符号整数的左移操作,如果结果超出该类型的范围(如溢出),行为是未定义的

a & b 的结果为负数(即符号位为 1),尤其是当结果为 INT_MIN(32 位 int 下为-2147483648)时,左移 1 位会导致:

  • INT_MIN << 1 的结果超出 int 范围(INT_MIN * 2 = -4294967296,远小于INT_MIN),属于未定义行为。
  • 未定义行为的结果不可预测:在 x86 平台上可能被截断为0,但在其他平台(如 ARM)可能出现错误结果或程序崩溃。

四、只出现一次的数字 ||

137.只出现一次的数字 ||

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        //依次填写ret中的每一个bit位
        for(int i = 0; i < 32; i++)
        {
            int count = 0;
            //计算nums种所有数的第i位的和
            for(auto& num : nums)
                if(((num >> i) & 1) == 1) count++;
            if((count % 3) != 0)
                ret |= (1 << i);

        }
        return ret;
    }
};

五、消失的两个数字

面试题 17.19. 消失的两个数字

  1. 解法(位运算):

算法思路:

本题就是 268. 丢失的数字 + 260. 只出现一次的数字 III 组合起来的题。

先将数组中的数和 [1, n + 2] 区间内的所有数「异或」在一起,问题就变成了:有两个数出现了「一次」,其余所有的数出现了「两次」。进而变成了 260. 只出现一次的数字 III 这道题。260这道题我有在其他文章中写过解析,建议先把这两道题做过后再做该题就会很轻松,链接我贴到这里方便跳转攻克算法面试:C++ Vector 核心问题精讲

这里给出两种解法:

解法一

cpp 复制代码
class Solution {
public:
    vector<int> missingTwo(vector<int>& nums) {
        //1.将所有的数异或在一起
        size_t tmp = 0;
        for(auto& num : nums) tmp ^= num;
        for(int i = 1; i <= nums.size() + 2; i++) tmp ^= i;

        //2.找到异或和种最右边的1的位置,用于分组
        size_t mask = tmp & (-tmp);

        //3.根据分组再进行异或消消乐
        int a = 0, b = 0;
        for(auto& e : nums)
        {
            if(e & mask) a ^= e;
            else b ^= e;
        }
        for(int i = 1; i <= nums.size() + 2; i++)
        {
            if(i & mask) a ^= i;
            else b ^= i;
        }
        return {a, b};
    }
};

解法二

cpp 复制代码
class Solution
{
public:
    vector<int> missingTwo(vector<int>& nums)
    {
        // 1. 将所有的数异或在一起
        int tmp = 0;
        for(auto x : nums) tmp ^= x;
        for(int i = 1; i <= nums.size() + 2; i++) tmp ^= i;

        // 2. 找出 a, b 中比特位不同的那一位
        int diff = 0;
        while(1)
        {
            if(((tmp >> diff) & 1) == 1) break;
            else diff++;
        }

        // 3. 根据 diff 位的不同,将所有的数划分为两类来异或
        int a = 0, b = 0;
        for(int x : nums)
            if(((x >> diff) & 1) == 1) b ^= x;
            else a ^= x;
        for(int i = 1; i <= nums.size() + 2; i++)
            if(((i >> diff) & 1) == 1) b ^= i;
            else a ^= i;

        return {a, b};
    }
};

整体来说解法一更优,解法二的思路也值得学习~

解法一的tmp & (-tmp) 是硬件层面直接支持的高效位运算,而循环和移位操作会引入额外的指令开销,即使是常数时间,也会比单次位运算慢


结语

相关推荐
ʚ希希ɞ ྀ1 小时前
不同路径|| -- dp
算法
繁华落尽,倾城殇?1 小时前
[C++11] : atomic,nullptr,default/delete,enum class
开发语言·c++·c++11·nullptr·atomic·enum class·default/delete
代码村新手2 小时前
C++-二叉搜索树
开发语言·c++
IT 行者2 小时前
SimHash 与 MinHash:相似性计算的双子星算法
算法·hash·比对
智者知已应修善业2 小时前
【51单片机8位数码管动态显示日期小数点风格】2023-11-13
c++·经验分享·笔记·算法·51单片机
智者知已应修善业2 小时前
【51单片机有三个LED 分别第一个灯闪三下 再到第二个灯又闪三下 再到第三个灯又闪三下 就这样循环程序】2023-11-16
c++·经验分享·笔记·算法·51单片机
小L~~~4 小时前
基于贪心策略的混合遗传算法求解01背包问题
python·算法
洛水水4 小时前
【力扣100题】53.最长回文子串
算法·leetcode·职场和发展