目录


🎬 云泽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;
}
};
二、丢失的数字
解法一(哈希表 + 暴力枚举)
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)可能出现错误结果或程序崩溃。
四、只出现一次的数字 ||

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;
}
};
五、消失的两个数字
- 解法(位运算):
算法思路:
本题就是 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) 是硬件层面直接支持的高效位运算,而循环和移位操作会引入额外的指令开销,即使是常数时间,也会比单次位运算慢
结语
