整理文件
BIN
0131-Palindrome-Partitioning/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 193 KiB |
@ -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)
|
BIN
0136-Single-Number/Animation/136.gif
Normal file
After Width: | Height: | Size: 446 KiB |
86
0136-Single-Number/Article/0136-Single-Number.md
Normal 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)
|
BIN
0138-Copy-List-with-Random-Pointer/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 1.4 MiB |
@ -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)
|
||||||
|
|
68
0139-Word-Break/Article/0139-Word-Break.md
Normal 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)
|
BIN
0141-Linked-List-Cycle/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 128 KiB |
83
0141-Linked-List-Cycle/Article/0141-Linked-List-Cycle.md
Normal 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)
|
BIN
0144-Binary-Tree-Preorder-Traversal/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 369 KiB |
@ -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)
|
BIN
0145-Binary-Tree-Postorder-Traversal/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 413 KiB |
@ -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)
|
BIN
0146-LRU-Cache/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 5.9 MiB |
97
0146-LRU-Cache/Article/0146-LRU-Cache.md
Normal 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)
|
BIN
0150-Evaluate-Reverse-Polish-Notation/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 357 KiB |
@ -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)
|
BIN
0167-Two-Sum-II-Input-array-is-sorted/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 68 KiB |
@ -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 左移指向一个较小的值。
|
||||||
|
- (3)tmp 等于 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)
|
BIN
0169-Majority-Element/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
0169-Majority-Element/Animation/Animation2.gif
Normal file
After Width: | Height: | Size: 141 KiB |
BIN
0169-Majority-Element/Animation/Animation3.gif
Normal file
After Width: | Height: | Size: 238 KiB |
171
0169-Majority-Element/Article/0169-Majority-Element.md
Normal 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)
|
@ -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 这个因子**。
|
||||||
|
|
||||||
|
需要注意的是,像 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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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)
|
BIN
0203-Remove-Linked-List-Elements/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 279 KiB |
@ -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)
|
BIN
0206-Reverse-Linked-List/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 439 KiB |
58
0206-Reverse-Linked-List/Article/0206-Reverse-Linked-List.md
Normal 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)重复(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)
|
BIN
0209-Minimum-Size-Subarray-Sum/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 143 KiB |
@ -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)重复(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)
|
BIN
0219-Contains-Duplicate-II/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 327 KiB |
@ -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)
|
81
0231-Power-Of-Two/Article/0231-Power-Of-Two.md
Normal 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)
|
BIN
0237-Delete-Node-in-a-Linked-List/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 63 KiB |
@ -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)
|
BIN
0239-Sliding-Window-Maximum/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 1.9 MiB |
@ -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(队列),它的直接实现有 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)
|
BIN
0268-Missing-Number/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 372 KiB |
126
0268-Missing-Number/Article/0268-Missing-Number.md
Normal 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)
|
BIN
0279-Perfect-Squares/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 174 KiB |
118
0279-Perfect-Squares/Article/0279-Perfect-Squares.md
Normal 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.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 题目解析
|
||||||
|
|
||||||
|
这道题目很有意思。
|
||||||
|
|
||||||
|
大部分文章给出的答案都是依托于一个定理:**四平方定理**。
|
||||||
|
|
||||||
|
四平方定理讲的就是任何一个正整数都可以表示成不超过四个整数的平方之和。也就是说,这道题的答案只有 1,2 ,3,4 这四种可能。
|
||||||
|
|
||||||
|
同时,还有一个非常重要的推论满足四数平方和定理的数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)
|
BIN
0283-Move-Zeroes/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 102 KiB |
135
0283-Move-Zeroes/Article/0283-Move-Zeroes.md
Normal 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)
|
BIN
0295-Find-Median-from-Data-Stream/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 4.5 MiB |
@ -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)
|
BIN
0328-Odd-Even-Linked-List/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 356 KiB |
@ -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)
|
112
0342-Power-Of-Four/Article/0342-Power-Of-Four.md
Normal 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)
|
BIN
0344-Reverse-String/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 178 KiB |
56
0344-Reverse-String/Article/0344-Reverse-String.md
Normal 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)
|
BIN
0347-Top-K-Frequent-Elements/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 263 KiB |
@ -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)
|
||||||
|
|
BIN
0349-Intersection-of-Two-Arrays/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 128 KiB |
@ -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)
|
BIN
0350-Intersection-of-Two-Arrays-II/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 84 KiB |
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
BIN
0445-Add-Two-Numbers-II/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 704 KiB |
69
0445-Add-Two-Numbers-II/Article/0445-Add-Two-Numbers-II.md
Normal 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)
|
BIN
0447-Number-of-Boomerangs/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 269 KiB |
@ -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;
|
||||||
|
- 如果有三个点 b,c,d都分别和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)
|
BIN
0454-4Sum-II/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 145 KiB |
79
0454-4Sum-II/Article/0454-4Sum-II.md
Normal 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)
|
BIN
0642-Design-Search-Autocomplete-System/Animation/Animation.gif
Normal file
After Width: | Height: | Size: 1.7 MiB |
@ -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是用户输入的下一个字符。字符只能是小写字母(“a”到“z”)、空格(“”)或特殊字符(“#”)。另外,前面输入的句子应该记录在系统中。输出将是前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”。其中,《ironman》和《i love leetcode》有着相同的热度。既然“ ” ASCII码为32,“r”ASCII码为114,那么“i love leetcode”应该在“ironman”前面。此外,我们只需要输出前3个热门句子,所以“ironman”将被忽略。
|
||||||
|
|
||||||
|
操作:输入(' ')
|
||||||
|
输出:[“i love you”,“i 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 类后,再构造一个树的根节点。
|
||||||
|
|
||||||
|
由于每次都要输入一个字符,我们可以用一个私有的 Node:curNode 来追踪当前的节点。
|
||||||
|
|
||||||
|
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)
|