Unverified Commit 3a802a7c authored by chilimyan's avatar chilimyan Committed by GitHub
Browse files

Merge pull request #3 from MisterBooo/master

更新代码
parents 53cc7424 6a20215c
# LeetCode 第 36 号问题:有效的数独
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 第 36 号问题:有效的数独.
## 题目
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
示例 1:
```
输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
```
示例 2:
```
输入:
[
["8","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。
```
示例 3:
```
输入: [1,3,5,6], 7
输出: 4
```
示例 4:
```
输入: [1,3,5,6], 0
输出: 0
```
## 思路解析
### 一次遍历法
#### 思路
这道题因为需要判断数值是否存在,所以用Hash Map是一个很好的选择。
因为每一行、每一列、每一格都是需要单独进行判断的,所以需要建立三个长度为9的HashMap数组,分别存放行、列、格的数值。
通过一个二层循环遍历这个9*9的数组,把当前格的数值存放到对应的HashMap中,判断之前是否已经存放过了,如果已经存放过那就退出,返回false,如果是.的话那就跳过,这样只需要遍历一边就可以了。
### 动画理解
![](../Animation/HashMap.gif)
#### 代码实现
```java
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
public boolean isValidSudoku(char[][] board) {
HashMap[] row = new HashMap[9];
HashMap[] column = new HashMap[9];
HashMap[] box = new HashMap[9];
for (int i = 0; i < 9; i++) {
row[i] = new HashMap(9);
column[i] = new HashMap(9);
box[i] = new HashMap(9);
}
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
continue;
}
int boxIndex=i / 3 * 3 + j / 3;
if ((boolean) row[i].getOrDefault(board[i][j], true)) {
return false;
}
if ((boolean) column[j].getOrDefault(board[i][j], true)) {
return false;
}
if ((boolean) box[boxIndex].getOrDefault(board[i][j], true)) {
return false;
}
row[i].put(board[i][j], false);
column[j].put(board[i][j], false);
box[boxIndex].put(board[i][j], false);
}
}
return true;
}
}
```
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
class Solution {
public boolean isValidSudoku(char[][] board) {
HashMap[] row = new HashMap[9];
HashMap[] column = new HashMap[9];
HashMap[] box = new HashMap[9];
for (int i = 0; i < 9; i++) {
row[i] = new HashMap(9);
column[i] = new HashMap(9);
box[i] = new HashMap(9);
}
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
continue;
}
int boxIndex=i / 3 * 3 + j / 3;
if ((boolean) row[i].getOrDefault(board[i][j], true)) {
return false;
}
if ((boolean) column[j].getOrDefault(board[i][j], true)) {
return false;
}
if ((boolean) box[boxIndex].getOrDefault(board[i][j], true)) {
return false;
}
row[i].put(board[i][j], false);
column[j].put(board[i][j], false);
box[boxIndex].put(board[i][j], false);
}
}
return true;
}
}
\ No newline at end of file
## LeetCode第42号问题:接雨水
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 个人博客:www.zhangxiaoshuai.fun
**本题选择leetcode中第42题,hard级别,目前通过率50.8%#**
### 题目描述:
```txt
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
```
![](../Animation/resource.png)
### 题目分析:
通过题意,一个“凹槽”可以存储的雨水的容量取决于它前后的柱子。
### 解法一:
仔细想想,其实这跟木桶原理是有相似的地方的,针对每一个柱子,我们需要往前看和往后看,分别找出当前柱子前面最高的柱子和后面最高的柱子。
这里有**三种情况**我们需要了解:
- **当前柱子小于前后两个柱子中最矮的那个**
![01](../Animation/01.png)
**当前位置可以存储的雨水容量 = leftMax - curr = 1**
- **当前柱子等于前后两个柱子中最矮的那个**
![02](../Animation/02.png)
**当前位置可以存储的雨水容量为0**
- **当前柱子大于前后两个柱子中最矮的那个**![03](../Animation/03.png)
**因为curr < leftMax,所以当前位置无法存储雨水**
**GIF动画演示:**
![gif01](../Animation/0042-trap.gif)
### 代码:
```java
public int trap02(int[] height) {
int sum = 0;
//最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2
for (int i = 1; i < height.length - 1; i++) {
int max_left = 0;
//找出左边最高
for (int j = i - 1; j >= 0; j--) {
if (height[j] > max_left) {
max_left = height[j];
}
}
int max_right = 0;
//找出右边最高
for (int j = i + 1; j < height.length; j++) {
if (height[j] > max_right) {
max_right = height[j];
}
}
//找出两端较小的
int min = Math.min(max_left, max_right);
//只有较小的一段大于当前列的高度才会有水,其他情况不会有水
if (min > height[i]) {
sum = sum + (min - height[i]);
}
}
return sum;
}
```
可以看到,上面方法的时间复杂度达到了**O(n^2)**
**那么有没有更好的办法来解决这个问题?**
下面的方法巧妙的使用了**双指针**来解决问题:
与上述解法的思路大致是相同的,都是单个地求出当前墙可以存储雨水的容量;这种解法也是非常的巧妙,是在浏览解题区的时候碰见的,大佬还做了视频(链接放在文末),讲解的非常清楚,我大概用自己的思路来作一文字叙述:
既然使用的是**twoPointers**的思路,那么我们需要分别从数组的最前面和最后面开始,这两个指针是互不影响,都是各走各的,但是如何确定当前指针走过的地方能存放多少雨水量呢?
这个时候,我们就需要两块挡板**leftMax****rightMax**,这两块挡板最开始都是挡在最外面的墙边,随着两个指针前进,**leftMax**代表的是**left**走过的路中最高的墙,**rightMax**同理。
**那么如何计算雨水量呢?**
比较左右两个挡板的高度,然后根据两个挡板各自的指针配合计算。
- 如果左边挡板的高度小于右边的挡板高度,那么左边指针之前的雨水量取决于**leftMax**和height[left]的大小关系,如果前者大于后者,那么容量等与前者减去后者;反之,容量为0(可以参考解法一中的图来理解)
- 如果左边挡板的高度大于等于右边挡板的高度,与上一种情况基本相同,只不过是求的右边的雨水量。
- 在每次移动指针之后,我们要将挡板更新到最大值。
**其实道理也是比较简单,用宏观的思维去看待整个问题,最起码先保证两边的墙的高度(两块挡板),然后依次去到其中各个墙之间能装多少雨水的问题上。(求每次更新最高的挡板和指针指向的墙之间可以存储的雨水量)**
### 代码:
```java
public int trap(int[] height) {
if (height.length == 0) return 0;
int left = 0;
int right = height.length-1;
int leftMax = 0;
int rightMax = 0;
int result = 0;
while (left <= right) {
if (leftMax < rightMax) {
result += leftMax - height[left] > 0 ?
leftMax - height[left] : 0;
leftMax = Math.max(leftMax, height[left]);
left++;
} else {
result += rightMax - height[right] > 0 ?
rightMax - height[right] : 0;
rightMax = Math.max(rightMax, height[right]);
right--;
}
}
return result;
}
```
**时间复杂度:O(n) 空间复杂度:O(1)**
[leetcode配套视频入口](https://leetcode-cn.com/problems/trapping-rain-water/solution/javashi-pin-jiang-jie-xi-lie-trapping-rain-water-b/)
## LeetCode第58号问题:最后一个单词的长度
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 个人博客:www.zhangxiaoshuai.fun
**本题选自leetcode第58题,easy难度,目前通过率33.0%**
### 题目描述:
```txt
给定一个仅包含大小写字母和空格' '的字符串s,返回其最后一个单词的长度。
如果字符串从左向右滚动显示,那么最后一个单词就是最后出现的单词。
如果不存在最后一个单词,请返回0。
说明:一个单词是指仅由字母组成、不包含任何空格字符的最大子字符串。
示例:
输入:"Hello World"
输出:5
```
### 题目分析:
既然需要求出最后一个单词的长度,那我们直接从**字符串的末尾**开始好了;
这里末尾有两种情况:有空格和没有空格
```
(1)有空格:我们从末尾忽略掉空格,然后找到第一个遇见的字符(遇到第一个空格或者遍历完整个字符串为止)
(2)无空格:直接从末尾往前寻找即可(遇到第一个空格或者遍历完整个字符串为止)
```
### 动画gif演示:
![](../Animation/0058.gif)
### 代码:
**The first version**
```java
public int lengthOfLastWord(String s) {
if (s.length()==0) {
return 0;
}
int index = 0;
int temp = 0;
int p = s.length()-1;
while ((p-index >=0) && s.charAt(p-index) == 32) index++;
for (int i = p-index;i >= 0;i--) {
if (s.charAt(i) != 32){
temp++;
continue;
}
break;
}
return temp;
}
```
**2.代码:**
**The second version**
```java
public int lengthOfLastWord(String s) {
int len = 0;
for (int i = s.length() - 1; i >= 0; i--) {
if (s.charAt(i) != ' ') {
len++;
} else if (len != 0) {
return len;
}
}
return len;
}
```
# LeetCode 第 88 号问题:合并两个有序数组
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
题目来源于 LeetCode 上第 88 号问题:合并两个有序数组。题目难度为 Easy
### 题目描述
给你两个有序整数数组 *nums1**nums2*,请你将 *nums2* 合并到 *nums1**,*使 *nums1* 成为一个有序数组。
**说明:**
- 初始化 *nums1**nums2* 的元素数量分别为 *m**n*
- 你可以假设 *nums1* 有足够的空间(空间大小大于或等于 *m* + *n*)来保存 *nums2* 中的元素。
**示例:**
```
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
```
### 题目解析
将这个题目放到现实中就容易多了, 不信你看看.
假如你是玩具店的老板, 其中两个货架依次摆放了形状相同大小不同的小汽车, 这些小汽车都按照从小到大摆放着, 现在你想把第二个货架的小汽车移到第一个货架上, 为了顾客看起来直观, 这些小汽车要摆放的有序, 你会怎么做呢?
**很显现, 你不会把所有小汽车放到一起, 然后在一个个排序好放到第一个货架上.**
你肯定会比较两个货架小汽车的大小, 把第二个货架小汽车移到第一个货架的相应位置上.
那么问题来了, 是从小的比较呢还是从大的比较呢?
**先从小的比较来看**, 现在第二个货架第一个汽车是最小的, 那么你得把第一个货架所有玩具往后挪一位, 然后才能放下这个汽车, **好像有点费力了**.
不想费力, 我们可以先把第一个货架的玩具移到第三个货架, 比较第二个货架和第三个货架, 把比较小的汽车放到第一个货架上. 看来还得先搬移第一个货架, **需要占用其他空间了**.
**如果从后面比较呢**, **也就是先比较大的汽车**, 现在第二个货架最后一个是最大的汽车, 我只需要把最大的汽车拿到第一货架的最后面就可以了, 是不是很轻松, 这样依次比较, **不费力也不用费空间**就挪到了第一个货架后面了. 和第一个货架都比较完了, 发现第二个货架还剩一个最小的汽车, 这个时候你会发现第一个货架的第一个位置是空的, 我们直接拿过去就可以啦.
故事讲完啦, 通过这几种方法的尝试, 你也许已经发现了:
> 第一种方法 对应的算法是 **' 合并后排序 '**, 时间复杂度比较大;
>
> 第二种方法 对应的算法是 **' 双指针 + 从前向后比较 '**
>
> - 往后挪动汽车时间复杂度高
> - 移到第三个货架空间复杂度高
>
> 第三种方法 对应的算法是 **' 双指针 + 从后向前比较 '** , 省时又不占空间, 完美!
下面说下 **' 双指针 + 从后向前比较 '** 的具体思路:
1. 设置双指针, 分别指向有序数组的最后一位;
2. 从后向前
- 终止条件: 其中一个指针不在指向数组
- 比较双指针指向的值
- 大的或相同的值放到 *num1* 空间的尾部( 尾部从后向前依次填充 ), 对应的指针向前挪一位
- 循环上面步骤
3. 遍历完成后检查
- 若指向 *num2* 的指针还有效, 说明 *num2* 中还有小于 *num1* 最小值的存在
- 将这些值搬移到 *num1* 最前面
### 动画描述
<img src="../Animation/Animation.gif" alt="Animation" style="zoom:150%;" />
### 参考代码
```javascript
/**
* JavaScript 描述
* 双指针 + 从后向前
*/
var merge = function(nums1, m, nums2, n) {
let len = m + n;
while(m > 0 && n > 0){
// '>=' 相比 '>' 在某些值相同的情况下能少比较一次
nums1[--len] = nums2[n-1] >= nums1[m-1] ? nums2[--n]: nums1[--m];
}
if(n > 0){
nums1.splice(0,n,...nums2.slice(0,n));
}
};
```
### 复杂度分析
- 时间复杂度: **O( m+n )**
- 空间复杂度: **O( 1 )**
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
This image diff could not be displayed because it is too large. You can view the blob instead.
# LeetCode 第 104 号问题:二叉树的最大深度
> 本文首发于公众号「图解面试算法」,是 [图解 LeetCode ](<https://github.com/MisterBooo/LeetCodeAnimation>) 系列文章之一。
>
> 同步博客:https://www.algomooc.com
今天分享的题目来源于 LeetCode 上第 104 号问题:二叉树的最大深度。题目难度为 Easy 。
### 题目描述
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
**说明:** 叶子节点是指没有子节点的节点。
**示例 :**
给定二叉树 `[3,9,20,null,null,15,7]`
```
3
/ \
9 20
/ \
15 7
```
返回它的最大深度 3 。
### 题目解析 - DFS
最直接的办法就是使用DFS ( 深度优先搜索 ) 策略计算树的高度. 具体算法流程如下:
- **终止条件:**当前节点为空
- **返回值:**
- 节点为空时,所以返回 0
- 节点不为空时, 返回左右子树高度的最大值 + 1
### 动画描述
![](../Animation/Animation1.gif)
### 代码实现
```javascript
/**
* JavaScript 描述
* DFS
*/
var maxDepth = function(root) {
if (root == null) {
return 0;
}
let leftHeight = maxDepth(root.left);
let rightHeight = maxDepth(root.right);
return Math.max(leftHeight, rightHeight) + 1;
};
```
**精简版**
```javascript
var maxDepth = function(root) {
return !root ? 0 : Math.max(maxDepth(root.left) + 1, maxDepth(root.right) + 1) ;
};
```
### 复杂度分析
- 时间复杂度:**O(n)**, 我们每个结点只访问一次,因此时间复杂度为 O(N)
- 空间复杂度:
- 最坏情况下,树是完全不平衡的,例如每个结点只剩下左子结点,递归将会被调用 N 次(树的高度),因此保持调用栈的存储将是 O(N)。
- 最好情况下(树是完全平衡的),树的高度将是 log(N)。因此,在这种情况下的空间复杂度将是 O(log(N))
### 题目解析 - BFS
求二叉树的深度也就是求二叉树有几层了, 采用 BFS ( 广度优先搜索 ) 策略对二叉树按层遍历.
实现 BFS 就要用到 '先进先出' 的队列了, 具体算法流程如下:
- 遍历二叉树节点,依次将当前节点 和它的左右子节点入队
- 依次出队, 出队子节点重复上一步操作
### 动画描述
![](../Animation/Animation2.gif)
### 代码实现
```javascript
/**
* JavaScript 描述
* BFS
*/
var maxDepth = function(root) {
let level = 0;
if (root == null) {
return level;
}
let queue = [root];
while (queue.length) {
let len = queue.length;
while (len--) {
let curNode = queue.pop();
curNode.left && queue.unshift(curNode.left);
curNode.right && queue.unshift(curNode.right);
}
level++;
}
return level;
};
```
### 复杂度分析
- 时间复杂度:**O(n)**
- 空间复杂度:**O(N)**
![](../../Pictures/qrcode.jpg)
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment