Commit f1048508 authored by 程序员吴师兄's avatar 程序员吴师兄
Browse files

整理文件

parent c3aa5c59
# 【数组中超过一半的数字】三种解法,最后一个解法太牛逼了!
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
今天分享的题目来源于 LeetCode 上第 169 号问题:求众数(求数组中超过一半的数字)。题目难度为 Easy,目前通过率为 45.8% 。
最后一种解法 **Cool** !!!
# 题目描述
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在众数。
**示例 1:**
```
输入: [3,2,3]
输出: 3
```
**示例 2:**
```
输入: [2,2,1,1,1,2,2]
输出: 2
```
# 题目解析
题目意思很好理解:给你一个数组,里面有一个数字出现的次数超过了一半,你要找到这个数字并返回。
## 解法一:暴力解法
遍历整个数组,同时统计每个数字出现的次数。
最后将出现次数大于一半的元素返回即可。
### 动画描述
![](../Animation/Animation.gif)
### **代码实现**
```java
class Solution {
public int majorityElement(int[] nums) {
int majorityCount = nums.length/2;
for (int num : nums) {
int count = 0;
for (int elem : nums) {
if (elem == num) {
count += 1;
}
}
if (count > majorityCount) {
return num;
}
}
}
}
```
### 复杂度分析
**时间复杂度**:O(n<sup>2</sup>)
暴力解法包含两重嵌套的 for 循环,每一层 n 次迭代,因此时间复杂度为 O(n<sup>2</sup>) 。
**空间复杂度**:O(1)
暴力解法没有分配任何与输入规模成比例的额外的空间,因此空间复杂度为 O(1)。
## 解法二:哈希表法
这个问题可以视为查找问题,对于查找问题往往可以使用时间复杂度为 O(1) 的 **哈希表**,通过以空间换时间的方式进行优化。
直接遍历整个 **数组** ,将每一个数字(num)与它出现的次数(count)存放在 **哈希表** 中,同时判断该数字出现次数是否是最大的,动态更新 maxCount,最后输出 maxNum。
### 动画描述
![](../Animation/Animation2.gif)
### 代码实现
```java
class Solution {
public int majorityElement(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
// maxNum 表示元素,maxCount 表示元素出现的次数
int maxNum = 0, maxCount = 0;
for (int num: nums) {
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count);
if (count > maxCount) {
maxCount = count;
maxNum = num;
}
}
return maxNum;
}
}
```
### 复杂度分析
**时间复杂度**:O(n)
总共有一个循环,里面哈希表的插入是常数时间的,因此时间复杂度为 O(n)。
**空间复杂度**:O(n)
哈希表占用了额外的空间 O(n),因此空间复杂度为 O(n)。
## 解法三:摩尔投票法
再来回顾一下题目:寻找数组中超过一半的数字,这意味着数组中**其他数字出现次数的总和都是比不上这个数字出现的次数**
即如果把 该众数记为 `+1` ,把其他数记为 `−1` ,将它们全部加起来,和是大于 0 的。
所以可以这样操作:
* 设置两个变量 candidate 和 count,**candidate** 用来保存数组中遍历到的某个数字,**count** 表示当前数字的出现次数,一开始 **candidate** 保存为数组中的第一个数字,**count** 为 1
* 遍历整个数组
* 如果数字与之前 **candidate** 保存的数字相同,则 **count** 加 1
* 如果数字与之前 **candidate** 保存的数字不同,则 **count** 减 1
* 如果出现次数 **count** 变为 0 ,**candidate** 进行变化,保存为当前遍历的那个数字,并且同时把 **count** 重置为 1
* 遍历完数组中的所有数字即可得到结果
### 动画描述
![](../Animation/Animation3.gif)
### 代码实现
```java
class Solution {
public int majorityElement(int[] nums) {
int candidate = nums[0], count = 1;
for (int i = 1; i < nums.length; ++i) {
if (count == 0) {
candidate = nums[i];
count = 1;
} else if (nums[i] == candidate) {
count++;
} else{
count--;
}
}
return candidate;
}
}
```
### 复杂度分析
**时间复杂度**:O(n)
总共只有一个循环,因此时间复杂度为 O(n)。
**空间复杂度**:O(1)
只需要常数级别的额外空间,因此空间复杂度为 O(1)。
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode第 172 号问题:阶乘后的零
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 172 号问题:阶乘后的零。题目难度为 Easy,目前通过率为 38.0% 。
### 题目描述
给定一个整数 *n*,返回 *n*! 结果尾数中零的数量。
**示例 1:**
```
输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。
```
**示例 2:**
```
输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.
```
**说明:** 你算法的时间复杂度应为 *O*(log *n*) 。
### 题目解析
题目很好理解,数阶乘后的数字末尾有多少个零。
最简单粗暴的方法就是先乘完再说,然后一个一个数。
事实上,你在使用暴力破解法的过程中就能发现规律: **这 9 个数字中只有 2(它的倍数) 与 5 (它的倍数)相乘才有 0 出现**
所以,现在问题就变成了这个阶乘数中能配 **多少对 2 与 5**
举个复杂点的例子:
` 10! = 【 2 *( 2 * 2 )* 5 *( 2 * 3 )*( 2 * 2 * 2 )*( 2 * 5)】`
在 10!这个阶乘数中可以匹配两对 2 * 5 ,所以10!末尾有 2 个 0。
可以发现,一个数字进行拆分后 2 的个数肯定是大于 5 的个数的,所以能匹配多少对取决于 5 的个数。(好比现在男女比例悬殊,最多能有多少对异性情侣取决于女生的多少)。
那么问题又变成了 **统计阶乘数里有多少个 5 这个因子**
需要注意的是,像 25,125 这样的不只含有一个 5 的数字的情况需要考虑进去。
比如 `n = 15`。那么在 `15!` 中 有 `3``5` (来自其中的`5`, `10`, `15`), 所以计算 `n/5` 就可以 。
但是比如 `n=25`,依旧计算 `n/5` ,可以得到 `5``5`,分别来自其中的`5, 10, 15, 20, 25`,但是在 `25` 中其实是包含 `2 ``5` 的,这一点需要注意。
所以除了计算 `n/5` , 还要计算 `n/5/5 , n/5/5/5 , n/5/5/5/5 , ..., n/5/5/5,,,/5`直到商为0,然后求和即可。
### 代码实现
```java
public class Solution {
public int trailingZeroes(int n) {
return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
}
}
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 201 号问题:数字范围按位与
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 201 号问题:数字范围按位与。题目难度为 Medium,目前通过率为 39.1% 。
### 题目描述
给定范围 [m, n],其中 0 <= m <= n <= 2147483647,返回此范围内所有数字的按位与(包含 m, n 两端点)。
**示例 1:**
```
输入: [5,7]
输出: 4
```
**示例 2:**
```
输入: [0,1]
输出: 0
```
### 题目解析
以 [ 26 ,30] 为例。
首先,将 [ 26 , 30 ] 的范围数字用二进制表示出来:
**11**010  **11**011  **11**100  **11**101  **11**110
而输出 24 的二进制是 11000 。
可以发现,只要找到二进制的 **左边公共部分** 即可。
所以,可以先建立一个 32 位都是 1 的 mask,然后每次向左移一位,比较 m 和 n 是否相同,不同再继续左移一位,直至相同,然后把 m 和 mask 相与就是最终结果。
### 动画描述
暂无
### 代码实现
```c++
class Solution {
public:
int rangeBitwiseAnd(int m, int n) {
unsigned int d = INT_MAX;
while ((m & d) != (n & d)) {
d <<= 1;
}
return m & d;
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 203 号问题:移除链表元素
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 203 号问题:移除链表元素。题目难度为 Easy,目前通过率为 55.8% 。
### 题目描述
删除链表中等于给定值 **val** 的所有节点。
**示例:**
```
输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5
```
### 题目解析
主要考察了基本的链表遍历和设置指针的知识点。
定义一个虚拟头节点`dummyHead `,遍历查看原链表,遇到与给定值相同的元素,将该元素的前后两个节点连接起来,然后删除该元素即可。
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
#### 代码一
```
// 203. Remove Linked List Elements
// https://leetcode.com/problems/remove-linked-list-elements/description/
// 使用虚拟头结点
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
// 创建虚拟头结点
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
ListNode* cur = dummyHead;
while(cur->next != NULL){
if(cur->next->val == val){
ListNode* delNode = cur->next;
cur->next = delNode->next;
delete delNode;
}
else
cur = cur->next;
}
ListNode* retNode = dummyHead->next;
delete dummyHead;
return retNode;
}
};
```
#### 代码二
用递归来解。
通过递归调用到链表末尾,然后回来,需要删的元素,将链表next指针指向下一个元素即可。
```
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
if (!head) return NULL;
head->next = removeElements(head->next, val);
return head->val == val ? head->next : head;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 206 号问题:反转链表
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 206 号问题:反转链表。题目难度为 Easy,目前通过率为 45.8% 。
### 题目描述
反转一个单链表。
**示例:**
```
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
```
**进阶:**
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
### 题目解析
设置三个节点`pre``cur``next`
- (1)每次查看`cur`节点是否为`NULL`,如果是,则结束循环,获得结果
- (2)如果`cur`节点不是为`NULL`,则先设置临时变量`next``cur`的下一个节点
- (3)让`cur`的下一个节点变成指向`pre`,而后`pre`移动`cur``cur`移动到`next`
- (4)重复(1)(2)(3)
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* pre = NULL;
ListNode* cur = head;
while(cur != NULL){
ListNode* next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 209 号问题:长度最小的子数组
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 209 号问题:长度最小的子数组。题目难度为 Medium,目前通过率为 25.8% 。
### 题目描述
给定一个含有 **n** 个正整数的数组和一个正整数 **s ,**找出该数组中满足其和 **≥ s** 的长度最小的连续子数组**。**如果不存在符合条件的连续子数组,返回 0。
**示例:**
```
输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的连续子数组。
```
**进阶:**
如果你已经完成了*O*(*n*) 时间复杂度的解法, 请尝试 *O*(*n* log *n*) 时间复杂度的解法。
### 题目解析
定义两个指针 left 和 right ,分别记录子数组的左右的边界位置。
* (1)让 right 向右移,直到子数组和大于等于给定值或者 right 达到数组末尾;
* (2)更新最短距离,将 left 像右移一位,sum 减去移去的值;
* (3)重复(1)(2)步骤,直到 right 到达末尾,且 left 到达临界位置
### 动画描述
![](../Animation/Animation.gif)
设置滑动窗口的长度为 0 ,位于数轴的最左端。
##### 1 .滑动窗口右端 R 开始移动,直到区间满足给定的条件,也就是和大于 7 ,此时停止于第三个元素 2,当前的最优长度为 4
![图 1](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/lo41y.jpg)
##### 2. 滑动窗口左端 L 开始移动,缩小滑动窗口的大小,停止于第一个元素 3,此时区间和为 6,使得区间和不满足给定的条件(此时不大于 7)
![图片 2](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/j7qnc.jpg)
#### 3. 滑动窗口右端 R 继续移动,停止于第四个元素 4,此时和位 10 ,但最优长度仍然为 4
![图片 3](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/q8dxy.jpg)
### 代码实现
```
// 滑动窗口的思路
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int l= 0,r = -1; // nums[l...r]为我们的滑动窗口
int sum = 0;
int result = nums.length + 1;
while (l < nums.length){ // 窗口的左边界在数组范围内,则循环继续
if( r+1 <nums.length && sum < s){
r++;
sum += nums[r];
}else { // r已经到头 或者 sum >= s
sum -= nums[l];
l++;
}
if(sum >= s){
result = (r-l+1) < result ? (r-l+1) : result ;
}
}
if(result==nums.length+1){
return 0;
}
return result;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 219 号问题:存在重复元素 II
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 219 号问题:存在重复元素 II。题目难度为 Easy,目前通过率为 34.8% 。
### 题目描述
给定一个整数数组和一个整数 *k*,判断数组中是否存在两个不同的索引 *i**j*,使得 **nums [i] = nums [j]**,并且 *i**j* 的差的绝对值最大为 *k*
**示例 1:**
```
输入: nums = [1,2,3,1], k = 3
输出: true
```
**示例 2:**
```
输入: nums = [1,0,1,1], k = 1
输出: true
```
**示例 3:**
```
输入: nums = [1,2,3,1,2,3], k = 2
输出: false
```
### 题目解析
考虑用滑动窗口与查找表来解决。
* 设置查找表`record`,用来保存每次遍历时插入的元素,`record `的最大长度为`k `
* 遍历数组`nums`,每次遍历的时候在`record `查找是否存在相同的元素,如果存在则返回`true`,遍历结束
* 如果此次遍历在`record `未查找到,则将该元素插入到`record `中,而后查看`record `的长度是否为`k + 1`
* 如果此时`record `的长度是否为`k + 1`,则删减`record`的元素,该元素的值为`nums[i - k]`
* 如果遍历完整个数组`nums`未查找到则返回`false`
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
// 219. Contains Duplicate II
// https://leetcode.com/problems/contains-duplicate-ii/description/
// 时间复杂度: O(n)
// 空间复杂度: O(k)
class Solution {
public:
bool containsNearbyDuplicate(vector<int>& nums, int k) {
if(nums.size() <= 1) return false;
if(k <= 0) return false;
unordered_set<int> record;
for(int i = 0 ; i < nums.size() ; i ++){
if(record.find(nums[i]) != record.end()){
return true;
}
record.insert(nums[i]);
// 保持record中最多有k个元素
// 因为在下一次循环中会添加一个新元素,使得总共考虑k+1个元素
if(record.size() == k + 1){
record.erase(nums[i - k]);
}
}
return false;
}
};
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 231 号问题:2 的幂
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 231 号问题:2 的幂。题目难度为 Easy,目前通过率为 45.6% 。
### 题目描述
给定一个整数,编写一个函数来判断它是否是 2 的幂次方。
**示例 1:**
```
输入: 1
输出: true
解释: 2^0 = 1
```
**示例 2:**
```
输入: 16
输出: true
解释: 2^4 = 16
```
**示例 3:**
```
输入: 218
输出: false
```
### 题目解析
首先,先来分析一下 2 的次方数的二进制写法:
![表格](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/3wdpd.jpg)
仔细观察,可以看出 2 的次方数都只有一个 1 ,剩下的都是 0 。根据这个特点,只需要每次判断最低位是否为 1 ,然后向右移位,最后统计 1 的个数即可判断是否是 2 的次方数。
代码很简单:
```c++
class Solution {
public:
bool isPowerOfTwo(int n) {
int cnt = 0;
while (n > 0) {
cnt += (n & 1);
n >>= 1;
}
return cnt == 1;
}
};
```
该题还有一种巧妙的解法。再观察上面的表格,如果一个数是 2 的次方数的话,那么它的二进数必然是最高位为1,其它都为 0 ,那么如果此时我们减 1 的话,则最高位会降一位,其余为 0 的位现在都为变为 1,那么我们把两数相与,就会得到 0。
比如 2 的 3 次方为 8,二进制位 1000 ,那么 ` 8 - 1 = 7`,其中 7 的二进制位 0111。
### 图片描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/1w9lq.jpg)
### 代码实现
利用这个性质,只需一行代码就可以搞定。
```c++
class Solution {
public:
bool isPowerOfTwo(int n) {
return (n > 0) && (!(n & (n - 1)));
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 237 号问题:删除链表中的节点
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 237 号问题:删除链表中的节点。题目难度为 Easy,目前通过率为 72.6% 。
### 题目描述
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/uv8bw.png)
**示例 1:**
```
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
```
**示例 2:**
```
输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
```
**说明:**
- 链表至少包含两个节点。
- 链表中所有节点的值都是唯一的。
- 给定的节点为非末尾节点并且一定是链表中的一个有效节点。
- 不要从你的函数中返回任何结果。
### 题目解析
此题注意的点是没有给我们链表的起点,只给我们了一个要删的节点,与以往处理的情况稍许不同。
**这道题的处理方法是先把当前节点的值用下一个节点的值覆盖,然后我们删除下一个节点即可**
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public:
void deleteNode(ListNode* node) {
if (node == NULL) return;
if (node->next == NULL) {
delete node;
node = NULL;
return;
}
node->val = node->next->val;
ListNode *delNode = node->next;
node->next = delNode->next;
delete delNode;
}
};
```
![](../../Pictures/qrcode.jpg)
# LeetCode 第 239 号问题:滑动窗口最大值
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 239 号问题:滑动窗口最大值。题目难度为 Hard,目前通过率为 40.5% 。
### 题目描述
给定一个数组 *nums*,有一个大小为 *k* 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口 *k* 内的数字。滑动窗口每次只向右移动一位。
返回滑动窗口最大值。
**示例:**
```
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
--------------- -----
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
```
**注意:**
你可以假设 *k* 总是有效的,1 ≤ k ≤ 输入数组的大小,且输入数组不为空。
**进阶:**
你能在线性时间复杂度内解决此题吗?
### 题目解析
利用一个 **双端队列**,在队列中存储元素在数组中的位置, 并且维持队列的严格递减,,也就说维持队首元素是 **最大的 **,当遍历到一个新元素时, 如果队列里有比当前元素小的,就将其移除队列,以保证队列的递减。当队列元素位置之差大于 k,就将队首元素移除。
### 补充:什么是双端队列(Dqueue)
Deque 的含义是 “double ended queue”,即双端队列,它具有队列和栈的性质的数据结构。顾名思义,它是一种前端与后端都支持插入和删除操作的队列。
Deque 继承自 Queue(队列),它的直接实现有 ArrayDeque、LinkedList 等。
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
//有点坑,题目里都说了数组不为空,且 k > 0。但是看了一下,测试用例里面还是有nums = [], k = 0,所以只好加上这个判断
if (nums == null || nums.length < k || k == 0) return new int[0];
int[] res = new int[nums.length - k + 1];
//双端队列
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < nums.length; i++) {
//在尾部添加元素,并保证左边元素都比尾部大
while (!deque.isEmpty() && nums[deque.getLast()] < nums[i]) {
deque.removeLast();
}
deque.addLast(i);
//在头部移除元素
if (deque.getFirst() == i - k) {
deque.removeFirst();
}
//输出结果
if (i >= k - 1) {
res[i - k + 1] = nums[deque.getFirst()];
}
}
return res;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
# LeetCode 第 268 号问题:缺失数字
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
今天分享一道很简单的算法题。
题目来源于 LeetCode 上第 268 号问题:缺失数字。题目难度为 Easy,目前通过率为 50.2% 。
## 题目描述
给定一个包含 `0, 1, 2, ..., n`*n* 个数的序列,找出 0 .. *n* 中没有出现在序列中的那个数。
**说明:**
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/i47fw.png)
## 题目解析
这道题目有三种解法。
### 解法一:异或法
和之前那道 **只出现一次的数字** 很类似:
> 只出现一次的数字: 给定一个**非空**整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
如果我们补充一个完整的数组和原数组进行组合,那所求解的问题就变成了 **只出现一次的数字**
将少了一个数的数组与 0 到 n 之间完整的那个数组进行异或处理,因为相同的数字异或会变为了 0 ,那么全部数字异或后,剩下的就是少了的那个数字。
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/el8zt.png)
#### 代码实现1
```java
class Solution {
public int missingNumber(int[] nums) {
int res = 0;
int i = 0;
//注意数组越界情况
for (; i < nums.length;i++){
// i 表示完整数组中的数字,与原数组中的数字 nums[i] 进行异或,再与保存的结果异或
res = res^i^nums[i];
}
//最后需要与循环中无法使用到的那个最大的数异或
return res^i;
}
}
```
#### 代码实现2
```java
class Solution {
public int missingNumber(int[] nums) {
int res = nums.length;
for (int i = 0; i < nums.length; ++i){
res ^= nums[i];
res ^= i;
}
return res;
}
}
```
### 解法二:求和法
- 求出 0 到 n 之间所有的数字之和
- 遍历数组计算出原始数组中数字的累积和
- 两和相减,差值就是丢失的那个数字
![](../Animation/Animation.gif)
```java
//小吴之前担心会数据溢出,不过估计这题考察的不是这个,所以测试用例没写这种吧,还是能 AC 的
class Solution {
public int missingNumber(int[] nums) {
int n = nums.length;
int sum = (n+0)*(n+1)/2;
for (int i=0; i<n; i++){
sum -= nums[i];
}
return sum;
}
}
```
### 解法三:二分法
将数组进行排序后,利用二分查找的方法来找到缺少的数字,注意搜索的范围为 0 到 n 。
- 首先对数组进行排序
- 用元素值和下标值之间做对比,如果元素值大于下标值,则说明缺失的数字在左边,此时将 right 赋为 mid ,反之则将 left 赋为 mid + 1 。
> 注:由于一开始进行了排序操作,因此使用二分法的性能是不如上面两种方法。
```java
public class Solution {
public int missingNumber(int[] nums) {
Arrays.sort(nums);
int left = 0;
int right = nums.length;
while (left < right){
int mid = (left + right) / 2;
if (nums[mid] > mid){
right = mid;
}else{
left = mid + 1;
}
}
return left;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment