个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创位运算(4)_丢失的数字
收录于专栏【经典算法练习】
本专栏旨在分享学习算法的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
温馨提示:
1. 题目链接 :
2. 题目描述 :
3. 解法 :
解法一(哈希表) :
算法思路 :
代码展示 :
结果分析 :
解法二(数学高斯求和) :
算法思路 :
代码展示 :
结果分析 :
解法三(位运算) :
算法思路 :
代码展示 :
结果分析 :
温馨提示:
本文的算法题需要一些位运算知识的基础,如果大家还不是很了解的话,可以先去看下面的博客:
位运算(1)_常见位运算总结-CSDN博客
1. 题目链接 :
OJ链接: 丢失的数字
2. 题目描述 :
给定一个包含 [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
中的所有数字都 独一无二
进阶:你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题?
3. 解法 :
解法一(哈希表) :
算法思路 :
哈希表构建:
首先,定义一个哈希表 ret,用于记录数组 nums 中每个数字的出现次数。
遍历 nums 数组,对于每个数字 ch,将其在哈希表中的计数加一。这样,ret 中将存储每个数字及其出现的次数。
查找缺失的数字:接下来,循环从 0 到 nums.size(),检查每个数字是否在哈希表 ret 中存在。这里的 nums.size() 是 n,意味着数组的数字范围是 0 到 n,因为我们期望有 n + 1 个数字(从 0 到 n)。
如果在哈希表中找不到某个数字(即 ret[i] 等于 0),则返回该数字 i,它就是缺失的数字。
返回结果:如果所有数字都存在于哈希表中,函数会返回 0,尽管在这个特定问题中,这种情况是不可能发生的,因为问题的设定保证总会有一个缺失的数字。
代码展示 :
class Solution {
public:
int missingNumber(vector<int>& nums) {
unordered_map<int, int> ret;
for(auto ch : nums)
ret[ch]++;
for(int i = 0; i <= nums.size(); i++)
if(!ret[i]) return i;
return 0;
}
};
结果分析 :
时间复杂度
这个算法的时间复杂度为O(n),因为我们分别遍历了一次数组来构建哈希表和一次来查找缺失的数字。
空间复杂度
空间复杂度为O(n),因为在最坏的情况下,我们可能需要存储数组中所有的数字在哈希表中。尽管这个算法有效,但使用哈希表可能会浪费一些空间。可以考虑其他更优化的方法,例如使用数学公式或位运算来降低空间复杂度。
解法二(数学高斯求和) :
算法思路 :
计算理论总和:
对于一个长度为n 的数组,数字的范围是从0 到n(共n + 1 个数字)。理论上,这些数字的总和可以使用高斯求和公式计算:
total_sum = n * (n + 1) / 2
这里的n 是数组 nums 的大小,表示有n + 1 个数字,缺失一个数字。通过遍历 nums 数组,将所有数字的值累加,并减去上述计算的理论总和。具体做法是初始化 ret 为 total_sum,然后遍历 nums 中的每个数字 ch,并从 ret 中减去 ch 的值。
返回缺失的数字:最后,ret 的值将是缺失的数字,因为所有存在的数字都已被减去,留下的就是缺失的数字。
代码展示 :
class Solution {
public:
int missingNumber(vector<int>& nums) {
int ret = nums.size() * (nums.size() + 1) / 2;
for(auto ch : nums)
ret -= ch;
return ret;
}
};
结果分析 :
时间复杂度
O(n),因为需要遍历整个数组一次。
空间复杂度:
O(1),因为只使用了有限的额外空间,不随输入规模增加而增加。
优点
这个方法避免了使用额外的数据结构(如哈希表),使得空间复杂度降低,性能更优,尤其在处理大规模输入时表现更好。
解法三(位运算) :
算法思路 :
设数组的大小为n,那么缺失之前的数就是[0,n],数组中是在[0,n]中缺失一个数形成的序列.
如果我们把数组中所有数,以及[0,n]中的所有数全部[异或]在一起,那么根据[异或]运算的[消消乐]规律,最终的异或结果应该是缺失的数~
代码展示 :
class Solution {
public:
int missingNumber(vector<int>& nums) {
int ret = 0;
for(int i = 0; i <= nums.size(); i++)
ret ^= i;
for(auto ch : nums)
ret ^= ch;
return ret;
}
};
结果分析 :
时间复杂度:
O(n),因为我们遍历了数组两次(一次计算完整范围的异或值,一次计算实际数组的异或值)。
空间复杂度:
O(1),因为只使用了有限的额外空间来存储 ret。
优点
这个方法是高效且简洁的,利用异或的性质避免了使用额外的数据结构,特别适合于这个类型的问题。
由于位运算速度非常快,这种方法在处理大规模数据时非常高效。