【优选算法】(实战体会位运算的逻辑思维)


🔥承渊政道: 个人主页
❄️个人专栏: 《C语言基础语法知识》 《数据结构与算法》 《C++知识内容》 《Linux系统知识》 《算法刷题指南》 《测评文章活动推广》 《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨ 🎬 博主简介:

在算法学习的过程中,位运算往往是一个既基础又容易被忽视的知识点.它不像排序、动态规划那样"显眼",却常常在优化空间复杂度、提升运行效率、设计巧妙解法时发挥关键作用.很多看似复杂的问题,一旦转换到二进制视角,就会变得清晰而直接.本文中的相关题目,正是帮助我们深入理解位运算思想的良好切入点.通过这些实战题目,不仅可以掌握与、或、异或、左移、右移等常见操作的基本规律,更重要的是逐步建立一种"从二进制结构出发分析问题"的逻辑思维方式.这样的思维,不只是为了应对面试题,更是在训练我们用更底层、更抽象的方式理解数据与运算本质.本文将结合具体题目,围绕位运算在判奇偶、交换变量、去重查找、状态压缩以及技巧性优化中的应用展开分析.在解题的过程中,我们不仅关注"怎么做",更关注"为什么这样做更高效".希望通过这些内容,能够让位运算不再只是几个符号的机械组合,而成为解决问题时真正可用的一把"利器".废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!

目录

1.常见的位运算总结

位运算是直接操作二进制位 的运算,执行效率远高于加减乘除等算术运算,是算法优化、底层开发、状态控制的核心技巧.先明确:位运算仅针对二进制位(0/1) 操作,所有数字都会先转为二进制再计算.
1️⃣按位与 &
规则 :两位都为 1,结果为 1;否则为 0

复制代码
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0

示例 :3 (011) & 5 (101) = 1 (001)
2️⃣按位或 |
规则 :任意一位为 1,结果为 1

复制代码
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0

示例 :3 (011) | 5 (101) = 7 (111)
3️⃣按位异或 ^(最常用!)
规则 :两位不同1,相同0

复制代码
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0

核心特性:

  • x ^ 0 = x(任何数异或0等于自身)
  • x ^ x = 0(任何数异或自身等于0)
  • 满足交换律/结合律
    示例 :3 (011) ^ 5 (101) = 6 (110)

4️⃣按位取反 ~
规则 :0变1,1变0(一元运算符)

⚠️注意:有符号数中,取反结果为 ~x = -x - 1
示例 :~3 = -4
5️⃣左移 <<
规则 :二进制位左移n位 ,右侧补0
等价 :x << n = x × 2ⁿ
示例 :3 (011) << 1 = 6 (110)
6️⃣右移 >>
规则 :二进制位右移n位 ,左侧补符号位 (正数补0,负数补1)
等价 :x >> n = x ÷ 2ⁿ(向下取整)
示例 :5 (101) >> 1 = 2 (10)


1.1位1的个数(OJ题)


核心代码

cpp 复制代码
class Solution {
public:
    int hammingWeight(int n) {
        int count = 0;
        //转为无符号数,避免负数符号位干扰
        unsigned int num = n;
        while (num != 0) {
            //清除最低位的1
            num &= num - 1;
            count++;
        }
        return count;
    }
};

1.2比特位计数(OJ题)


核心代码

cpp 复制代码
class Solution {
public:
    vector<int> countBits(int n) {
        vector<int> ans(n + 1);
        for (int i = 0; i <= n; ++i) {
            int cnt = 0;
            unsigned int num = i; //避免符号位干扰
            while (num != 0) {
                num &= num - 1; //清除最低位的1
                cnt++;
            }
            ans[i] = cnt;
        }
        return ans;
    }
};

1.3汉明距离(OJ题)


核心代码

cpp 复制代码
class Solution {
public:
    int hammingDistance(int x, int y) {
        int z = x ^ y;
        int cnt = 0;
        while (z != 0) {
            z &= z - 1; //清除最低位的1
            cnt++;
        }
        return cnt;
    }
};

1.4只出现一次数字(OJ题)


核心代码

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

1.5只出现一次的数字|||(OJ题)


核心代码

cpp 复制代码
class Solution {
public:
    vector<int> singleNumber(vector<int>& nums) {
        int xor_sum = 0;
        //1. 全员异或,得到两个目标数的异或结果
        for (int num : nums) {
            xor_sum ^= num;
        }
        
        //2. 找到最低位的1,作为分组掩码
         // 补码特性:保留最低位1,其余位清0,循环找到最低位的1,无溢出风险
        int mask = 1;
        while ((xor_sum & mask) == 0) {
            mask <<= 1;
        }
        
        //3. 按mask分组,分别异或得到结果
        int a = 0, b = 0;
        for (int num : nums) {
            if (num & mask) {
                a ^= num; // 该组包含其中一个目标数
            } else {
                b ^= num; // 该组包含另一个目标数
            }
        }
        
        return {a, b};
    }
};

2.判断字符是否唯一(OJ题)


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

利用位图的思想,每一个比特位代表一个字符,一个 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;
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

class Solution
{
public:
    bool isUnique(string astr)
    {
        //鸽巢原理优化:小写字母只有26个,长度>26一定重复
        if (astr.size() > 26)
            return false;

        //用int的32个比特位模拟位图,存储26个小写字母的出现状态
        int bitMap = 0;
        for (auto ch : astr)
        {
            //计算字符对应的比特位下标(a=0, b=1...z=25)
            int i = ch - 'a';

            //右移i位后与1相与,判断该位是否已被占用(字符重复)
            if (((bitMap >> i) & 1) == 1)
                return false;

            //将对应比特位设为1,标记该字符已出现
            bitMap |= 1 << i;
        }
        //遍历完成,无重复字符
        return true;
    }
};

int main() {
    Solution sol;

    string s1 = "";
    cout << "输入:\"" << s1 << "\"  输出:" << boolalpha << sol.isUnique(s1) << endl;

    string s2 = "abc";
    cout << "输入:\"" << s2 << "\"  输出:" << boolalpha << sol.isUnique(s2) << endl;

    string s3 = "leetcode";
    cout << "输入:\"" << s3 << "\"  输出:" << boolalpha << sol.isUnique(s3) << endl;

    string s4 = "abcdefghijklmnopqrstuvwxyz";
    cout << "输入:\"" << s4 << "\"  输出:" << boolalpha << sol.isUnique(s4) << endl;

    string s5 = "abcdefghijklmnopqrstuvwxyza";
    cout << "输入:\"" << s5 << "\"  输出:" << boolalpha << sol.isUnique(s5) << endl;

    return 0;
}

3.丢失的数字(OJ题)


算法思路:解法(位运算)

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

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

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

完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int ret = 0;
        //第一步:异或数组中所有元素
        for (auto x : nums)
            ret ^= x;
        //第二步:异或 0 ~ nums.size() 的所有整数
        for (int i = 0; i <= nums.size(); i++)
            ret ^= i;
        //最终结果就是缺失的数字(重复出现的数会异或抵消为0)
        return ret;
    }
};

int main() {
    Solution sol;

    vector<int> nums1 = {3, 0, 1};
    cout << "测试用例1:nums = [3,0,1] → 缺失数字:" << sol.missingNumber(nums1) << endl;

    vector<int> nums2 = {0, 1};
    cout << "测试用例2:nums = [0,1] → 缺失数字:" << sol.missingNumber(nums2) << endl;

    vector<int> nums3 = {9,6,4,2,3,5,7,0,1};
    cout << "测试用例3:nums = [9,6,4,2,3,5,7,0,1] → 缺失数字:" << sol.missingNumber(nums3) << endl;

    vector<int> nums4 = {0};
    cout << "测试用例4:nums = [0] → 缺失数字:" << sol.missingNumber(nums4) << endl;

    vector<int> nums5 = {1};
    cout << "测试用例5:nums = [1] → 缺失数字:" << sol.missingNumber(nums5) << endl;

    return 0;
}

4.两整数之和(OJ题)


算法思路:解法(位运算)

  • 异或 ^ 运算本质是无进位加法;
  • 按位与 & 操作能够得到进位;
  • 然后一直循环进行,直到进位变成 0 为止.


核心代码

cpp 复制代码
class Solution {
public:
    int getSum(int a, int b) {
        while (b != 0) {
            int x = a ^ b; //先算出⽆进位相加的结果
            unsigned int carry = (unsigned int)(a & b) << 1; //算出进位
            a = x;
            b = carry;
        }
        return a;
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
using namespace std;

class Solution {
public:
    int getSum(int a, int b) {
        while (b != 0) {
            int x = a ^ b; //无进位加法结果
            unsigned int carry = (unsigned int)(a & b) << 1; //计算进位(无符号避免溢出)
            a = x;
            b = carry;
        }
        return a;
    }
};

int main() {
    Solution sol;
    
    cout << "3 + 1 = " << sol.getSum(3, 1) << endl;
    cout << "2 + (-3) = " << sol.getSum(2, -3) << endl;
    cout << "-1 + (-2) = " << sol.getSum(-1, -2) << endl;
    cout << "5 + 0 = " << sol.getSum(5, 0) << endl;
    cout << "6 + (-6) = " << sol.getSum(6, -6) << endl;
    cout << "-1000 + 1000 = " << sol.getSum(-1000, 1000) << endl;

    return 0;
}

5.只出现一次的数字||(OJ题)


算法思路:解法(比特位计数)

设要找的数位 ret.

由于整个数组中,需要找的元素只出现了一次,其余的数都出现的三次,因此我们可以根据所有数的某一个比特位的总和 %3 的结果,快速定位到 ret 的一个比特位上的值是 0 还是 1.这样,我们通过 ret 的每一个比特位上的值,就可以将 ret 给还原出来.

核心代码

cpp 复制代码
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        for (int i = 0; i < 32; i++) //依次去修改ret中的每⼀位
        {
            int sum = 0;
            for (int x : nums) //计算nums中所有的数的第i位的和
                if (((x >> i) & 1) == 1)
                    sum++;
            sum %= 3;
            if (sum == 1)
                ret |= 1 << i;
        }
        return ret;
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int ret = 0;
        //遍历int的32个二进制位(0~31位)
        for (int i = 0; i < 32; i++)
        {
            int sum = 0;
            //统计所有数字第i位为1的总个数
            for (int x : nums)
                if (((x >> i) & 1) == 1)
                    sum++;
            //对3取余,余数为1说明目标数的第i位是1
            sum %= 3;
            if (sum == 1)
                ret |= 1 << i; // 将ret的第i位设为1
        }
        return ret;
    }
};

int main() {
    Solution sol;
    
    vector<int> nums1 = {2,2,3,2};
    cout << "测试用例1:[2,2,3,2] → 只出现一次的数字:" << sol.singleNumber(nums1) << endl;
    
    vector<int> nums2 = {0,1,0,1,0,1,99};
    cout << "测试用例2:[0,1,0,1,0,1,99] → 只出现一次的数字:" << sol.singleNumber(nums2) << endl;
    
    vector<int> nums3 = {-2,-2,1,1,4,1,4,4,-3,-2};
    cout << "测试用例3:[-2,-2,1,1,4,1,4,4,-3,-2] → 只出现一次的数字:" << sol.singleNumber(nums3) << endl;
    
    vector<int> nums4 = {5,5,5,7};
    cout << "测试用例4:[5,5,5,7] → 只出现一次的数字:" << sol.singleNumber(nums4) << endl;

    return 0;
}

6.消失的两个数字(OJ题)


算法思路:解法(位运算)

本题就是丢失的数字+只出现一次的数字III组合起来的题.大家可以回顾一下这两道题的算法思想.

先将数组中的数和 [1, n + 2] 区间内的所有数异或在一起,问题就变成了:有两个数出现了一次,其余所有的数出现了两次.进而变成了只出现一次的数字 III 这道题.
核心代码

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};
    }
};

完整测试代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

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};
    }
};

int main() {
    Solution sol;
    
    vector<int> nums1;
    vector<int> res1 = sol.missingTwo(nums1);
    cout << "测试用例1:nums = [] → 缺失的两个数:" << res1[0] << " " << res1[1] << endl;
    
    vector<int> nums2 = {1};
    vector<int> res2 = sol.missingTwo(nums2);
    cout << "测试用例2:nums = [1] → 缺失的两个数:" << res2[0] << " " << res2[1] << endl;
    
    vector<int> nums3 = {2,3};
    vector<int> res3 = sol.missingTwo(nums3);
    cout << "测试用例3:nums = [2,3] → 缺失的两个数:" << res3[0] << " " << res3[1] << endl;
    
    vector<int> nums4 = {1,2,3,5};
    vector<int> res4 = sol.missingTwo(nums4);
    cout << "测试用例4:nums = [1,2,3,5] → 缺失的两个数:" << res4[0] << " " << res4[1] << endl;

    return 0;
}


🚀真正的勇者不是流泪的人,而是含泪奔跑的人!


敬请期待下一篇文章内容:【优选算法】(实战推演模拟算法的蕴含深意)


每日心灵鸡汤:坦然接受一切,允许一切发生!
总有一天你会明白,"生命中最强大的力量,不是对抗,而是接受."人活着,总会经历许多坎坷与挫折,有的人选择勇敢接受,坦然面对;有的人却选择惶恐逃避,或怨天尤人,或一蹶不振.其实,面对生命里的无常与艰难,与其焦虑和烦恼,不如放平心态,试着去允许和接受.凡事不计较,不强求,不对峙,不取悦,不迎合,不执念,允许一切如其所是,允许自己做自己,也允许别人做别人;接纳不可更改之事,看淡、看开,然后全身心投入当下生活,去遇见更多的美好和诗意.真正的强大,是允许一切发生;人生最高的境界,是接纳一切无常的变化.生活不过是见招拆招,经事炼心,你要做的就是修炼强大的内心,随时做好允许和接纳的准备.请相信,一切都是最好的安排!

相关推荐
Frostnova丶2 小时前
LeetCode 2573. 找出对应 LCP 矩阵的字符串
算法·leetcode·矩阵
AI-Ming2 小时前
程序员转行学习 AI 大模型: 踩坑记录:服务器内存不够,程序被killed
服务器·人工智能·python·gpt·深度学习·学习·agi
m0_716765232 小时前
C++提高编程--STL常用容器(set/multiset、map/multimap容器)详解
java·开发语言·c++·经验分享·学习·青少年编程·visual studio
2501_945318492 小时前
零基础学习AI的选型指南:CAIE认证与编程型AI认证如何取舍
人工智能·学习
承渊政道2 小时前
【优选算法】(实战推演模拟算法的蕴含深意)
数据结构·c++·笔记·学习·算法·leetcode·排序算法
林鸿群2 小时前
实现支持纳秒级精度的时间引擎(C++)
算法·定时引擎
Keep learning!3 小时前
PCA主成分分析学习
学习·算法
朽棘不雕3 小时前
c++中为什么new[]和delete[]要配对使用
c++
专注VB编程开发20年3 小时前
CUDA实现随机切割算法,显卡多线程计算
算法·cuda