未验证 提交 c11ab1e5 编写于 作者: C chilimyan 提交者: GitHub

Merge pull request #4 from MisterBooo/master

更新项目
......@@ -57,6 +57,7 @@ public:
record[nums[i]] = i;
}
return {};
}
};
......@@ -66,4 +67,4 @@ public:
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
![](../../Pictures/qrcode.jpg)
# 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)
如图,我们要找到一组A,B,满足上面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)
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;
}
}
因为 它太大了无法显示 image diff 。你可以改为 查看blob
# 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)
\ No newline at end of file
# 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]
]
```
自顶向下的最小路径和为 11(即,2 + 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)
\ No newline at end of file
# 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)
\ No newline at end of file
因为 它太大了无法显示 image diff 。你可以改为 查看blob
# 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)
\ No newline at end of file
# 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)
\ No newline at end of file
# 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²)。
\ No newline at end of file
## 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;
}
```
**以上内容如有错误、不当之处,欢迎批评指正。**
\ No newline at end of file
## 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;
}
```
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册