整理文件

This commit is contained in:
程序员吴师兄 2020-04-17 15:39:41 +08:00
parent c3aa5c59ee
commit f104850874
65 changed files with 3185 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

View File

@ -0,0 +1,91 @@
# LeetCode 131 号问题分割回文串
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 131 号问题分割回文串题目难度为 Medium目前通过率为 45.8%
### 题目描述
给定一个字符串 *s* *s* 分割成一些子串使每个子串都是回文串
返回 *s* 所有可能的分割方案
**示例:**
```yaml
输入: "aab"
输出:
[
["aa","b"],
["a","a","b"]
]
```
###
### 题目解析
首先对于一个字符串的分割肯定需要将所有分割情况都遍历完毕才能判断是不是回文数不能因为 **abba** 是回文串就认为它的所有子串都是回文的
既然需要将所有的分割方法都找出来那么肯定需要用到DFS深度优先搜索或者BFS广度优先搜索
在分割的过程中对于每一个字符串而言都可以分为两部分左边一个回文串加右边一个子串比如 "abc" 可分为 "a" + "bc" 然后对"bc"分割仍然是同样的方法分为"b"+"c"
在处理的时候去优先寻找更短的回文串然后回溯找稍微长一些的回文串分割方法不断回溯分割直到找到所有的分割方法
举个🌰分割"aac"
1. 分割为 a + ac
2. 分割为 a + a + c分割后得到一组结果再回溯到 a + ac
3. a + ac ac 不是回文串继续回溯回溯到 aac
4. 分割为稍长的回文串分割为 aa + c 分割完成得到一组结果再回溯到 aac
5. aac 不是回文串搜索结束
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```java
class Solution {
List<List<String>> res = new ArrayList<>();
public List<List<String>> partition(String s) {
if(s==null||s.length()==0)
return res;
dfs(s,new ArrayList<String>(),0);
return res;
}
public void dfs(String s,List<String> remain,int left){
if(left==s.length()){ //判断终止条件
res.add(new ArrayList<String>(remain)); //添加到结果中
return;
}
for(int right=left;right<s.length();right++){ //从left开始依次判断left->right是不是回文串
if(isPalindroom(s,left,right)){ //判断是否是回文串
remain.add(s.substring(left,right+1)); //添加到当前回文串到list中
dfs(s,remain,right+1); //从right+1开始继续递归寻找回文串
remain.remove(remain.size()-1); //回溯从而寻找更长的回文串
}
}
}
/**
* 判断是否是回文串
*/
public boolean isPalindroom(String s,int left,int right){
while(left<right&&s.charAt(left)==s.charAt(right)){
left++;
right--;
}
return left>=right;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB

View File

@ -0,0 +1,86 @@
# LeetCode 136 号问题只出现一次的数字
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 136 号问题只出现一次的数字题目难度为 Easy目前通过率为 66.8%
### 题目描述
给定一个**非空**整数数组除了某个元素只出现一次以外其余每个元素均出现两次找出那个只出现了一次的元素
**说明**
你的算法应该具有线性时间复杂度 你可以不使用额外空间来实现吗
**示例 1:**
```
输入: [2,2,1]
输出: 1
```
**示例 2:**
```
输入: [4,1,2,1,2]
输出: 4
```
### 题目解析
根据题目描述由于加上了时间复杂度必须是 O(n) 并且空间复杂度为 O(1) 的条件因此不能用排序方法也不能使用 map 数据结构
程序员小吴想了一下午没想出来答案是使用 **位操作Bit Operation** 来解此题
将所有元素做异或运算即a[1] a[2] a[3] a[n]所得的结果就是那个只出现一次的数字时间复杂度为O(n)
### 异或
异或运算A B的真值表如下
| A | B | |
| :--- | :--: | ---: |
| F | F | F |
| F | T | T |
| T | F | T |
| T | T | F |
### 动画演示
![](../Animation/136.gif)
### 进阶版
有一个 n 个元素的数组除了两个数只出现一次外其余元素都出现两次让你找出这两个只出现一次的数分别是几要求时间复杂度为 O(n) 且再开辟的内存空间固定( n 无关)
#### 示例 :
输入: [1,2,2,1,3,4]
输出: [3,4]
### 题目再解析
根据前面找一个不同数的思路算法在这里把所有元素都异或那么得到的结果就是那两个只出现一次的元素异或的结果
然后因为这两个只出现一次的元素一定是不相同的所以这两个元素的二进制形式肯定至少有某一位是不同的即一个为 0 另一个为 1 现在需要找到这一位
根据异或的性质 `任何一个数字异或它自己都等于 0 `得到这个数字二进制形式中任意一个为 1 的位都是我们要找的那一位
再然后以这一位是 1 还是 0 为标准将数组的 n 个元素分成两部分
- 将这一位为 0 的所有元素做异或得出的数就是只出现一次的数中的一个
- 将这一位为 1 的所有元素做异或得出的数就是只出现一次的数中的另一个
这样就解出题目忽略寻找不同位的过程总共遍历数组两次时间复杂度为O(n)
### 动画再演示
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/5uz1n.gif)
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,77 @@
# LeetCode 138 号问题复制带随机指针的链表
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 138 号问题复制带随机指针的链表题目难度为 Medium目前通过率为 40.5%
### 题目描述
给定一个链表每个节点包含一个额外增加的随机指针该指针可以指向链表中的任何节点或空节点
要求返回这个链表的**深拷贝**
**示例**
```
输入
{"$id":"1","next":{"$id":"2","next":null,"random":{"$ref":"2"},"val":2},"random":{"$ref":"2"},"val":1}
解释
节点 1 的值是 1它的下一个指针和随机指针都指向节点 2
节点 2 的值是 2它的下一个指针指向 null随机指针指向它自己
```
### 题目解析
1. 在原链表的每个节点后面拷贝出一个新的节点
2. 依次给新的节点的随机指针赋值而且这个赋值非常容易 cur->next->random = cur->random->next
3. 断开链表可得到深度拷贝后的新链表
之所以说这个方法比较巧妙是因为相较于一般的解法如使用 Hash map 来处理上面这个解法 **不需要占用额外的空间**
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
我发现带指针的题目使用 C++ 版本更容易描述所以下面的代码实现是 C++ 版本
```c++
class Solution {
public:
RandomListNode *copyRandomList(RandomListNode *head) {
if (!head) return NULL;
RandomListNode *cur = head;
while (cur) {
RandomListNode *node = new RandomListNode(cur->label);
node->next = cur->next;
cur->next = node;
cur = node->next;
}
cur = head;
while (cur) {
if (cur->random) {
cur->next->random = cur->random->next;
}
cur = cur->next->next;
}
cur = head;
RandomListNode *res = head->next;
while (cur) {
RandomListNode *tmp = cur->next;
cur->next = tmp->next;
if(tmp->next) tmp->next = tmp->next->next;
cur = cur->next;
}
return res;
}
};
```
![](../../Pictures/qrcode.jpg)

View File

@ -0,0 +1,68 @@
# LeetCode 139 号问题单词拆分
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 139 号问题单词拆分
### 题目描述
给定一个**非空**字符串 *s* 和一个包含**非空**单词列表的字典 *wordDict*判定 *s* 是否可以被空格拆分为一个或多个在字典中出现的单词
**说明**
- 拆分时可以重复使用字典中的单词
- 你可以假设字典中没有重复的单词
### 题目解析
**分割回文串** 有些类似都是拆分但是如果此题采取 深度优先搜索 的方法来解决的话答案是超时的不信的同学可以试一下~
为什么会超时呢
因为使用 深度优先搜索 会重复的计算了有些位的可拆分情况这种情况的优化肯定是需要 动态规划 来处理的
如果不知道动态规划的可以看一下小吴之前的万字长文比较详细的介绍了动态规划的概念
在这里只需要去定义一个数组 boolean[] memo其中第 i memo[i] 表示待拆分字符串从第 0 位到第 i-1 位是否可以被成功地拆分
然后分别计算每一位是否可以被成功地拆分
### 动画描述
暂无~
### 代码实现
```java
class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
int n = s.length();
int max_length=0;
for(String temp:wordDict){
max_length = temp.length() > max_length ? temp.length() : max_length;
}
// memo[i] 表示 s 中以 i - 1 结尾的字符串是否可被 wordDict 拆分
boolean[] memo = new boolean[n + 1];
memo[0] = true;
for (int i = 1; i <= n; i++) {
for (int j = i-1; j >= 0 && max_length >= i - j; j--) {
if (memo[j] && wordDict.contains(s.substring(j, i))) {
memo[i] = true;
break;
}
}
}
return memo[n];
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -0,0 +1,83 @@
# 使用快慢指针求解环形链表so easy
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
今天分享的题目来源于 LeetCode 上第 141 号问题环形链表题目难度为 Easy目前通过率为 40.4%
使用快慢指针的方式去求解 **so easy**
### 题目描述
给定一个链表判断链表中是否有环
为了表示给定链表中的环我们使用整数 `pos` 来表示链表尾连接到链表中的位置索引从 0 开始 如果 `pos` `-1`则在该链表中没有环
**示例 1**
```
输入head = [3,2,0,-4], pos = 1
输出true
解释链表中有一个环其尾部连接到第二个节点
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/vweoq.png)
**示例 2**
```
输入head = [1,2], pos = 0
输出true
解释链表中有一个环其尾部连接到第一个节点
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/kxbrz.png)
**示例 3**
```
输入head = [1], pos = -1
输出false
解释链表中没有环
```
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/w3vsg.png)
**进阶**
你能用 O(1)常量内存解决此问题吗
### 题目解析
这道题是快慢指针的**经典应用**
设置两个指针一个每次走一步的**慢指针**和一个每次走两步的**快指针**
* 如果不含有环跑得快的那个指针最终会遇到 null说明链表不含环
* 如果含有环快指针会超慢指针一圈和慢指针相遇说明链表含有环
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```java
//author:程序员小吴
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 369 KiB

View File

@ -0,0 +1,75 @@
# LeetCode 144 号问题二叉树的前序遍历
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 144 号问题二叉树的前序遍历题目难度为 Medium目前通过率为 59.8%
### 题目描述
给定一个二叉树返回它的 *前序* 遍历
**示例:**
```
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
```
**进阶:** 递归算法很简单你可以通过迭代算法完成吗
### 题目解析
**(Stack)**的思路来处理问题
前序遍历的顺序为**--**具体算法为
- 把根节点 push 到栈中
- 循环检测栈是否为空若不空则取出栈顶元素保存其值
- 看其右子节点是否存在若存在则 push 到栈中
- 看其左子节点若存在 push 到栈中
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
//非递归前序遍历需要借助栈
Stack<TreeNode> stack = new Stack<>();
List<Integer> list = new LinkedList<>();
//当树为空树时直接返回一个空list
if(root == null){
return list;
}
//第一步是将根节点压入栈中
stack.push(root);
//当栈不为空时出栈的元素插入list尾部
//当它的孩子不为空时将孩子压入栈一定是先压右孩子再压左孩子
while(!stack.isEmpty()){
//此处的root只是一个变量的复用
root = stack.pop();
list.add(root.val);
if(root.right != null) stack.push(root.right);
if(root.left != null) stack.push(root.left);
}
return list;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

View File

@ -0,0 +1,70 @@
# LeetCode 145 号问题二叉树的后序遍历
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 145 号问题二叉树的后序遍历题目难度为 Hard目前通过率为 25.8%
### 题目描述
给定一个二叉树返回它的 *后序* 遍历
**示例:**
```
输入: [1,null,2,3]
1
\
2
/
3
输出: [3,2,1]
```
**进阶:** 递归算法很简单你可以通过迭代算法完成吗
### 题目解析
**(Stack)**的思路来处理问题
后序遍历的顺序为**--**具体算法为
- 先将根结点压入栈然后定义一个辅助结点 head
- while 循环的条件是栈不为空
- 在循环中首先将栈顶结点t取出来
- 如果栈顶结点没有左右子结点或者其左子结点是 head或者其右子结点是 head 的情况下我们将栈顶结点值加入结果 res 并将栈顶元素移出栈然后将 head 指向栈顶元素
- 否则的话就看如果右子结点不为空将其加入栈
- 再看左子结点不为空的话就加入栈
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
public class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<Integer>();
if(root == null)
return res;
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
if(node.left != null) stack.push(node.left);//和传统先序遍历不一样先将左结点入栈
if(node.right != null) stack.push(node.right);//后将右结点入栈
res.add(0,node.val); //逆序添加结点值
}
return res;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

View File

@ -0,0 +1,97 @@
# LeetCode 146 号问题LRU缓存机制
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 146 号问题LRU缓存机制题目难度为 Hard目前通过率为 15.8%
### 题目描述
运用你所掌握的数据结构设计和实现一个 [LRU (最近最少使用) 缓存机制](https://baike.baidu.com/item/LRU)它应该支持以下操作 获取数据 `get` 写入数据 `put`
获取数据 `get(key)` - 如果密钥 (key) 存在于缓存中则获取密钥的值总是正数否则返回 -1
写入数据 `put(key, value)` - 如果密钥不存在则写入其数据值当缓存容量达到上限时它应该在写入新数据之前删除最近最少使用的数据值从而为新的数据值留出空间
**进阶:**
你是否可以在 **O(1)** 时间复杂度内完成这两种操作
**示例:**
```
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
```
### 题目解析
这道题是让我们实现一个 LRU 缓存器LRU是Least Recently Used的简写就是最近最少使用的意思
这个缓存器主要有两个成员函数get和put
其中 get 函数是通过输入 key 来获得 value如果成功获得后这对 (key, value) 升至缓存器中最常用的位置顶部如果 key 不存在则返回 -1
put 函数是插入一对新的 (key, value)如果原缓存器中有该 key则需要先删除掉原有的将新的插入到缓存器的顶部如果不存在则直接插入到顶部
若加入新的值后缓存器超过了容量则需要删掉一个最不常用的值也就是底部的值
具体实现时我们需要三个私有变量cap , l m其中 cap 是缓存器的容量大小l 是保存缓存器内容的列表m HashMap保存关键值 key 和缓存器各项的迭代器之间映射方便我们以 O(1) 的时间内找到目标项
然后我们再来看 get put 如何实现
其中get 相对简单些我们在 m 中查找给定的key若不存在直接返回 -1如果存在则将此项移到顶部
对于 put 我们也是现在 m 中查找给定的 key如果存在就删掉原有项并在顶部插入新来项然后判断是否溢出若溢出则删掉底部项(最不常用项)
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```c++
class LRUCache{
public:
LRUCache(int capacity) {
cap = capacity;
}
int get(int key) {
auto it = m.find(key);
if (it == m.end()) return -1;
l.splice(l.begin(), l, it->second);
return it->second->second;
}
void put(int key, int value) {
auto it = m.find(key);
if (it != m.end()) l.erase(it->second);
l.push_front(make_pair(key, value));
m[key] = l.begin();
if (m.size() > cap) {
int k = l.rbegin()->first;
l.pop_back();
m.erase(k);
}
}
private:
int cap;
list<pair<int, int>> l;
unordered_map<int, list<pair<int, int>>::iterator> m;
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 357 KiB

View File

@ -0,0 +1,101 @@
# LeetCode 150 号问题逆波兰表达式求值
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 150 号问题逆波兰表达式求值题目难度为 Medium目前通过率为 43.7%
### 题目描述
根据[逆波兰表示法](https://baike.baidu.com/item/%E9%80%86%E6%B3%A2%E5%85%B0%E5%BC%8F/128437)求表达式的值
有效的运算符包括 `+`, `-`, `*`, `/` 每个运算对象可以是整数也可以是另一个逆波兰表达式
**说明**
- 整数除法只保留整数部分
- 给定逆波兰表达式总是有效的换句话说表达式总会得出有效数值且不存在除数为 0 的情况
**示例 1**
```
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: ((2 + 1) * 3) = 9
```
**示例 2**
```
输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: (4 + (13 / 5)) = 6
```
**示例 3**
```
输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
```
### 题目解析
用数据结构``来解决这个问题
- 从前往后遍历数组
- 遇到数字则压入栈中
- 遇到符号则把栈顶的两个数字拿出来运算把结果再压入栈中
- 遍历完整个数组栈顶数字即为最终答案
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> nums;
stack<char> ops;
for(const string& s: tokens){
if(s == "+" || s == "-" || s == "*" || s == "/"){
int a = nums.top();
nums.pop();
int b = nums.top();
nums.pop();
if(s == "+"){
nums.push(b + a);
}else if(s == "-"){
nums.push(b - a);
} else if(s == "*"){
nums.push(b * a);
}else if(s == "/"){
nums.push(b / a);
}
}
else{
nums.push(atoi(s.c_str()));
}
}
return nums.top();
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -0,0 +1,69 @@
# LeetCode 167 号问题两数之和 II - 输入有序数组
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 167 号问题两数之和 II - 输入有序数组题目难度为 Easy目前通过率为 48.2%
### 题目描述
给定一个已按照**升序排列** 的有序数组找到两个数使得它们相加之和等于目标数
函数应该返回这两个下标值 index1 index2其中 index1 必须小于 index2**
**说明:**
- 返回的下标值index1 index2不是从零开始的
- 你可以假设每个输入只对应唯一的答案而且你不可以重复使用相同的元素
**示例:**
```
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 7 之和等于目标数 9 因此 index1 = 1, index2 = 2
```
### 题目解析
初始化左指针 left 指向数组起始初始化右指针 right 指向数组结尾
根据**已排序**这个特性
- 1如果 numbers[left] numbers[right] 的和 tmp 小于 target 说明应该增加 tmp 因此 left 右移指向一个较大的值
- 2如果 tmp大于 target 说明应该减小 tmp 因此 right 左移指向一个较小的值
- 3tmp 等于 target 则找到返回 left + 1 right + 1注意以 1 为起始下标
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
// 对撞指针
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int l = 0, r = numbers.size() - 1;
while(l < r){
if(numbers[l] + numbers[r] == target){
int res[2] = {l+1, r+1};
return vector<int>(res, res+2);
}
else if(numbers[l] + numbers[r] < target)
l ++;
else // numbers[l] + numbers[r] > target
r --;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

View File

@ -0,0 +1,171 @@
# 数组中超过一半的数字三种解法最后一个解法太牛逼了
> 本文首发于公众号图解面试算法 [图解 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)

View File

@ -0,0 +1,78 @@
# 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 这个因子**
需要注意的是 25125 这样的不只含有一个 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)

View File

@ -0,0 +1,60 @@
# 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

View File

@ -0,0 +1,85 @@
# 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 KiB

View File

@ -0,0 +1,58 @@
# 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重复123
### 动画描述
![](../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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

View File

@ -0,0 +1,96 @@
# 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重复12步骤直到 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 KiB

View File

@ -0,0 +1,85 @@
# 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)

View File

@ -0,0 +1,81 @@
# 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -0,0 +1,77 @@
# 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@ -0,0 +1,86 @@
# 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队列它的直接实现有 ArrayDequeLinkedList
### 动画描述
![](../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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

View File

@ -0,0 +1,126 @@
# 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -0,0 +1,118 @@
# LeetCode 279 号问题完全平方数
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 279 号问题完全平方数题目难度为 Medium目前通过率为 49.1%
### 题目描述
给定正整数 *n*找到若干个完全平方数比如 `1, 4, 9, 16, ...`使得它们的和等于 *n*你需要让组成和的完全平方数的个数最少
**示例 1:**
```
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
```
**示例 2:**
```
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
```
### 题目解析
这道题目很有意思
大部分文章给出的答案都是依托于一个定理**四平方定理**
四平方定理讲的就是任何一个正整数都可以表示成不超过四个整数的平方之和也就是说这道题的答案只有 12 34 这四种可能
同时还有一个非常重要的推论满足四数平方和定理的数n这里要满足由四个数构成小于四个不行必定满足 n = 4<sup>a</sup> * (8b + 7)
根据这个重要的推论来解决此题首先将输入的`n`迅速缩小然后再判断这个缩小后的数是否可以通过`两个平方数的和或一个平方数`组成不能的话我们返回`3`能的话我们返回`平方数的个数`
所以代码很简洁如下
```java
public int numSquares(int n) {
while (n % 4 == 0){
n /= 4;
}
if ( n % 8 == 7){
return 4;
}
int a = 0;
while ( (a * a) <= n){
int b = (int)Math.pow((n - a * a),0.5);
if(a * a + b * b == n) {
//如果可以 在这里返回
if(a != 0 && b != 0) {
return 2;
} else{
return 1;
}
}
a++;
}
return 3;
}
```
但因为本章是广度优先遍历的专栏因此再补充一个图的广度优先遍历的答案
使用广度优先搜索方法 n 依次减去比 n 小的所有平方数直至 n = 0 此时的层数即为最后的结果
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
import java.util.LinkedList;
import javafx.util.Pair;
class Solution {
public int numSquares(int n) {
if(n == 0)
return 0;
LinkedList<Pair<Integer, Integer>> queue = new LinkedList<Pair<Integer, Integer>>();
queue.addLast(new Pair<Integer, Integer>(n, 0));
boolean[] visited = new boolean[n+1];
visited[n] = true;
while(!queue.isEmpty()){
Pair<Integer, Integer> front = queue.removeFirst();
int num = front.getKey();
int step = front.getValue();
if(num == 0)
return step;
for(int i = 1 ; num - i*i >= 0 ; i ++){
int a = num - i*i;
if(!visited[a]){
if(a == 0) return step + 1;
queue.addLast(new Pair(num - i * i, step + 1));
visited[num - i * i] = true;
}
}
}
return 0;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@ -0,0 +1,135 @@
# LeetCode 283 号问题移动零
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 283 号问题移动零题目难度为 Easy目前通过率为 53.8%
### 题目描述
给定一个数组 `nums`编写一个函数将所有 `0` 移动到数组的末尾同时保持非零元素的相对顺序
**示例:**
```
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
```
**说明**:
1. 必须在原数组上操作不能拷贝额外的数组
2. 尽量减少操作次数
### 题目解析
设定一个临时变量 k = 0遍历数组 nums 将非零元素移动到 nums[k] 同时 k++而后将k,.nums.size()中的元素置零
### 解法一
创建一个临时数组 nonZeroElements 遍历 nums nums 中非 0 元素赋值到 nonZeroElements中而后按顺序将 nonZeroElements 赋值到 nums 未遍历的元素置 0
动画如下
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/0eeix.gif)
代码如下
```
// 时间复杂度: O(n)
// 空间复杂度: O(n)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
vector<int> nonZeroElements;
// 将vec中所有非0元素放入nonZeroElements中
for(int i = 0 ; i < nums.size() ; i ++)
if(nums[i])
nonZeroElements.push_back(nums[i]);
// 将nonZeroElements中的所有元素依次放入到nums开始的位置
for(int i = 0 ; i < nonZeroElements.size() ; i ++)
nums[i] = nonZeroElements[i];
// 将nums剩余的位置放置为0
for(int i = nonZeroElements.size() ; i < nums.size() ; i ++)
nums[i] = 0;
}
};
```
### 解法二
设定一个临时变量 k = 0遍历数组 nums 将非零元素移动到 nums[k] 位置同时 k++而后将k,.nums.size()中的元素置零
动画如下
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/jodp0.gif)
代码如下
```
// 原地(in place)解决该问题
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int k = 0; // nums中, [0...k)的元素均为非0元素
// 遍历到第i个元素后,保证[0...i]中所有非0元素
// 都按照顺序排列在[0...k)
for(int i = 0 ; i < nums.size() ; i ++)
if(nums[i])
nums[k++] = nums[i];
// 将nums剩余的位置放置为0
for(int i = k ; i < nums.size() ; i ++)
nums[i] = 0;
}
};
```
### 解法三
思路设定一个临时变量 k = 0遍历数组 nums将非零元素与之前的零元素进行交换维护变量k的值
动画如下
![](../Animation/Animation.gif)
代码如下
```
// 原地(in place)解决该问题
// 时间复杂度: O(n)
// 空间复杂度: O(1)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int k = 0; // nums中, [0...k)的元素均为非0元素
// 遍历到第i个元素后,保证[0...i]中所有非0元素
// 都按照顺序排列在[0...k)
// 同时, [k...i] 0
for(int i = 0 ; i < nums.size() ; i ++)
if(nums[i]){
if(k != i){
swap(nums[k++] , nums[i]);
}else{
k ++;
}
}
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 MiB

View File

@ -0,0 +1,90 @@
# LeetCode 295 号问题数据流的中位数
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 295 号问题数据流的中位数难度级别为 Hard目前通过率为 33.5%
### 题目描述
中位数是有序列表中间的数如果列表长度是偶数中位数则是中间两个数的平均值
例如
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
设计一个支持以下两种操作的数据结构
- void addNum(int num) - 从数据流中添加一个整数到数据结构中
- double findMedian() - 返回目前所有元素的中位数
**示例**
```java
addNum(1)
addNum(2)
findMedian() -> 1.5
addNum(3)
findMedian() -> 2
```
### 题目解析
这道题给我们一个数据流让我们找出中位数对于数据流这种动态流动的数据如果使用数组存储那么每次新进来一个数据都进行排序的话效率很低
处理动态数据来说一般使用的数据结构是栈队列二叉树
本题中我们使用 **** 这种数据结构
首先将数据分为两部分位于 **上边最大堆** 的数据要比 **下边最小堆** 的数据都要小
为了保证将数据平均分配到两个堆中在动态的操作的过程中两个堆中数据的数目之差不能超过 1
为了保证 **最大堆中的所有数据都小于最小堆中的数据**在操作过程中新添加进去的数据需要先和最大堆的最大值或者最小堆中的最小值进行比较
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```java
class MedianFinder {
public PriorityQueue<Integer> minheap, maxheap;
public MedianFinder() {
//维护较大的元素的最小堆
maxheap = new PriorityQueue<Integer>(Collections.reverseOrder());
//维护较小元素的最大堆
minheap = new PriorityQueue<Integer>();
}
// Adds a number into the data structure.
public void addNum(int num) {
maxheap.add(num);
minheap.add(maxheap.poll());
if (maxheap.size() < minheap.size()) {
maxheap.add(minheap.poll());
}
}
// Returns the median of current data stream
public double findMedian() {
if (maxheap.size() == minheap.size()) {
return (maxheap.peek() + minheap.peek()) * 0.5;
} else {
return maxheap.peek();
}
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

View File

@ -0,0 +1,85 @@
# LeetCode 328 号问题奇偶链表
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 328 号问题奇偶链表题目难度为 Medium目前通过率为 52.0%
### 题目描述
给定一个单链表把所有的奇数节点和偶数节点分别排在一起请注意这里的奇数节点和偶数节点指的是节点编号的奇偶性而不是节点的值的奇偶性
请尝试使用原地算法完成你的算法的空间复杂度应为 O(1)时间复杂度应为 O(nodes)nodes 为节点总数
**示例 1:**
```
输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
```
**示例 2:**
```
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
```
**说明:**
- 应当保持奇数节点和偶数节点的相对顺序
- 链表的第一个节点视为奇数节点第二个节点视为偶数节点以此类推
### 题目解析
这道题给了我们一个链表让我们分开奇偶节点所有奇节点在前偶节点在后
* 设定两个虚拟节点`dummyHead1 `用来保存奇节点`dummyHead2 `来保存偶节点
* 遍历整个原始链表将奇节点放于`dummyHead1 `其余的放置在`dummyHead2 `
* 遍历结束后`dummyHead2 `插入到`dummyHead1 `后面
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if(head == NULL || head->next == NULL || head->next->next == NULL)
return head;
ListNode* dummyHead1 = new ListNode(-1);
ListNode* dummyHead2 = new ListNode(-1);
ListNode* p1 = dummyHead1;
ListNode* p2 = dummyHead2;
ListNode* p = head;
for(int i = 0; p; i ++)
if(i % 2 == 0){
p1->next = p;
p = p->next;
p1 = p1->next;
p1->next = NULL;
}
else{
p2->next = p;
p = p->next;
p2 = p2->next;
p2->next = NULL;
}
p1->next = dummyHead2->next;
ListNode* ret = dummyHead1->next;
delete dummyHead1;
delete dummyHead2;
return ret;
}
};
```
![](../../Pictures/qrcode.jpg)

View File

@ -0,0 +1,112 @@
# LeetCode 342 号问题4 的幂
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 342 号问题4 的幂题目难度为 Easy目前通过率为 45.3%
### 题目描述
给定一个整数 (32 位有符号整数)请编写一个函数来判断它是否是 4 的幂次方
**示例 1:**
```
输入: 16
输出: true
```
**示例 2:**
```
输入: 5
输出: false
```
**进阶**
你能不使用循环或者递归来完成本题吗
### 题目解析
这道题最直接的方法就是不停的去除以 4 看最终结果是否为 1 参见代码如下
```java
class Solution {
public boolean isPowerOfFour(int num) {
while ( (num != 0) && (num % 4 == 0)) {
num /= 4;
}
return num == 1;
}
}
```
不过这段代码使用了 **循环** 逼格不够高
对于一个整数而言如果这个数是 4 的幂次方那它必定也是 2 的幂次方
我们先将 2 的幂次方列出来找一下其中哪些数是 4 的幂次方
| 十进制 | 二进制 |
| ------ | ------------------------------- |
| 2 | 10 |
| 4 | **100** 1 在第 3 |
| 8 | 1000 |
| 16 | **10000**1 在第 5 |
| 32 | 100000 |
| 64 | **1000000**1 在第 7 |
| 128 | 10000000 |
| 256 | **100000000**1 在第 9 |
| 512 | 1000000000 |
| 1024 | **10000000000**1 在第 11 |
找一下规律 4 的幂次方的数的二进制表示 1 的位置都是在**奇数位**
之前在小吴的文章中判断一个是是否是 2 的幂次方数使用的是位运算 `n & ( n - 1 )`同样的这里依旧可以使用位运算将这个数与特殊的数做位运算
这个特殊的数有如下特点
* 足够大但不能超过 32 即最大为 1111111111111111111111111111111 31 1
* 它的二进制表示中奇数位为 1 偶数位为 0
符合这两个条件的二进制数是
```java
1010101010101010101010101010101
```
**如果用一个 4 的幂次方数和它做与运算得到的还是 4 的幂次方数**
将这个二进制数转换成 16 进制表示0x55555555 有没有感觉逼格更高点
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/c0s9n.png)
### 图片描述
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/ux5pa.jpg)
### 代码实现
```java
class Solution {
public boolean isPowerOfFour(int num) {
if (num <= 0)
return false;
//先判断是否是 2 的幂
if ((num & num - 1) != 0)
return false;
//如果与运算之后是本身则是 4 的幂
if ((num & 0x55555555) == num)
return true;
return false;
}
}
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

View File

@ -0,0 +1,56 @@
# LeetCode 344 号问题反转字符串
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 344 号问题反转字符串面试官最喜欢让你手写的一道算法题
### 题目描述
编写一个函数其作用是将输入的字符串反转过来输入字符串以字符数组 `char[]` 的形式给出
不要给另外的数组分配额外的空间你必须**原地修改输入数组**使用 O(1) 的额外空间解决这一问题
你可以假设数组中的所有字符都是 [ASCII](https://baike.baidu.com/item/ASCII) 码表中的可打印字符
**示例 1**
```
输入["h","e","l","l","o"]
输出["o","l","l","e","h"]
```
**示例 2**
```
输入["H","a","n","n","a","h"]
输出["h","a","n","n","a","H"]
```
### 题目解析
这道题没什么难度直接从两头往中间走同时交换两边的字符注意需要白板编程写出来即可也注意千万别回答一句使用 reverse() 这种高级函数来解决
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public:
string reverseString(string s) {
int i = 0, j = s.size() - 1;
while (i < j){
swap(s[i],s[j]);
i++;
j--;
}
return s;
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

View File

@ -0,0 +1,169 @@
# LeetCode 347 号问题 K 个高频元素
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
今天分享的题目来源于 LeetCode 上第 347 号问题 K 个高频元素题目难度为 Medium目前通过率为 56.9%
## 题目描述
给定一个非空的整数数组**返回其中出现频率前 k **的元素
**示例 1:**
```
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
```
**示例 2:**
```
输入: nums = [1], k = 1
输出: [1]
```
**说明**
- 你可以假设给定的 k 总是合理的 1 k 数组中不相同的元素的个数
- 你的算法的时间复杂度必须优于 O(n log n) n 是数组的大小
### 题目解析
### 解法一粗暴排序法
最简单粗暴的思路就是 **使用排序算法对元素按照频率由高到低进行排序**然后再取前 k 个元素
以下十种排序算法任你挑选
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/qya5e.png)
可以发现使用常规的诸如 冒泡选择甚至快速排序都是不满足题目要求它们的时间复杂度都是大于或者等于 O(n logn) 而题目要求算法的时间复杂度必须优于 O(n log n)
#### 复杂度分析
- **时间复杂度**O(nlogn)n 表示数组长度首先遍历一遍数组统计元素的频率这一系列操作的时间复杂度是 O(n)接着排序算法时间复杂度为O(nlogn) 因此整体时间复杂度为 O(nlogn)
- **空间复杂度**O(n)最极端的情况下每个元素都不同用于存储元素及其频率的 Map 需要存储 n 个键值对
### 解法二最小堆
题目最终需要返回的是前 k 个频率最大的元素可以想到借助堆这种数据结构对于 k 频率之后的元素不用再去处理进一步优化时间复杂度
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/nloow.jpg)
具体操作为
- 借助 **哈希表** 来建立数字和其出现次数的映射遍历一遍数组统计元素的频率
- 维护一个元素数目为 k 的最小堆
- 每次都将新的元素与堆顶元素堆中频率最小的元素进行比较
- 如果新的元素的频率比堆顶端的元素大则弹出堆顶端的元素将新的元素添加进堆中
- 最终堆中的 k 个元素即为前 k 个高频元素
### 动画理解
![](../Animation/Animation.gif)
### 参考代码
```java
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
// 使用字典统计每个元素出现的次数元素为键元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
// 遍历map用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> res = new ArrayList<>();
while (!pq.isEmpty()) {
res.add(pq.remove());
}
return res;
}
}
```
#### 复杂度分析
- **时间复杂度**O(nlogk) n 表示数组的长度首先遍历一遍数组统计元素的频率这一系列操作的时间复杂度是 O(n)接着遍历用于存储元素频率的 map如果元素的频率大于最小堆中顶部的元素则将顶部的元素删除并将该元素加入堆中**这里维护堆的数目是 k **所以这一系列操作的时间复杂度是 O(nlogk)因此总的时间复杂度是 O(nlogk)
- **空间复杂度**O(n)最坏情况下每个元素都不同map 需要存储 n 个键值对优先队列需要存储 k个元素因此空间复杂度是 O(n)
### 解法三桶排序法
首先依旧使用哈希表统计频率统计完成后创建一个数组将频率作为数组下标对于出现频率不同的数字集合存入对应的数组下标即可
![](https://blog-1257126549.cos.ap-guangzhou.myqcloud.com/blog/6tge2.jpg)
代码实现如下
```java
//基于桶排序求解 K 个高频元素
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer> res = new ArrayList();
// 使用字典统计每个元素出现的次数元素为键元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
//桶排序
//将频率作为数组下标对于出现频率不同的数字集合存入对应的数组下标
List<Integer>[] list = new List[nums.length+1];
for(int key : map.keySet()){
// 获取出现的次数作为下标
int i = map.get(key);
if(list[i] == null){
list[i] = new ArrayList();
}
list[i].add(key);
}
// 倒序遍历数组获取出现顺序从大到小的排列
for(int i = list.length - 1;i >= 0 && res.size() < k;i--){
if(list[i] == null) continue;
res.addAll(list[i]);
}
return res;
}
}
```
#### 复杂度分析
- **时间复杂度**O(n) n 表示数组的长度首先遍历一遍数组统计元素的频率这一系列操作的时间复杂度是 O(n)桶的数量为 n + 1所以桶排序的时间复杂度为 O(n)因此总的时间复杂度是 O(n)
- **空间复杂度**很明显为 O(n)
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

View File

@ -0,0 +1,71 @@
# LeetCode 349 号问题两个数组的交集
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 349 号问题两个数组的交集题目难度为 Easy目前通过率为 62.3%
### 题目描述
给定两个数组编写一个函数来计算它们的交集
**示例 1:**
```
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
```
**示例 2:**
```
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
```
**说明:**
- 输出结果中的每个元素一定是唯一的
- 我们可以不考虑输出结果的顺序
### 题目解析
容器类 [set](https://zh.cppreference.com/w/cpp/container/set) 的使用
- 遍历 num1通过 set 容器 record 存储 num1 的元素
- 遍历 num2 record 中查找是否有相同的元素如果有 set 容器 resultSet 进行存储
- resultSet 转换为 vector 类型
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
set<int> record;
for(int i = 0; i < nums1.size(); i ++){
record.insert(nums1[i]);
}
set<int> resultSet;
for(int i = 0; i < nums2.size();i++){
if(record.find(nums2[i]) != record.end()){
resultSet.insert(nums2[i]);
}
}
vector<int> resultVector;
for(set<int>::iterator iter=resultSet.begin(); iter != resultSet.end();iter++){
resultVector.push_back(*iter);
}
return resultVector;
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -0,0 +1,83 @@
# LeetCode 350 号问题两个数组的交集 II
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 350 号问题两个数组的交集 II题目难度为 Easy目前通过率为 41.8%
### 题目描述
给定两个数组编写一个函数来计算它们的交集
**示例 1:**
```
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
```
**示例 2:**
```
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
```
**说明**
- 输出结果中每个元素出现的次数应与元素在两个数组中出现的次数一致
- 我们可以不考虑输出结果的顺序
**进阶:**
- 如果给定的数组已经排好序呢你将如何优化你的算法
- 如果 *nums1* 的大小比 *nums2* 小很多哪种方法更优
- 如果 *nums2* 的元素存储在磁盘上磁盘内存是有限的并且你不能一次加载所有的元素到内存中你该怎么办
### 题目解析
容器类 [map](https://zh.cppreference.com/w/cpp/container/map) 的使用
- 遍历 num1通过map容器 record 存储 num1 的元素与频率
- 遍历 num2 record 中查找是否有相同的元素该元素的存储频率大于0如果有用map容器resultVector 进行存储同时该元素的频率减一
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
// 350. Intersection of Two Arrays II
// https://leetcode.com/problems/intersection-of-two-arrays-ii/description/
// 时间复杂度: O(nlogn)
// 空间复杂度: O(n)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
map<int, int> record;
for(int i = 0 ; i < nums1.size() ; i ++){
record[nums1[i]] += 1;
}
vector<int> resultVector;
for(int i = 0 ; i < nums2.size() ; i ++){
if(record[nums2[i]] > 0){
resultVector.push_back(nums2[i]);
record[nums2[i]] --;
}
}
return resultVector;
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

View File

@ -0,0 +1,69 @@
# LeetCode 445 号问题两数相加 II
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 445 号问题两数相加 II题目难度为 Medium目前通过率为 48.8%
### 题目描述
给定两个**非空**链表来代表两个非负整数数字最高位位于链表开始位置它们的每个节点只存储单个数字将这两数相加会返回一个新的链表
你可以假设除了数字 0 之外这两个数字都不会以零开头
**进阶:**
如果输入链表不能修改该如何处理换句话说你不能对列表中的节点进行翻转
**示例:**
```
输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7
```
### 题目解析
由于计算时要保证最右边的数对齐那么很自然的想到先用****存放链表中的每个值然后依次计算由于相加时可能产生进位所以使用一个flag表示是否有进位
提示若栈中元素相加结束之后仍有进位则需要新加入一个头结点
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```python
class Solution:
def addTwoNumbers(self, l1, l2):
# 分别入栈
stack1 = []
stack2 = []
while l1:
stack1.append(l1.val)
l1 = l1.next
while l2:
stack2.append(l2.val)
l2 = l2.next
flag = 0
head = None
while stack1 or stack2 or flag != 0:
if stack1:
flag += stack1.pop()
if stack2:
flag += stack2.pop()
node = ListNode(flag % 10)
node.next = head
head = node
flag = flag // 10
return head
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

View File

@ -0,0 +1,97 @@
# LeetCode第447号问题回旋镖的数量
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 447 号问题回旋镖的数量题目难度为 Easy目前通过率为 45.8%
### 题目描述
给定平面上 *n* 对不同的点回旋镖 是由点表示的元组 `(i, j, k)` 其中 `i` `j` 之间的距离和 `i` `k` 之间的距离相等**需要考虑元组的顺序**
找到所有回旋镖的数量你可以假设 *n* 最大为 **500**所有点的坐标在闭区间 **[-10000, 10000]**
**示例:**
```
输入:
[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] [[1,0],[2,0],[0,0]]
```
### 题目解析
n 最大为 500可以使用时间复杂度为 O(n^2)的算法
- 遍历所有的点让每个点作为一个锚点
- 然后再遍历其他的点统计和锚点距离相等的点有多少个
- 然后分别带入 n(n-1) 计算结果并累加到res中
##### Tips
###### Tip1
- 如果有一个点 a还有两个点 b c 如果 ab ac 之间的距离相等那么就有两种排列方法 abc acb
- 如果有三个点 bcd都分别和a之间的距离相等那么有六种排列方法abc, acb, acd, adc, abd, adb
- 如果有 n 个点和点 a 距离相等那么排列方式为 n(n-1)
###### Tip2
- 计算距离时不进行开根运算, 以保证精度
- 只有当n大于等于2时res值才会真正增加因为当n=1时增加量为`1*(1-1)=0`
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
// 447. Number of Boomerangs
// https://leetcode.com/problems/number-of-boomerangs/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n)
class Solution {
public:
int numberOfBoomerangs(vector<pair<int, int>>& points) {
int res = 0;
for( int i = 0 ; i < points.size() ; i ++ ){
// record中存储 点i 到所有其他点的距离出现的频次
unordered_map<int, int> record;
for(int j = 0 ; j < points.size() ; j ++){
if(j != i){
// 计算距离时不进行开根运算, 以保证精度
record[dis(points[i], points[j])] += 1;
}
}
for(unordered_map<int, int>::iterator iter = record.begin() ; iter != record.end() ; iter ++){
res += (iter->second) * (iter->second - 1);
}
}
return res;
}
private:
int dis(const pair<int,int> &pa, const pair<int,int> &pb){
return (pa.first - pb.first) * (pa.first - pb.first) +
(pa.second - pb.second) * (pa.second - pb.second);
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@ -0,0 +1,79 @@
# LeetCode 454 号问题四数相加 II
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 454 号问题四数相加 II题目难度为 Medium目前通过率为 50.8%
### 题目描述
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 `(i, j, k, l)` 使得 `A[i] + B[j] + C[k] + D[l] = 0`
为了使问题简单化所有的 A, B, C, D 具有相同的长度 N 0 N 500 所有整数的范围在 -228 228 - 1 之间最终结果不会超过 231 - 1
**例如:**
```
输入:
A = [ 1, 2]
B = [-2,-1]
C = [-1, 2]
D = [ 0, 2]
输出:
2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0
```
### 题目解析
[Two Sum](https://xiaozhuanlan.com/topic/7923618450)类似需要用哈希表来解决问题
- A B 的两两之和都求出来在哈希表中建立两数之和与其出现次数之间的映射
- 遍历 C D 中任意两个数之和只要看哈希表存不存在这两数之和的相反数就行了
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
```
// 454. 4Sum II
// https://leetcode.com/problems/4sum-ii/description/
// 时间复杂度: O(n^2)
// 空间复杂度: O(n^2)
class Solution {
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D) {
unordered_map<int,int> hashtable;
for(int i = 0 ; i < A.size() ; i ++){
for(int j = 0 ; j < B.size() ; j ++){
hashtable[A[i]+B[j]] += 1;
}
}
int res = 0;
for(int i = 0 ; i < C.size() ; i ++){
for(int j = 0 ; j < D.size() ; j ++){
if(hashtable.find(-C[i]-D[j]) != hashtable.end()){
res += hashtable[-C[i]-D[j]];
}
}
}
return res;
}
};
```
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

View File

@ -0,0 +1,201 @@
# LeetCode 642 号问题设计一个搜索自动完成系统
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 642 号问题设计一个搜索自动完成系统题目难度为 Hard目前通过率为 37.8%
### 题目描述
为搜索引擎设计一个搜索自动完成系统用户可以输入一个句子(至少一个单词并以一个特殊的字符'#'结尾)对于除'#'之外的每个字符您需要返回与已输入的句子部分前缀相同的前3个历史热门句子具体规则如下:
一个句子的热度定义为用户输入完全相同句子的次数
返回的前3个热门句子应该按照热门程度排序(第一个是最热的)如果几个句子的热度相同则需要使用ascii代码顺序(先显示较小的一个)
如果少于3个热门句子那么就尽可能多地返回
当输入是一个特殊字符时它意味着句子结束在这种情况下您需要返回一个空列表
您的工作是实现以下功能:
构造函数:
AutocompleteSystem(String[] sentence, int[] times):这是构造函数输入是历史数据句子是由之前输入的句子组成的字符串数组Times是输入一个句子的相应次数您的系统应该记录这些历史数据
现在用户想要输入一个新句子下面的函数将提供用户类型的下一个字符:
List<String> input(char c):输入c是用户输入的下一个字符字符只能是小写字母(az)空格()或特殊字符(#)另外前面输入的句子应该记录在系统中输出将是前3个历史热门句子它们的前缀与已经输入的句子部分相同
例子:
操作:AutocompleteSystem(["i love you" "island""ironman" "i love leetcode"] [5,3,2,2])
系统已经追踪到以下句子及其对应的时间:
"i love you" : 5 times
"island" : 3 times
"ironman" : 2 times
"i love leetcode" : 2 times
现在用户开始另一个搜索:
操作:输入(i)
输出:["i love you" "island""i love leetcode"]
解释:
有四个句子有前缀i其中ironmani love leetcode有着相同的热度既然 ASCII码为32rASCII码为114那么i love leetcode应该在ironman前面此外我们只需要输出前3个热门句子所以ironman将被忽略
操作:输入(' ')
输出:[i love youi love leetcode]
解释:
只有两个句子有前缀i
操作:输入(' a ')
输出:[]
解释:
没有以i a为前缀的句子
操作:输入(#)
输出:[]
解释:
用户完成输入后在系统中将句子i a保存为历史句下面的输入将被计算为新的搜索
注意:
输入的句子总是以字母开头#结尾两个单词之间只有一个空格
要搜索的完整句子不会超过100个包括历史数据在内的每句话的长度不会超过100句
在编写测试用例时即使是字符输入也请使用双引号而不是单引号
请记住重置在AutocompleteSystem类中声明的类变量因为静态/类变量是跨多个测试用例持久化的详情请点击这里
### 题目解析
设计一个搜索自动补全系统它需要包含如下两个方法
#### 构造方法
AutocompleteSystem(String[] sentences, int[] times): 输入句子sentences及其出现次数times
#### 输入方法
List<String> input(char c): 输入字符c可以是26个小写英文字母也可以是空格'#'结尾返回输入字符前缀对应频率最高的至多3个句子频率相等时按字典序排列
### 思路解析
核心点Trie字典树
利用字典树记录所有出现过的句子集合利用字典保存每个句子出现的次数
#### 解题思路
题目的要求是补全的句子是按之前出现的频率排列的高频率的出现在最上面如果频率相同就按字母顺序来显示
频率 这种要求很容易想到 优先队列Map等知识点这里涉及到 字典 那肯定使用 字典树 能解决
所以首先构造 Trie trieNode 结构以及 insert 方法构造完 trieNode 类后再构造一个树的根节点
由于每次都要输入一个字符我们可以用一个私有的 NodecurNode 来追踪当前的节点
curNode 初始化为 root 在每次输入完一个句子时即输入的字符为#我们需要将其置为root
同时还需要一个 string 类型 stn 来表示当前的搜索的句子
每输入一个字符首先检查是不是结尾标识#如果是的话将当前句子加入trie树重置相关变量返回空数组
* 如不是检查当前 TrieNode 对应的 child 是否含有 c 的对应节点如果没有 curNode 置为 NULL 并且返回空数组
* 若存在将curNode 更新为c对应的节点并且对curNode进行dfs
dfs 我们首先检查当前是不是一个完整的句子如果是将句子与其次数同时加入 priority_queue 然后对其 child 中可能存在的子节点进行 dfs
进行完 dfs 只需要取出前三个需要注意的是可能可选择的结果不满3个所以要在 while 中多加入检测 q 为空的条件语句
最后要将 q 中的所有元素都弹出
### 动画描述
![](../Animation/Animation.gif)
### 代码实现
#### C++
```
class TrieNode{
public:
string str;
int cnt;
unordered_map<char, TrieNode*> child;
TrieNode(): str(""), cnt(0){};
};
struct cmp{
bool operator() (const pair<string, int> &p1, const pair<string, int> &p2){
return p1.second < p2.second || (p1.second == p2.second && p1.first > p2.first);
}
};
class AutocompleteSystem {
public:
AutocompleteSystem(vector<string> sentences, vector<int> times) {
root = new TrieNode();
for(int i = 0; i < sentences.size(); i++){
insert(sentences[i], times[i]);
}
curNode = root;
stn = "";
}
vector<string> input(char c) {
if(c == '#'){
insert(stn, 1);
stn.clear();
curNode = root;
return {};
}
stn.push_back(c);
if(curNode && curNode->child.count(c)){
curNode = curNode->child[c];
}else{
curNode = NULL;
return {};
}
dfs(curNode);
vector<string> ret;
int n = 3;
while(n > 0 && !q.empty()){
ret.push_back(q.top().first);
q.pop();
n--;
}
while(!q.empty()) q.pop();
return ret;
}
void dfs(TrieNode* n){
if(n->str != ""){
q.push({n->str, n->cnt});
}
for(auto p : n->child){
dfs(p.second);
}
}
void insert(string s, int cnt){
TrieNode* cur = root;
for(auto c : s){
if(cur->child.count(c) == 0){
cur->child[c] = new TrieNode();
}
cur = cur->child[c];
}
cur->str = s;
cur->cnt += cnt;
}
private:
TrieNode *root, *curNode;
string stn;
priority_queue<pair<string,int>, vector<pair<string, int>>, cmp > q;
};
```
![](../../Pictures/qrcode.jpg)