【算法一周目】位间流转,数字律动——洞察 C++ 位运算中的精妙与哲思

【算法一周目】位间流转,数字律动——洞察 C++ 位运算中的精妙与哲思

文章目录

常见位运算

  1. 判断一个数的二进制表示的第x位是0还是1
    • (n >> x) & 1
  2. 将一个数的二进制表示的第x位修改成1
    • n |= (1 << x)
  3. 将一个数的二进制表示的第x位修改成0
    • n &= ~ (1 << x)
  4. lowbit操作:提取一个数二进制表示中最右侧的1
    • n & -n
  5. 将一个数的二进制表示的最右侧的1修改成0
    • n & (n - 1)

1. 位1的个数

题目链接:191. 位1的个数

题目描述:

编写一个函数,输入一个无符号整数,返回该整数的二进制表示中 1 的个数。

示例

示例 1:

  • 输入:n = 00000000000000000000000000001011
  • 输出:3
  • 解释:输入的二进制串 00000000000000000000000000001011 中,共有 3 个 1

示例 2:

  • 输入:n = 00000000000000000000000010000000
  • 输出:1
  • 解释:输入的二进制串 00000000000000000000000010000000 中,共有 1 个 1

示例 3:

  • 输入:n = 11111111111111111111111111111101
  • 输出:31
  • 解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 个 1

提示

  • 输入必须是一个无符号整数。

解题思路

对于这道题,我们可以将输入的 n n n 右移 32 32 32 次,逐位进行比较;也可以使用上面提到的消除一个数二进制最低位1的操作,统计多少次操作后 n n n 变为 0 0 0 ,得到的次数即为位1的个数。

代码实现

逐位比较

classSolution{public:inthammingWeight(int n){int ans =0;for(int i =0; i <32;++i){if((n >> i)&1) ans++;}return ans;}};

消除一个数二进制最低位1

classSolution{public:inthammingWeight(int n){int ans =0;while(n){ n &= n -1; ans++;}return ans;}};

2. 比特位计数

题目链接LCR 003. 比特位计数

题目描述

给定一个非负整数 n,请计算 0 到 n 之间的每个数字的二进制表示中 1 的个数,并输出一个数组。

示例 1:

  • 输入: n = 2
  • 输出: [0, 1, 1]
  • 解释: 0 --> 01 --> 12 --> 10

示例 2:

  • 输入: n = 5
  • 输出: [0, 1, 1, 2, 1, 2]
  • 解释:0 --> 01 --> 12 --> 103 --> 114 --> 1005 --> 101

说明:

  • 0 <= n <= 10^5

解题思路

这道题要每个数的位1的个数进行统计,很容易想到对每个数进行位运算,统计出每个数二进制表示的1的个数。

代码实现

classSolution{public:inthammingWeight(int n){int ans =0;for(int i =0; i <32;++i){if((n >> i)&1) ans++;}return ans;} vector<int>countBits(int n){ vector<int>ret(n +1);for(int i =1; i <= n;++i) ret[i]=hammingWeight(i);return ret;}};

这种解法的时间复杂度是 O ( 32 n ) O(32n) O(32n) ,但其实这道题还能用动态规划来做。

d p [ i ] dp[i] dp[i] :表示数字 i i i 二进制表示中1的个数

如果 i i i 是偶数,其二进制表示中最低位为 0 0 0 ,右移一位后二进制 1 1 1 的个数不变,则有 d p [ i ] = d p [ i > > 1 ] dp[i] = dp[i>>1] dp[i]=dp[i>>1]

如果 i i i 是计数,其二进制表示中最低位为 1 1 1 ,右移一位后二进制 1 1 1 的个数减少了一个,则有 d p [ i ] = d p [ i > > 1 ] + 1 dp[i] = dp[i>>1] + 1 dp[i]=dp[i>>1]+1

综上可得

  • 递推公式: d p [ i ] = d p [ i > > 1 ] + ( i & 1 ) dp[i] = dp[i >> 1] + (i\And1) dp[i]=dp[i>>1]+(i&1)

代码实现

classSolution{public: vector<int>countBits(int n){ vector<int>ret(n +1);for(int i =0; i <= n;++i) ret[i]= ret[i >>1]+(i &1);return ret;}};

动态规划的解法时间复杂度严格为 O ( n ) O(n) O(n) 。


3.汉明距离

题目链接:461. 汉明距离

题目描述:

两个整数的 汉明距离 是指这两个数字对应的二进制位不同的 positions 的个数。

给定两个整数 xy,计算它们之间的汉明距离。

示例 1:

  • 输入:2
  • 输出:2

示例 2:

  • 输入:x = 3, y = 1
  • 输出:1

提示:

  • 0 <= x, y <= 231 -1

解题思路

将输入的 x x x 和 y y y 右移,逐位比较,统计对应二进制位不同的位置的数目,当 x ∣ y x | y x∣y 为 0 0 0 时,说明两个数最高位 1 1 1 统计完,结束循环。

代码实现

classSolution{public:inthammingDistance(int x,int y){int ans =0;while(x | y){int a = x &1, b = y &1; ans += a ^ b; x >>=1; y >>=1;}return ans;}};

这道题还可以用 l o w b i t lowbit lowbit 来求解, l o w b i t lowbit lowbit 可以提取一个数二进制表示最右侧的 1 1 1 。

我们可以先将 x x x 和 y y y 异或起来,再统计异或结果中 1 1 1 的个数。

classSolution{public:intlowbit(int x){return x &-x;}inthammingDistance(int x,int y){int ret=0;for(int i = x ^ y; i >0; i -=lowbit(i)) ret++;return ret;}};

4. 只出现一次的数字

题目链接:136. 只出现一次的数字

题目描述:

给定一个整数数组 nums,其中元素出现的次数可以是 21,请你找出只出现一次的元素。

你必须实现一个线性时间复杂度的算法,并且不使用额外空间。

示例 1 :

  • 输入:nums = [2,2,1]
  • 输出:1

示例 2 :

  • 输入:nums = [4,1,2,1,2]
  • 输出:4

示例 3 :

  • 输入:nums = [1]
  • 输出:1

提示:

  • 1 <= nums.length <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104
  • 除了某个元素只出现一次以外,其余每个元素均出现两次。

解题思路

对这道题,我们可以使用哈希表来统计数字出现的次数,然后遍历一次哈希表找出出现次数为 2 2 2 的数字。

当然这道题更优的解法是使用异或运算,直接将所有的数异或起来就能得到出现次数为 1 1 1 的数字。

代码实现

classSolution{public:intsingleNumber(vector<int>& nums){int ret =0;for(auto num : nums) ret ^= num;return ret;}};

5. 只出现一次的数字 III

题目链接:260. 只出现一次的数字 III

题目描述:

给定一个整数数组 nums ,其中有两个元素只出现一次,其余元素都出现两次。请你找出那两个只出现一次的元素并返回。

你可以不使用额外空间,并且要求时间复杂度为 O(n)

示例 1:

  • 输入:nums = [1,2,1,3,2,5]
  • 输出:[3,5]
  • 解释:[5, 3] 也是有效的答案。

示例 2:

  • 输入:nums = [-1,0]
  • 输出:[-1,0]

示例 3:

  • 输入:nums = [0,1]
  • 输出:[1,0]

提示:

  • 2 <= nums.length <= 3 * 104
  • -231 <= nums[i] <= 231 - 1
  • 除两个只出现一次的整数外,nums 中的其他数字都出现两次

解题思路

这道题是只出现一次的数字的升级版,要求找到两个只出现一次的元素,我们用异或+分治的思路去解决。

  1. 将数组的所有数字异或起来,得到两个只出现一次的数字的异或值。
  2. 通过位运算得该异或值二进制表示最低位的 1 1 1 是第 i i i 位。
  3. 通过第 i i i 位上值的不同,将原数组划分,因为异或结果二进制表示位为 1 1 1 表示只出现一次的两个数字 a a a 和 b b b 该比特位的不同,我们只需要根据这个划分依据分别将对应的数字异或起来就能得到只出现一次的两个数字。

原理:异或运算,相同为 0 0 0 ,不同为 1 1 1 。

代码实现

classSolution{public: vector<int>singleNumber(vector<int>& nums){int x =0;//a ^ b = xfor(auto e : nums) x ^= e;//判断x第几位是1int i =0;for(i =0; i <32;++i)if((x >> i)&1)break;//根据第i位划分原数组int a =0, b =0;for(auto e : nums){if((e >> i)&1) a ^= e;else b ^= e;}return{a, b};}};

6. 只出现一次的数字 II

题目链接:137.只出现一次的数字 II

题目描述:

给定一个整数数组 nums ,其中每个元素出现三次,除了一个元素只出现一次。请你找出那个只出现一次的元素。

你必须实现一个线性时间复杂度的算法,并且不使用额外空间。

示例 1:

  • nums = [2,2,3,2]
  • 输出:3

示例 2:

  • 输入:nums = [0,1,0,1,0,1,99]
  • 输出:99

提示:

  • 1 <= nums.length <= 3 * 104
  • -231 <= nums[i] <= 231 - 1
  • nums 中,除某个元素仅出现一次外,其余每个元素都恰出现三次。

解题思路

题目要求我们找出只出现一次的元素,并且其他元素出现了 3 3 3 次。

既然这样,那么我们将每个数对应的比特位上的值加起来,将结果 % 3 \%3 %3 ,得到余数只有 0 0 0 和 1 1 1 (目标元素出现 1 1 1 次,其他元素出现 3 3 3 次),余数为 0 0 0 说明目标元素对应比特位上的值为 0 0 0,余数为 1 1 1 说明目标元素对应比特位上的值为 1 1 1。

这样我们只需要对数组所有元素的 32 32 32 个比特位进行同样的操作,就能一位一位的还原出只出现一次的元素。

代码实现

classSolution{public:intsingleNumber(vector<int>& nums){int x =0;for(int i =0; i <32;++i){int bitSum =0;for(auto e : nums) bitSum +=(e >> i)&1; x |=(bitSum %3)<< i;}return x;}};

7. 判定字符是否唯一

题目链接:面试题 01.01. 判定字符是否唯一

题目描述: 实现一个算法,确定一个字符串 s 的所有字符是否全都不同。

示例 1:

  • 输入:s = "leetcode"
  • 输出:false

示例 2:

  • 输入:s = "abc"
  • 输出:true

提示:

  • 0 <= len(s) <= 100
  • s[i] 仅包含小写字母
  • 如果不使用额外的数据结构,会很加分。

解题思路

一个整型变量有 32 32 32 位,而 s[i] 仅包含小写字母,小写字母共有 26 26 26 位,所有我们可以利用位图来充当哈希表,快速判断出判定字符是否唯一。

值得注意的是,如果字符串长度大于 26 26 26 ,那说明字符串里一定有重复的字符,直接返回 f a l s e false false 。

代码实现

classSolution{public:boolisUnique(string astr){//当字符串长度大于26,必定有重复字符if(astr.size()>26)returnfalse;//利用位图做哈希表int bitMap =0;for(auto ch : astr){int i = ch -'a';//判断字符是否已经存在if((bitMap >> i)&1)returnfalse;//加入哈希表 bitMap |=1<< i;}returntrue;}};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1),仅仅使用一个 int 的额外空间。

8. 丢失的数字

题目链接:268. 丢失的数字

题目描述:

给定⼀个包含 [0, n]n个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。

示例 1:

  • 输入:nums = [3,0,1]
  • 输出:2
  • 解释:n = 3,因为有 3 个数字,所以所有的数字都在范围 [0,3] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 2:

  • 输入:nums = [0,1]
  • 输出:2
  • 解释:n = 2,因为有 2 个数字,所以所有的数字都在范围 [0,2] 内。2 是丢失的数字,因为它没有出现在 nums 中。

示例 3:

  • 输入:nums = [9,6,4,2,3,5,7,0,1]
  • 输出:8
  • 解释:n = 9,因为有 9 个数字,所以所有的数字都在范围 [0,9] 内。8 是丢失的数字,因为它没有出现在 nums 中。

示例 4:

  • 输入:nums = [0]
  • 输出:1
  • 解释:n = 1,因为有 1 个数字,所以所有的数字都在范围 [0,1] 内。1 是丢失的数字,因为它没有出现在 nums 中。

提示:

  • n == nums.length
  • 1 <= n <= 104
  • 0 <= nums[i] <= n
  • nums 中的所有数字都独⼊无二

进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?


解题思路

对于这道题,我们可以用异或的解决。对不缺失数字的序列 [ 0 , n ] [0, n] [0,n] ,将其全部数异或起来,得到一个异或值,再用这个异或值与缺失数字的数组 n u m s nums nums 的每个元素异或,最后的得到的结果就是缺失的数字,因为重复的数字已经被消除掉了 ( x ⊕ x = 0 x \oplus x = 0 x⊕x=0),类比于136. 只出现一次的数字

再或者我们可以利用高斯公式快速求出不缺失数字的序列 [ 0 , n ] [0, n] [0,n] 的所有元素和,再减去缺失数字的数组 n u m s nums nums 的每个元素,最后得到的结果也是缺失的数字。

这道题利用哈希也能解决,不过空间复杂度是 O ( n ) O(n) O(n) ,相比之下,并不是最优解法。

代码实现

异或

classSolution{public:intmissingNumber(vector<int>& nums){int x =0;for(auto e : nums) x ^= e;for(int i =0; i <= nums.size();++i) x ^= i;return x;}};

高斯求和

classSolution{public:intmissingNumber(vector<int>& nums){int n = nums.size();//高斯公式求和int sum = n *(n +1)/2;for(auto e : nums) sum -= e;return sum;}};

哈希

classSolution{public:intmissingNumber(vector<int>& nums){int n = nums.size();int* hash =newint[n+1]{0};for(auto e : nums) hash[e]++;for(int i =0; i <= n;++i){if(hash[i]==0){delete[] hash;return i;}}return-1;}};

注意:C++不支持可变长数组 a r r [ n ] arr[n] arr[n] ,所以创建哈希表要使用动态开辟的方法,同时记得要进行初始化。


9. 两整数之和

题目链接:371. 两整数之和

题目描述: 给你两个整数 ab,不使用运算符 +-,计算并返回两整数之和。

示例 1:

  • 输入:a = 1, b = 2
  • 输出:3

示例 2:

  • 输入:a = 2, b = 3
  • 输出:5

提示:

  • -1000 <= a, b <= 1000

解题思路

异或的本质其实是无进位相加, a ⊕ b a \oplus b a⊕b 得到的是 a a a 和 b b b 无进位相加的结果。

而 a a a 和 b b b 的进位是 ( a & b ) < < 1 (a \And b) << 1 (a&b)<<1 ,将其左移是因为相加时是进位的 1 1 1 是加在当前位的下一位。

所以可以得出两整数之和 s u m = a + b ⇒ a ⊕ b + ( a & b ) < < 1 sum = a+b \Rightarrow a \oplus b + (a \And b) << 1 sum=a+b⇒a⊕b+(a&b)<<1

代码实现

迭代写法

classSolution{public:intgetSum(int a,int b){while(b){int x = a ^ b; b =(a & b)<<1; a = x;}return a;}};

递归写法

classSolution{public:intgetSum(int a,int b){return b ==0? a :getSum(a ^ b ,(a & b)<<1);}};

提多几句,实际上关于进位的处理实际上是有点问题的,因为 b b b 是带符号整数,左移操作时如果超出表示范围(左移相当于乘2),其结果是未定义的,所以进位我们最好用无符号整数类型 u n s i g n e d i n t unsigned int unsignedint 来存储,因为无符号整数超出表示范围会自动取模。


10. 只出现一次的数字 II

题目链接:面试题 17.19. 消失的两个数字

题目描述:

给定一个数组,包含从 1N 所有的整数,但其中缺了两个数字。你能在 O ( N ) O(N) O(N) 时间内只用 O ( 1 ) O(1) O(1) 的空间找到它们吗?
以任意顺序返回这两个数字均可。

示例 1:

  • 输入:[1]
  • 输出:[2,3]

示例 2:

  • 输入:[2,3]
  • 输出:[1,4]

提示:

  • nums.length <= 30000

解题思路

这道题其实就是缝合了268. 丢失的数字260. 只出现一次的数字 III,我们还是使用异或+分治来解决问题。

  1. 异或 [ 1 , n ] [1, n] [1,n] 和数组 n u m s nums nums 的所有元素,得到异或值 x x x 。
  2. 异或结果二进制表示位为 1 1 1 表示缺失的两个数字 a a a 和 b b b 该比特位的不同,找出异或值二进制表示最低位的 1 1 1 是第 i i i 位。
  3. 通过第 i i i 位上值的不同,将 [ 1 , n ] [1, n] [1,n] 和数组 n u m s nums nums 的所有元素划分,我们只需要根据这个划分依据分别将对应的元素异或起来就能得到缺失的两个数字。

代码实现

classSolution{public: vector<int>missingTwo(vector<int>& nums){//1.求出缺失两个数的异或xint n = nums.size(), x =0;for(int i =1; i <= n+2;++i) x ^= i;for(auto e : nums) x ^= e;//2.找出a b二进制中不同的那位diffint diff =0;while(((x >> diff)&1)==0) diff++;//3.根据 diff 位的不同,将所有的数划分为两类并异或int a =0, b =0;for(auto e : nums)if((e >> diff)&1) a ^= e;else b ^= e;for(int j =1; j <= n+2;++j)if((j >> diff)&1) a ^= j;else b ^= j;return{a, b};}};
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

Have a good day😏

See you next time, guys!😁✨🎞

请添加图片描述

Read more

FastAPI:Python 高性能 Web 框架的优雅之选

FastAPI:Python 高性能 Web 框架的优雅之选

🚀 FastAPI:Python 高性能 Web 框架的优雅之选 * 🌟 FastAPI 框架简介 * ⚡ 性能优势:为何选择 FastAPI? * 性能对比表 * 🔍 同步 vs 异步:性能测试揭秘 * 测试代码示例 * 测试结果分析 * 🛠️ FastAPI 开发体验:优雅而高效 * 1. 类型提示与自动验证 * 2. 交互式 API 文档 * 🏆 真实案例:为什么企业选择 FastAPI * 📚 后续学习引导 * 🎯 结语 🌟 FastAPI 框架简介 在当今快速发展的互联网时代,构建高效、可靠的 API 服务已成为后端开发的核心需求。FastAPI 作为 Python 生态中的新星,以其卓越的性能和开发者友好特性迅速赢得了广泛关注。 框架概述:FastAPI 是一个现代化的 Python Web 框架,专为构建

By Ne0inhk
《C#属性:优雅的封装艺术 vs C++成员变量:原始的直接访问——谁在定义现代面向对象编程的哲学?

《C#属性:优雅的封装艺术 vs C++成员变量:原始的直接访问——谁在定义现代面向对象编程的哲学?

你是否在C#中使用属性时感到"优雅",而在C++中直接访问成员变量时感到"高效"? 当团队争论"C#属性 vs C++成员变量",却不知这背后是两种编程哲学的生死对决 当你的C#项目因过度使用属性而变得臃肿,而C++项目因缺乏封装而崩溃 别再让技术选择毁掉你的代码质量! 本文揭示C#属性与C++成员变量的10个核心哲学差异——从内存布局到设计模式,每行代码都是来自Google、Microsoft和Tesla的实战血泪经验 没有理论堆砌,只有能直接部署到生产环境的哲学实践 (附:2023年C#与C++项目维护性深度分析 + 100%可运行的双语言对比框架) 为什么这个"比较"是编程哲学的生死线? 2023年IEEE软件工程报告揭示关键数据: * 78%的C#项目因过度使用属性导致性能下降 * 65%的C++项目因缺乏封装导致安全漏洞

By Ne0inhk
【C++】从「树」到「平衡」:全面解密 AVL 树的奥秘与实现

【C++】从「树」到「平衡」:全面解密 AVL 树的奥秘与实现

个人主页:起名字真南的ZEEKLOG博客 个人专栏: * 【数据结构初阶】 📘 基础数据结构 * 【C语言】 💻 C语言编程技巧 * 【C++】 🚀 进阶C++ * 【OJ题解】 📝 题解精讲 目录 * * 前言 * 1 AVL树的概念 * 2 AVL树的实现 * 2.1 AVL树的结构 * 2.2 AVL树的插入 * 2.2.1 插入的大概过程 * 2.2.2 插入节点以及更新平衡因子的代码实现 * 2.3 AVL树的旋转 * **2.3.1 单旋调整** * **左旋 (RotateL)** * **右旋 (RotateR)** * **2.3.2 双旋调整** * **左-右双旋 (RotateLR)** * **右-左双旋 (RotateRL)

By Ne0inhk
【C++高性能计算、系统开发】实战指南:多线程+SIMD+内存池+并发数据结构

【C++高性能计算、系统开发】实战指南:多线程+SIMD+内存池+并发数据结构

文章目录 * 1. 高性能计算核心认知与优化目标 * 1.1 核心性能瓶颈与优化方向 * 1.2 性能评估指标与工具 * (1)核心性能指标 * (2)常用性能分析工具 * 2. C++多线程编程实战(C++11/14/17) * 2.1 线程基础:C++11核心组件 * (1)线程管理(std::thread) * (2)同步机制 * 2.2 C++14/17线程特性增强 * (1)C++14特性 * (2)C++17特性 * 2.3 实战:高性能线程池实现 * 2.4 多线程避坑指南 * 3.

By Ne0inhk