位运算 - 找出数组中缺失的那个数

1.题目

LeetCode: 268. 丢失的数字

【easy】

2.解题

方法一:异或

分析题意,长为n的数组包含0到n的n个数,将所有数与数组下标进行异或操作,剩下的结果是多余的数组下标,即丢失的元素。

java:

class Solution {
    public int missingNumber(int[] nums) {
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            res = res ^ i ^ nums[i];
        }
        return res ^ nums.length;
    }
}

时间复杂度:O(n)。这里假设异或运算的时间复杂度是常数的,总共会进行 O(n) 次异或运算,因此总的时间复杂度为 O(n)。

空间复杂度:O(1)。算法中只用到了 O(1) 的额外空间,用来存储答案。

方法二:数学

根据求和公式我们可以知道所有元素的和,遍历数组减去所有数,最后剩下的值便是结果。

class Solution {
    public int missingNumber(int[] nums) {
        int sum = nums.length * (nums.length - 1) / 2;
        for (int num : nums) {
            sum = sum - num;
        }
        return sum + nums.length;
    }
}

时间复杂度:O(n)。

空间复杂度:O(1)。

<think>我们从提供的引用中提取了四种不同的方法来查找0到n中缺失字: 1. 引用[1]:使用辅助数组,通过将原数组元素作为下标存入辅助数组,最后检查辅助数组中值为0的位置即为缺失字。 2. 引用[2]和[3]:使用二分查找法,利用数组有序的特性,通过比较中间元素的值和下标的关系,缩小查找范围。 3. 引用[4]:使用异或(XOR)运算,利用XOR的性质(相同异或为0,0与任何异或为该本身)来找出缺失字。 此外,还有常见的一种方法:学求和法,即计算0到n的总和(等差列求和)然后减去数组中所有字的和,差值即为缺失字。 下面我们将详细介绍这些方法: ### 方法1:辅助数组法(空间换时间) **思路**: 1. 创建一个长度为n+1的辅助数组(初始值全0)。 2. 遍历给定的数组,将每个元素的值作为辅助数组的下标,并将该位置的值置为1(或非0值)。 3. 遍历辅助数组,如果某个位置的值仍为0,则该位置的下标就是缺失字。 **示例**: 假设数组长度为3(n=3),数组为[0,1,3],则缺失字是2。 - 创建辅助数组b,长度为4(下标0~3),初始为[0,0,0,0]。 - 遍历数组:a[0]=0 -> b[0]=1(非0);a[1]=1 -> b[1]=1;a[2]=3 -> b[3]=1。 - 遍历b数组:b[0]=1, b[1]=1, b[2]=0, b[3]=1 -> 缺失字为2。 **时间复杂度**:$O(n)$ **空间复杂度**:$O(n)$ ### 方法2:二分查找法(适用于有序数组) **思路**: 1. 数组是排序的(题目中通常是有序的),利用数组下标和元素值的关系。 2. 在缺失字之前,每个元素的值等于其下标;在缺失字之后,元素的值大于其下标。 3. 使用二分查找找到第一个元素值大于其下标的位置,该位置即为缺失字。 **算法步骤**: - 初始化左边界`l=0`,右边界`r=n-1`(n为数组长度,数组下标0~n-1,字范围0~n,共n+1个数,其中缺失一个)。 - 当`l<=r`时循环: - 计算中间下标`mid = l + (r-l)/2`(防止溢出)。 - 如果`nums[mid] == mid`,则说明缺失字在mid的右边,因此`l = mid+1`。 - 否则(`nums[mid] > mid`),缺失字在mid的左边(包括mid),因此`r = mid-1`。 - 循环结束后,`l`即为缺失字。 **示例**: 数组[0,1,3](n=3,数组长度3,字范围0~3) - 初始:l=0, r=2 - 第一次:mid=1,nums[1]=1,等于1,则l=mid+1=2 - 第二次:l=2, r=2, mid=2, nums[2]=3>2,所以r=mid-1=1 - 循环结束,l=2,即缺失字为2。 **时间复杂度**:$O(\log n)$ **空间复杂度**:$O(1)$ ### 方法3:异或法(XOR) **思路**: 利用异或运算的性质:任何与0异或等于其本身,相同异或等于0。 1. 初始化一个变量`res`为0。 2. 将`res`与0到n的所有整进行异或。 3. 再将`res`与数组中的所有元素进行异或。 4. 最后`res`的值就是缺失字。 **原理**: 假设缺失字为x,那么在第一次异或时,我们得到了0到n所有的异或结果(包括x)。然后我们再用这个结果与数组中的每个数异或,由于数组中除了缺失x外,其他都在0到n(除了x)之间,且每个数都出现了,所以除了x以外的都出现了两次(一次在0~n的异或中,一次在数组的异或中),因此两次异或抵消为0,而x只出现了一次(在0~n的异或中出现),所以最后的结果就是x。 **示例**: 数组[0,1,3],n=3 - 0~3的异或:0^1^2^3 - 再与数组[0,1,3]异或:0^1^2^3 ^ 0^1^3 = (0^0)^(1^1)^(3^3)^2 = 0^0^0^2 = 2 **代码**(Java): ```java public int missingNumber(int[] nums) { int res = 0; // 先与0~n(注意:n为数组长度,因为字范围0~n,而数组有n个元素,所以n=nums.length) for (int i = 0; i <= nums.length; i++) { // 注意这里i从0到n(包括n) res ^= i; } for (int num : nums) { res ^= num; } return res; } ``` **时间复杂度**:$O(n)$ **空间复杂度**:$O(1)$ ### 方法4:学求和法 **思路**: 计算0到n(共n+1个数)的等差列和,然后减去数组中所有的和,差值即为缺失字。 **公式**: 总和 = $n \times (n+1) / 2$ 数组和 = $\sum_{i=0}^{n-1} nums[i]$ 缺失字 = 总和 - 数组和 **示例**: 数组[0,1,3],n=3(数组长度) - 总和 = 3*4/2 = 6 - 数组和 = 0+1+3 = 4 - 缺失字 = 6-4 = 2 **时间复杂度**:$O(n)$ **空间复杂度**:$O(1)$ ### 总结 以上给出了四种方法,各有优劣: - 如果数组无序且空间允许,可以使用辅助数组法。 - 如果数组有序,二分查找法效率更高(对时间)。 - 异或法和学求和法都是线性时间,空间复杂度为常学求和法需要注意整溢出问题(当n很大时),而异或法不存在溢出问题。 用户可以根据具体场景选择合适的方法。 §§相关问题§§ 1. 如果数组中有多个缺失字,如何找出所有缺失字? 2. 在二分查找法中,如果数组无序,应该如何处理? 3. 学求和法在什么情况下可能会失效?如何避免? 4. 异或法能否扩展到找出多个缺失字的情况? </think>### 查找0到n中缺失字的方法 以下是四种高效且常用的方法,根据引用内容整理: #### 1. **辅助数组法**(适合无序数组)[^1] **思路**: 创建一个长度为 $n+1$ 的辅助数组 $b$(初始值为0)。遍历原数组 $a$,将每个元素 $a[i]$ 作为下标存入 $b$ 数组:$b[a[i]] \gets a[i]$。最终 $b$ 中值为0的索引即为缺失字。 **伪代码**: ```python def find_missing(nums, n): b = [0] * (n+1) # 创建辅助数组 for num in nums: b[num] = num # 标记存在的字 for i in range(n+1): if b[i] == 0: # 值为0的位置即缺失字 return i ``` **复杂度**: - 时间复杂度:$O(n)$ - 空间复杂度:$O(n)$ **示例**: 输入 $[0,1,3], n=3$ → 缺失字 $2$($b[2]=0$)。 --- #### 2. **二分查找法**(适合有序数组)[^2][^3] **思路**: 利用数组有序的特性,比较中间值 $nums[mid]$ 与下标 $mid$ 的关系: - 若 $nums[mid] = mid$,说明左侧无缺失,向右查找($l \gets mid+1$) - 若 $nums[mid] > mid$,说明左侧有缺失,向左查找($r \gets mid-1$) 缺失字为最终左边界 $l$。 **伪代码**(Java实现): ```java int missingNumber(int[] nums) { int l = 0, r = nums.length - 1; while (l <= r) { int mid = l + (r - l) / 2; if (nums[mid] == mid) l = mid + 1; else r = mid - 1; } return l; // 缺失字 } ``` **复杂度**: - 时间复杂度:$O(\log n)$ - 空间复杂度:$O(1)$ **示例**: 输入 $[0,1,3], n=3$ → $mid=1$ 时 $nums[1]=1$,向右查找;$mid=2$ 时 $nums[2]=3>2$,向左查找;返回 $l=2$。 --- #### 3. **异或法**(避免溢出)[^4] **思路**: 利用异或性质 $a \oplus a=0$ 和 $a \oplus 0=a$: 1. 计算 $0$ 到 $n$ 所有的异或:$xor = 0 \oplus 1 \oplus \cdots \oplus n$ 2. 再与数组所有元素异或:$xor = xor \oplus nums[0] \oplus \cdots \oplus nums[n-1]$ 3. 最终结果即缺失字(其他字异或后抵消)。 **伪代码**: ```python def missingNumber(nums, n): xor = 0 for i in range(1, n+1): # 计算 0~n 的异或(含缺失) xor ^= i for num in nums: # 抵消存在的字 xor ^= num return xor ``` **复杂度**: - 时间复杂度:$O(n)$ - 空间复杂度:$O(1)$ **示例**: 输入 $[0,1,3], n=3$ → $(0 \oplus 1 \oplus 2 \oplus 3) \oplus (0 \oplus 1 \oplus 3) = 2$。 --- #### 4. **学求和法**(直观但需防溢出) **思路**: 计算 $0$ 到 $n$ 的和(等差列公式),减去数组元素总和: $$ \text{缺失字} = \sum_{i=0}^{n} i - \sum_{k \in \text{nums}} k = \frac{n(n+1)}{2} - \sum \text{nums} $$ **伪代码**: ```python def missingNumber(nums, n): total = n * (n + 1) // 2 # 0~n 的总和 array_sum = sum(nums) # 数组实际总和 return total - array_sum ``` **复杂度**: - 时间复杂度:$O(n)$ - 空间复杂度:$O(1)$ **注意**:当 $n$ 较大时可能整溢出,异或法更安全。 --- ### 方法选择建议 | **场景** | **推荐方法** | **原因** | |------------------------|---------------|-----------------------------| | 数组无序 | 辅助数组法 | 简单直观,无额外要求 | | 数组有序 | 二分查找法 | 时间效率最优($O(\log n)$) | | 避免整溢出 | 异或法 | 安全高效 | | 小规模据 | 学求和法 | 代码最简洁 | > 例如二分查找法利用了数组有序性,通过比较 $nums[mid]$ 与 $mid$ 的关系逐步缩小搜索范围[^2][^3];异或法则通过位运算规避了求和法的溢出风险[^4]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值