Merge pull request #4 from MisterBooo/master

更新项目
This commit is contained in:
chilimyan 2020-05-26 16:57:43 +08:00 committed by GitHub
commit c11ab1e5e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1083 additions and 1 deletions

View File

@ -57,6 +57,7 @@ public:
record[nums[i]] = i;
}
return {};
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,151 @@
# LeetCode 4 号问题寻找两个正序数组的中位数
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 4 号问题寻找两个正序数组的中位数题目难度为 Hard目前通过率为 29.0%
#### 题目描述
> 给定两个大小为 m n 的正序从小到大数组 nums1  nums2
请你找出这两个正序数组的中位数并且要求算法的时间复杂度为 O(log(m + n))
你可以假设 nums1  nums2 不会同时为空
```java
示例1
nums1 = [1, 3]
nums2 = [2]
则中位数是 2.0
示例2
nums1 = [1, 2]
nums2 = [3, 4]
则中位数是 (2 + 3)/2 = 2.5
```
#### 题目解析
这道题网络上的解析都非常高深很难理解私以为它们都将简单的问题复杂化了本题在一些处理上确实会有些麻烦比如数组边界的处理和偶数个数的中位数的处理但其核心思想并不复杂
首先我们可以只考虑数字总个数为奇数的情况让我们看下下图
![](../Animation/image1.PNG)
蓝框是中位数左边的数包括中位数而橘框则为中位数右边的数
3个显然的规则
1.两个数组的蓝框总个数=(数字总个数+1)/2;
2.所有蓝框内的数都小于橘框内的数
3.中位数为蓝框中最大的那一位即数组1蓝框最后一位或数组2蓝框最后一位
![](../Animation/image2.PNG)
如图我们要找到一组AB满足上面3条规则
对于规则1我们在数组1中找任意A然后根据规则1就能推算出对应的B的位置
对于规则2由于数组1和2都是有序数组即X1<A<Y1;X2<B<Y2我们实际上只需要判断A是否小于Y2以及B是否小于Y2
对于规则3由于数组1和2都是有序数组因此中位数为A,B中较大的那一项
那么具体该如何操作呢?
由于数组1和2都是有序数组且题目要求O(log(m+n))复杂度我们明显应考虑二分法
**情况1**
![](../Animation/case1.png)
首先我们选择数组1进行操作取其中间值9 因此 A=9) 根据规则1,我们在数组2中找到对应值B = 4一共有11个数(11+1) / 2 = 6因此蓝色框总数为6
紧接着我们根据规则2判断A(9)是否小于B.next(5)以及B(4)是否小于A.next(11)
显然A比B.next,也就是一个橘框还要大这是不允许的可见A只能取比9更小的数字了如果取更大的数字那B就会更小更不可能满足规则2所以这种情况下我们要向左进行二分
**情况2**
![](../Animation/case2.png)
这种情况下B比A.next,也就是一个橘框还要大这是不允许的可见A只能取比9更大的数字了如果取更小的数字那B就会更大更不可能满足规则2所以这种情况下我们要向右进行二分
**情况3**
![](../Animation/case3.png)
随着我们不断地二分中位数显然必然会出现
如图上这种情况A小于B.next且B小于A.next
那么显然A,B中较大的那一项就是中位数规则3
本题算法的核心思想就是这样简单此外当数字总数为偶数时我们需要把我们求得的中位数"与它下一项相加并除以2即可。由于本题中数字可能相同所以大小的比较需要使用>=和<=。
下面提供了作者的一份代码leetcode上的结果为执行用时2 ms内存消耗40.3 MB都超过了100%的用户读者可以参考一下
#### 代码实现
Java语言
```java
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 使nums1成为较短数组,不仅可以提高检索速度,同时可以避免一些边界问题
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int len1 = nums1.length;
int len2 = nums2.length;
int leftLen = (len1 + len2 + 1) / 2; //两数组合并&排序后,左半边的长度
// 对数组1进行二分检索
int start = 0;
int end = len1;
while (start <= end) {
// 两个数组的被测数A,B的位置(从1开始计算)
// count1 = 2 表示 num1 数组的第2个数字
// 比index大1
int count1 = start + ((end - start) / 2);
int count2 = leftLen - count1;
if (count1 > 0 && nums1[count1 - 1] > nums2[count2]) {
// A比B的next还要大
end = count1 - 1;
} else if (count1 < len1 && nums2[count2 - 1] > nums1[count1]) {
// B比A的next还要大
start = count1 + 1;
} else {
// 获取中位数
int result = (count1 == 0)? nums2[count2 - 1]: // 当num1数组的数都在总数组右边
(count2 == 0)? nums1[count1 - 1]: // 当num2数组的数都在总数组右边
Math.max(nums1[count1 - 1], nums2[count2 - 1]); // 比较A,B
if (isOdd(len1 + len2)) {
return result;
}
// 处理偶数个数的情况
int nextValue = (count1 == len1) ? nums2[count2]:
(count2 == len2) ? nums1[count1]:
Math.min(nums1[count1], nums2[count2]);
return (result + nextValue) / 2.0;
}
}
return Integer.MIN_VALUE; // 绝对到不了这里
}
// 奇数返回true,偶数返回false
private boolean isOdd(int x) {
return (x & 1) == 1;
}
}
```
#### 动画理解
![](../Animation/Animation.gif)
#### 复杂度分析
+ 时间复杂度对数组进行二分查找因此为O(logN)
+ 空间复杂度O(1)
![](../../Pictures/qrcode.jpg)

View File

@ -0,0 +1,54 @@
public class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
// 使nums1成为较短数组,不仅可以提高检索速度,同时可以避免一些边界问题
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int len1 = nums1.length;
int len2 = nums2.length;
int leftLen = (len1 + len2 + 1) / 2; //两数组合并&排序后,左半边的长度
// 对数组1进行二分检索
int start = 0;
int end = len1;
while (start <= end) {
// 两个数组的被测数A,B的位置(从1开始计算)
// count1 = 2 表示 num1 数组的第2个数字
// 比index大1
int count1 = start + ((end - start) / 2);
int count2 = leftLen - count1;
if (count1 > 0 && nums1[count1 - 1] > nums2[count2]) {
// A比B的next还要大
end = count1 - 1;
} else if (count1 < len1 && nums2[count2 - 1] > nums1[count1]) {
// B比A的next还要大
start = count1 + 1;
} else {
// 获取中位数
int result = (count1 == 0)? nums2[count2 - 1]: // 当num1数组的数都在总数组右边
(count2 == 0)? nums1[count1 - 1]: // 当num2数组的数都在总数组右边
Math.max(nums1[count1 - 1], nums2[count2 - 1]); // 比较A,B
if (isOdd(len1 + len2)) {
return result;
}
// 处理偶数个数的情况
int nextValue = (count1 == len1) ? nums2[count2]:
(count2 == len2) ? nums1[count1]:
Math.min(nums1[count1], nums2[count2]);
return (result + nextValue) / 2.0;
}
}
return Integer.MIN_VALUE; // 绝对到不了这里
}
// 奇数返回true,偶数返回false
private boolean isOdd(int x) {
return (x & 1) == 1;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 MiB

View File

@ -0,0 +1,169 @@
# LeetCode 25 号问题K 个一组翻转链表
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 25 号问题K 个一组翻转链表题目难度为 Hard
### 题目描述
给你一个链表 *k* 个节点一组进行翻转请你返回翻转后的链表
*k* 是一个正整数它的值小于或等于链表的长度
如果节点总数不是 *k* 的整数倍那么请将最后剩余的节点保持原有顺序
**示例:**
给你这个链表`1->2->3->4->5`
*k* = 2 应当返回: `2->1->4->3->5`
*k* = 3 应当返回: `3->2->1->4->5`
**说明:**
- 你的算法只能使用常数的额外空间
- **你不能只是单纯的改变节点内部的值**而是需要实际进行节点交换
### 题目解析
这道算法题可以说是 [两两交换链表中的节点](https://github.com/MisterBooo/LeetCodeAnimation/blob/master/0024-Swap-Nodes-in-Pairs/Article/0024-Swap-Nodes-in-Pairs2.md) 的升级版, 区别就是反转的子链表节点个数变成了自定义.
总体思路还是一样的, 具体可以分为两个处理模块:
1. 根据 *k* 划分若干个需要反转的子链表, 连接反转后的子链表, 最后不足 *k* 的子链表保持不变
- 设置哨兵 `dummy` 指向 `head` , 为了能找到反转后的链表头结点;
- 循环 *k* 确定需要 反转子链表 的范围:
- 循环完成, 确定子链表可以反转
假设 *A* , *B* 子链表邻接且都可以反转
- 指针 `start` 指向 *A* 的头结点, `end` 指向 *A* 的尾结点, `nxt` 指向 *B* 的头结点
- `start -> end` 反转后, `start` 变成了 A 的尾结点, `start -> next = nxt` , 反转后的 *A* 链表指向了 *B*
- 重置 `start` *B* 的头节点, `end` *B* 的尾结点, `nxt` 为下一个子链表头节点, 反转 *B* 链表
- 重复上面动作, 知道 循环终止
- 循环终止, 剩余节点不足 *k* , 终止反转, 返回链表
2. 反转子链表
假设子链表前三个节点为 *a*, *b*, *c* ,设置指针 `pre`, `cur`, `nxt` , 初始化 `pre` 值为 `null`, `cur` 值为 *a* , `nxt` 值为 *a* , 这三个指针位置不变且相邻
终止条件: `cur` 不为空
将当前节点的指针指向上一个节点
1. `cur` 指向 `nxt` ( `nxt` 值为 *b* )
2. `cur` 指向 `pre` ( `cur` 指向 `null` )
3. `cur` 赋值给 `pre` ( `pre` 值为 *a* ) , `nxt` 赋值给 `cur` ( `cur` 值为 *b* )
4. 在执行 步骤 `1` ( `nxt` 值为 *c* , 到此相当于 `pre`, `cur` , `nxt` 指向依次向后移动 `1` )
5. 重复上面动作
### 动画描述
<img src="../Animation/Animation.gif" alt="Animation" style="zoom:150%;" />
### 参考代码
#### 反转链表
```javascript
/**
* JavaScript 描述
* 反转区间 [start, end) 的元素, 注意不包含 end
*/
function reverse(start, end) {
let pre = null,
cur = start,
nxt = start;
while (cur != end) {
nxt = cur.next;
// 逐个节点反转
cur.next = pre;
// 更新指针位置
pre = cur;
cur = nxt;
}
// 反转后的头结点, start 移到了最后, end 没有发生改变
return pre;
};
```
#### 递归解法
```javascript
/**
* JavaScript 描述
* 递归
*/
var reverseKGroup = function(head, k) {
if (head == null) {
return null;
}
let start, end;
start = end = head;
for (let i = 0; i < k; i++) {
// 不足 k 不需要反转
if (end == null) {
return head;
}
end = end.next;
}
// 反转前 k 个元素, 不包含 end
let reverseHead = reverse(start, end);
// 递归反转后面k个元素 , 并前后连接起来
start.next = reverseKGroup(end, k);
return reverseHead;
};
```
#### 迭代解法
```javascript
/**
* JavaScript 描述
* 迭代
*/
var reverseKGroup = function(head, k) {
let dummy = new ListNode(0);
dummy.next = head;
let pre, start ,end, nxt;
pre = start = end = nxt = dummy;
while (end.next != null) {
for (let i = 0; i < k && end != null; i++) {
end = end.next;
}
if (end == null) {
// 不足 k , 跳出循环
break;
}
start = pre.next;
nxt = end.next;
// 反转前 k 个元素, 不包含 nxt
pre.next = reverse(start, nxt);
// 链接后面的链表
start.next = nxt;
// pre , end 重置到 下一个 k 子链表
pre = start;
end = pre;
}
return dummy.next;
};
```
### 复杂度分析
- 时间复杂度: **O( nk )** , 最好情况 O( n ), 最坏情况 O( n^2 )
- 空间复杂度: **O( 1 )**
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

Binary file not shown.

View File

@ -0,0 +1,129 @@
# LeetCode 120 号问题三角形最小路径和
> 本文首发于公众号图解面试算法 [图解 LeetCode](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 120 号问题三角形最小路径和题目难度为 Medium目前通过率为 64.7%
<br>
### 题目描述
给定一个三角形找出自顶向下的最小路径和每一步只能移动到下一行中相邻的结点上
相邻的结点 在这里指的是 下标 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点
**示例 1:**
```
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
```
自顶向下的最小路径和为 112 + 3 + 5 + 1 = 11
**说明**
如果你可以只使用 O(n) 的额外空间n 为三角形的总行数来解决这个问题那么你的算法会很加分
<br>
### 题目解析
给定一个三角形数组需要求出从上到下的最小路径和再确定这道题目可以用动态规划来解后可以按照四个步骤来分析
* 问题拆解
这里的总问题是求出最小的路径和路径是这里的分析重点路径是由一个个元素组成的`[i][j]` 位置的元素经过这个元素的路径肯定也会经过 `[i - 1][j]` 或者 `[i - 1][j - 1]`因此经过一个元素的路径和可以通过这个元素上面的一个或者两个元素的路径和得到
* 状态定义
状态的定义一般会和问题需要求解的答案联系在一起这里其实有两种方式一种是考虑路径从上到下另外一种是考虑路径从下到上因为元素的值是不变的所以路径的方向不同也不会影响最后求得的路径和如果是从上到下你会发现在考虑下面元素的时候起始元素的路径只会从 [i - 1][j] 获得每行当中的最后一个元素的路径只会从 [i - 1][j - 1] 获得中间二者都可这样不太好实现因此这里考虑从下到上的方式状态的定义就变成了 **最后一行元素到当前元素的最小路径和**对于 [0][0] 这个元素来说最后状态表示的就是我们的最终答案
* 递推方程
状态定义 中我们已经定义好了状态递推方程就出来了
```
dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]
```
* 实现
这里初始化时我们需要将最后一行的元素填入状态数组中然后就是按照前面分析的策略从下到上计算即可
这里有一个小小的空间上面的优化就是每次我们更新状态(dp)数组都是基于之前的结果我们并不需要知道之前的之前的结果平行的状态之间也没有相互影响因此只用开一维数组即可
<br>
### 代码实现空间优化前
```java
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[][] dp = new int[n][n];
List<Integer> lastRow = triangle.get(n - 1);
for (int i = 0; i < n; ++i) {
dp[n - 1][i] = lastRow.get(i);
}
for (int i = n - 2; i >= 0; --i) {
List<Integer> row = triangle.get(i);
for (int j = 0; j < i + 1; ++j) {
dp[i][j] = Math.min(dp[i + 1][j], dp[i + 1][j + 1]) + row.get(j);
}
}
return dp[0][0];
}
```
<br>
### 代码实现空间优化后
```java
public int minimumTotal(List<List<Integer>> triangle) {
int n = triangle.size();
int[] dp = new int[n];
List<Integer> lastRow = triangle.get(n - 1);
for (int i = 0; i < n; ++i) {
dp[i] = lastRow.get(i);
}
for (int i = n - 2; i >= 0; --i) {
List<Integer> row = triangle.get(i);
for (int j = 0; j < i + 1; ++j) { // i + 1 == row.size()
dp[j] = Math.min(dp[j], dp[j + 1]) + row.get(j);
}
}
return dp[0];
}
```
<br>
### 动画描述
![](../Animation/120.gif)
<br>
### 复杂度分析
时空复杂度从代码中都清晰可见我们必须遍历三角形中的每个元素时间复杂度就是 `O(1 + 2 + ... + n)`也就是 `O(n^2)`空间复杂度经过优化后是 `O(n)`
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

View File

@ -0,0 +1,50 @@
# LeetCode 137 号问题只出现一次的数字 II
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 137 号问题只出现一次的数字 II题目难度为 Medium目前通过率为 66.7%
### 题目描述
给定一个非空整数数组除了某个元素只出现一次以外其余每个元素均出现了三次找出那个只出现了一次的元素
说明
你的算法应该具有线性时间复杂度 你可以不使用额外空间来实现吗
**示例 1:**
```
输入: [2,2,3,2]
输出: 3
```
**示例 2:**
```
输入: [0,1,0,1,0,1,99]
输出: 99
```
### 题目解析
相比 [Single Number](https://leetcode.com/problems/single-number/)输入数组的条件变了数组中除了其中的一个元素只出现了一次其余的元素都出现了 **** 最后的问题还是让你找出这个只出现一次的元素这道题目一开始看起来从位运算思考貌似是不可能的但如果你从集合的角度去思考或许可以想到解法如果我们遍历数组里面的元素在遍历的过程中我们会发现 **对于每个元素来说只有三种情况出现一次出现两次出现三次**因为我们要找的是出现一次的那个元素而且最终除了我们要找的元素其他所有的元素都会出现三次因此我们需要想办法排除掉出现三次的元素一开始的时候可以想我们用两个集合集合 1 用于存放出现一次的元素集合 2 用于存放出现两次的元素于是我们可以发现下面的逻辑对应关系
```
如果遍历到的元素不在集合 1 也不在集合 2 该元素第一次出现加入集合 1
如果遍历到的元素在集合 1 不在集合 2 该元素第二次出现移出集合 1加入集合 2
如果遍历到的元素不在集合 1 在集合 2 该元素第三次出现移出集合 2
```
上面的逻辑对应关系你应该很容易理解但是我想说的是通过位操作可以做到这一点我们不需要真正的集合我们只需要用一个整数来代替集合即可怎么解释呢假设我们用整数 `ones` 表示集合 1整数 `twos` 表示集合 2这两个整数的值初始化均为 0`ones ^ ele[i]` 表示把元素 `ele[i]` 加入到集合 1 如果说下一个元素 `ele[i + 1]` 来了并且 `ele[i] != ele[i + 1]`那么 `ones ^ ele[i] ^ ele[i + 1]` 肯定会产生一个不为零的值至于这个值是多少你不用关心但如果 `ele[i] == ele[i + 1]`那么 `ones ^ ele[i] ^ ele[i + 1]` 的结果肯定为 0到这里你应该知道通过异或运算我们已经可以做到将出现一次的元素加入集合 1将出现两次的元素移出集合 1但是这还不够因为元素还有可能出现三次如果仅仅是上面的异或表达式第三次出现的元素还是会被加入到集合 1我们还需要保证该元素不在集合 2 `(ones ^ ele[i]) & (~twos)` 就可以保证这一点对集合 2 来说也是一样的`(twos ^ ele[i]) & (~ones)` 保证将不存在于集合 1 且不存在集合 2 中的元素加入到集合 2如果我们先更新集合 1再更新集合 2就可以实现我们之前说的逻辑对应关系说到这里如果你还是不理解那么你 **可以尝试把一个元素看作是一堆值为 1 bit 位的组合**比如 12 的二进制是 `0001 0100`如果说 12 出现了三次那么从右往左数第三位和第五位 bit 的就出现了三次我们把这个结论放在数组中也是一样的对于那些出现了 3 的整数倍次的 bits 位我们要进行消除找到那些出现了 `3 * n + 1` 次的 bit 将它们组合在一起就是我们要找的元素上面的位运算做的就是这个事情与其说把元素放入集合中我们也可以说 **将元素的所有值为 1 bit 位放入集合中**这样会更好理解些
<br>
### 动画演示
![](../Animation/137.gif)
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

View File

@ -0,0 +1,71 @@
# LeetCode 260 号问题只出现一次的数字 III
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上第 260 号问题只出现一次的数字 III题目难度为 Medium目前通过率为 72.1%
## 题目描述
给定一个整数数组 `nums`其中恰好有两个元素只出现一次其余所有元素均出现两次 找出只出现一次的那两个元素
**示例**
```
输入: [1,2,1,3,2,5]
输出: [3,5]
```
**注意**
1. 结果输出的顺序并不重要对于上面的例子 [5, 3] 也是正确答案
2. 你的算法应该具有线性时间复杂度你能否仅使用常数空间复杂度来实现
<br>
## 题目解析
第三道题目和第一道题目只变化了一点就是输入数组中除了 **** 个元素出现了一次其余的都出现了两次我们依然可以从第一道题目的解法去思考这道题如果我们还是按照第一题的做法最后我们得到的答案将会是 `ele1 ^ ele2` 的结果我们需要思考如何从这个结果出发得到 `ele1` `ele2`首先思考一个问题 `ele1 ^ ele2` 的结果具体是什么或者说里面有什么样的信息异或操作是将相同的 bit 位置 0相异的置 1也就是说 **`ele1` `ele2` 异或的结果中为 1 bit 位是两个元素相异的 bit 再进一步讲我们可以用这个 bit 位来区分两个元素**于是在第一题的基础之上用一个 bit 位作为判断条件来决定当前遍历到的元素和那个值进行异或因为这时我们要求的值有两个
从上面这些题目中你可以看到位运算的强大三道系列题目的时间复杂度均为 `O(n)`但是位运算是更加底层的运算实际时间消耗会比正常操作要更快一些在理解位运算的时候**试着把 bit 作为最小单位去思考或许会有不一样的发现**
<br>
### 代码实现
```java
public int[] singleNumber(int[] nums) {
if (nums == null || nums.length == 0) {
return new int[2];
}
int different = 0;
for (int i : nums) {
different ^= i;
}
// 这个操作是取 different 从左往右最后一个为 1 bit
different &= -different;
int[] ans = {0, 0};
for (int i : nums) {
if ((different & i) == 0) {
ans[0] ^= i;
} else {
ans[1] ^= i;
}
}
return ans;
}
```
<br>
### 动画演示
![](../Animation/260.gif)
![](../../Pictures/qrcode.jpg)

View File

@ -0,0 +1,95 @@
# 530. 二叉搜索树的最小绝对差
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客https://www.algomooc.com
题目来源于 LeetCode 上 530. 二叉搜索树的最小绝对差. 是关于树的一道题。
## 题目
给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
 
示例:
```
输入:
1
\
3
/
2
输出:
1
解释:
最小绝对差为 1其中 2 和 1 的差的绝对值为 1或者 2 和 3
```
提示:
树中至少有 2 个节点。
本题与 783 https://leetcode-cn.com/problems/minimum-distance-between-bst-nodes/ 相同
## 题目解析
计算树中任意两节点的差的绝对值的最小值,那么肯定是要遍历树,然后相邻节点求差对比是不是最小的。
二叉树的遍历有三种,前序遍历,中序遍历,后序遍历。
题目中给的是二叉搜索树,二叉搜索树有一个特色,就是中序遍历出来的结果,值是按照从小到大排列的。
所以我们只要中序遍历,保存上一个节点,然后遍历的时候取得当前节点和上一个节点的值的绝对值,如果比当前最小差还要小,那么更新最小差。
中序遍历是遍历左子树,然后根节点,最后是右子树,我们用递归去实现。
## 动画理解
<video id="video" controls="" preload="none" >
<source id="mp4" src="../Animation/0530.m4v" type="video/mp4">
</video>
## 参考代码
```javaScript
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var getMinimumDifference = function(root) {
let min = Number.MAX_VALUE
let preNode = null
var travelTree = function (node) {
if (node) {
travelTree(node.left)
if(preNode) {
min = Math.min(min, Math.abs(preNode.val - node.val))
}
preNode = node
travelTree(node.right)
}
}
travelTree(root)
return min
};
```
## 复杂度分析
时间复杂度O(N)N为树中节点个数。
空间复杂度O(log(N))。
![](../../Pictures/qrcode.jpg)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

View File

@ -0,0 +1,173 @@
# LeetCode 图解 |
> 本文首发于公众号图解面试算法 [图解 LeetCode](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步博客https://www.algomooc.com
本题解作者nettee
## 题目描述
给定一个包含了一些 `0` `1` 的非空二维数组 `grid`
一个**岛屿**是由一些相邻的 `1` (代表土地) 构成的组合这里的相邻要求两个 `1` 必须在水平或者竖直方向上相邻你可以假设 `grid` 的四个边缘都被 `0`代表水包围着
找到给定的二维数组中最大的岛屿面积(如果没有岛屿则返回面积为 `0`)
**示例 1:**
```
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
```
对于上面这个给定矩阵应返回 6注意答案不应该是 11 因为岛屿只能包含水平或垂直的四个方向的 `1`
**示例 2:**
```
[[0,0,0,0,0,0,0,0]]
```
对于上面这个给定的矩阵, 返回 0
注意: 给定的矩阵 `grid` 的长度和宽度都不超过 50
## 题目解析
这道题的主要思路是深度优先搜索每次走到一个是 1 的格子就搜索整个岛屿并计算当前岛屿的面积最后返回岛屿面积的最大值
网格可以看成是一个无向图的结构每个格子和它上下左右的四个格子相邻如果四个相邻的格子坐标合法且是陆地就可以继续搜索
在深度优先搜索的时候要注意避免重复遍历我们可以把已经遍历过的陆地改成 2这样遇到 2 我们就知道已经遍历过这个格子了不进行重复遍历
## 动画理解
![](../Animation/Animation.gif)
## 参考代码
C++ 代码
```C++
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int res = 0;
for (int r = 0; r < grid.size(); r++) {
for (int c = 0; c < grid[0].size(); c++) {
if (grid[r][c] == 1) {
int a = area(grid, r, c);
res = max(res, a);
}
}
}
return res;
}
int area(vector<vector<int>>& grid, int r, int c) {
if (!(inArea(grid, r, c))) {
return 0;
}
if (grid[r][c] != 1) {
return 0;
}
grid[r][c] = 2;
return 1
+ area(grid, r - 1, c)
+ area(grid, r + 1, c)
+ area(grid, r, c - 1)
+ area(grid, r, c + 1);
}
bool inArea(vector<vector<int>>& grid, int r, int c) {
return 0 <= r && r < grid.size()
&& 0 <= c && c < grid[0].size();
}
};
```
Java 代码
```Java
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int res = 0;
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[0].length; c++) {
if (grid[r][c] == 1) {
int a = area(grid, r, c);
res = Math.max(res, a);
}
}
}
return res;
}
int area(int[][] grid, int r, int c) {
if (!inArea(grid, r, c)) {
return 0;
}
if (grid[r][c] != 1) {
return 0;
}
grid[r][c] = 2;
return 1
+ area(grid, r - 1, c)
+ area(grid, r + 1, c)
+ area(grid, r, c - 1)
+ area(grid, r, c + 1);
}
boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
}
```
Python 代码
```Python
class Solution:
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
res = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == 1:
a = self.area(grid, r, c)
res = max(res, a)
return res
def area(self, grid: List[List[int]], r: int, c: int) -> int:
if not self.inArea(grid, r, c):
return 0
if grid[r][c] != 1:
return 0
grid[r][c] = 2
return 1 \
+ self.area(grid, r - 1, c) \
+ self.area(grid, r + 1, c) \
+ self.area(grid, r, c - 1) \
+ self.area(grid, r, c + 1)
def inArea(self, grid: List[List[int]], r: int, c: int) -> bool:
return 0 <= r < len(grid) and 0 <= c < len(grid[0])
```
## 复杂度分析
设网格的边长为 n则时间复杂度为 O(n²)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View File

@ -0,0 +1,121 @@
## LeetCode第942号问题增减字符串匹配
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步个人博客www.zhangxiaoshuai.fun
本题在leetcode中题目序号942属于easy级别目前通过率为71.4%
### 题目描述
```
给定只含 "I"增大 "D"减小的字符串 S N = S.length
返回 [0, 1, ..., N] 的任意排列 A 使得对于所有 i = 0, ..., N-1都有
如果 S[i] == "I"那么 A[i] < A[i+1]
如果 S[i] == "D"那么 A[i] > A[i+1]
示例 1
输出"IDID"
输出[0,4,1,3,2]
示例 2
输出"III"
输出[0,1,2,3]
示例 3
输出"DDI"
输出[3,2,0,1]
提示
1 <= S.length <= 10000
S 只包含字符 "I" "D"
```
**题目分析**
```
题目中的意思很明确我们只要满足给出的两个条件即可
1.假如字符串的长度为N那么目标数组的长度就为N+1
2.数组中的数字都是从0~N且没有重复
3.遇见I要增加遇见D要减少
```
### GIF动画演示
![](../Animation/0942-di-String-Match01.gif)
### 代码
```java
//这里搬运下官方的解法
public int[] diStringMatch(String S) {
int N = S.length();
int lo = 0, hi = N;
int[] ans = new int[N + 1];
for (int i = 0; i < N; ++i) {
if (S.charAt(i) == 'I')
ans[i] = lo++;
else
ans[i] = hi--;
}
ans[N] = lo;
return ans;
}
```
**虽然上述代码很简洁好像已经不需要我们去实现什么但是满足条件的序列并不止一种官方的好像只能通过一种下面的代码虽然有些冗余但是得出的序列是满足题意要求的但是并不能AC**
### 思路
```
(1)如果遇见的是I那么对应数组当前位置的数字要小于它右边的第一个数字
(2)如果遇见的是D那么对应数组当前位置的数字要大于它右边的第一个数字
首先对目标数组进行初始化赋值0~N
我们开始遍历字符串如果遇见I就判断对应数组该位置上的数是否满足1号条件
如果满足跳过本次循环如果不满足交换两个数字的位置
对于D也是同样的思路
```
### GIF动画演示
![](../Animation/0942-di-String-Match02.gif)
### 代码
```java
public int[] diStringMatch(String S) {
int[] res = new int[S.length()+1];
String[] s = S.split("");
for (int i = 0; i < res.length; i++) {
res[i] = i;
}
for (int i = 0; i < s.length; i++) {
if (s[i].equals("I")) {
//判断指定位置的数字是否符合条件
if (res[i] < res[i + 1]) {
continue;
} else {
//交换两个数字的位置
res[i] = res[i] ^ res[i+1];
res[i+1] = res[i] ^ res[i+1];
res[i] = res[i] ^ res[i+1];
}
} else {
if (res[i] > res[i + 1]) {
continue;
} else {
res[i] = res[i] ^ res[i+1];
res[i+1] = res[i] ^ res[i+1];
res[i] = res[i] ^ res[i+1];
}
}
}
return res;
}
```
**以上内容如有错误不当之处欢迎批评指正**

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

View File

@ -0,0 +1,68 @@
## LeetCode第1054号问题距离相等的条形码
> 本文首发于公众号图解面试算法 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一
>
> 同步个人博客www.zhangxiaoshuai.fun
**本题选自leetcode第1054号问题medium级别目前通过率33.3%**
**题目描述**
在一个仓库里有一排条形码其中第 i 个条形码为 barcodes[i]
请你重新排列这些条形码使其中两个相邻的条形码不能相等
你可以返回任何满足该要求的答案此题保证存在答案
示例 1
输入[1,1,1,2,2,2]
输出[2,1,2,1,2,1]
示例 2
输入[1,1,1,1,2,2,3,3]
输出[1,3,1,3,2,1,2,1]
提示
1 <= barcodes.length <= 10000
1 <= barcodes[i] <= 10000
### 题目分析
1.首先我们需要将每个条形码和出现的次数作一记录为了存取方便这里使用数组题目中已经给出了数组的最大和最小长度进行操作;
2.找出其中出现最多次数的条形码拿到该barcode和count;
3.先将出现次数最多的条形码存入目标数组中偶数位或者奇数位并对记录数组作一更新
4.随后将剩余的barcode填充进目标数组中
### GIF动画展示
![](../Animation/1054-rearrangeBarcodes.gif)
### 代码
```java
public static int[] rearrangeBarcodes(int[] barcodes){
int[] address = new int[10001];
for (int barcode : barcodes)
address[barcode]++;
// 找到出现次数最多的barcode
int maxCode = 0, maxCount = 0;
for (int i = 0; i < address.length; i++) {
if (maxCount < address[i]) {
maxCode = i;
maxCount = address[i];
}
}
int index = 0;
// 先填充最大的那一位barcode
for (; address[maxCode] > 0; index += 2) {
barcodes[index] = maxCode;
address[maxCode]--;
}
// 继续填充剩余的条形码
for (int i = 1; i < address.length; i++) {
while (address[i] > 0) {
//偶数位填充完毕
if (index >= barcodes.length) index = 1;
barcodes[index] = i;
address[i]--;
index += 2;
}
}
return barcodes;
}
```