From 17cb4b45c7c53cf50ba91815b0f8feb9a5aa4e5a Mon Sep 17 00:00:00 2001 From: programmercarl <826123027@qq.com> Date: Fri, 10 Mar 2023 14:02:32 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=9B=BE=E5=BA=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- problems/0005.最长回文子串.md | 4 +- problems/0017.电话号码的字母组合.md | 4 +- ...0019.删除链表的倒数第N个节点.md | 3 +- problems/0020.有效的括号.md | 9 +- problems/0035.搜索插入位置.md | 47 +++-- problems/0037.解数独.md | 50 +++--- problems/0039.组合总和.md | 66 ++++--- problems/0040.组合总和II.md | 4 +- problems/0042.接雨水.md | 99 +++++----- problems/0045.跳跃游戏II.md | 7 +- problems/0046.全排列.md | 2 +- problems/0047.全排列II.md | 32 ++-- problems/0051.N皇后.md | 4 +- problems/0052.N皇后II.md | 4 +- .../0053.最大子序和(动态规划).md | 2 +- problems/0054.螺旋矩阵.md | 3 +- problems/0055.跳跃游戏.md | 3 +- problems/0056.合并区间.md | 2 +- problems/0062.不同路径.md | 46 +++-- problems/0063.不同路径II.md | 50 +++--- problems/0070.爬楼梯.md | 3 +- problems/0072.编辑距离.md | 4 +- problems/0077.组合.md | 95 +++++----- problems/0077.组合优化.md | 2 +- problems/0078.子集.md | 4 +- problems/0090.子集II.md | 2 +- problems/0093.复原IP地址.md | 6 +- problems/0096.不同的二叉搜索树.md | 22 ++- problems/0098.验证二叉搜索树.md | 4 +- problems/0101.对称二叉树.md | 4 +- problems/0102.二叉树的层序遍历.md | 170 ++++++++++-------- problems/0104.二叉树的最大深度.md | 8 +- ...序与后序遍历序列构造二叉树.md | 84 ++++----- ...将有序数组转换为二叉搜索树.md | 3 +- problems/0110.平衡二叉树.md | 41 +++-- problems/0111.二叉树的最小深度.md | 7 +- problems/0112.路径总和.md | 117 +++++++----- problems/0121.买卖股票的最佳时机.md | 3 +- .../0122.买卖股票的最佳时机II.md | 3 +- .../0123.买卖股票的最佳时机III.md | 3 +- problems/0132.分割回文串II.md | 2 +- problems/0135.分发糖果.md | 6 +- problems/0139.单词拆分.md | 3 +- problems/0142.环形链表II.md | 19 +- .../0188.买卖股票的最佳时机IV.md | 2 +- problems/0198.打家劫舍.md | 2 +- problems/0203.移除链表元素.md | 53 ++++-- problems/0206.翻转链表.md | 3 +- problems/0209.长度最小的子数组.md | 2 +- problems/0213.打家劫舍II.md | 6 +- problems/0216.组合总和III.md | 21 ++- .../0222.完全二叉树的节点个数.md | 6 +- problems/0226.翻转二叉树.md | 6 +- ...35.二叉搜索树的最近公共祖先.md | 3 +- .../0236.二叉树的最近公共祖先.md | 9 +- problems/0257.二叉树的所有路径.md | 4 +- problems/0279.完全平方数.md | 3 +- problems/0300.最长上升子序列.md | 2 +- ...09.最佳买卖股票时机含冷冻期.md | 5 +- problems/0322.零钱兑换.md | 2 +- problems/0332.重新安排行程.md | 6 +- problems/0337.打家劫舍III.md | 3 +- problems/0343.整数拆分.md | 2 +- problems/0349.两个数组的交集.md | 2 +- problems/0376.摆动序列.md | 4 +- problems/0377.组合总和Ⅳ.md | 2 +- problems/0392.判断子序列.md | 9 +- problems/0404.左叶子之和.md | 6 +- problems/0406.根据身高重建队列.md | 2 +- problems/0416.分割等和子集.md | 3 +- .../0450.删除二叉搜索树中的节点.md | 3 +- .../0452.用最少数量的箭引爆气球.md | 2 +- problems/0474.一和零.md | 5 +- problems/0491.递增子序列.md | 7 +- problems/0494.目标和.md | 2 +- problems/0501.二叉搜索树中的众数.md | 4 +- problems/0513.找树左下角的值.md | 5 +- problems/0516.最长回文子序列.md | 20 ++- problems/0518.零钱兑换II.md | 2 +- .../0530.二叉搜索树的最小绝对差.md | 4 +- ...38.把二叉搜索树转换为累加树.md | 6 +- .../0583.两个字符串的删除操作.md | 2 +- problems/0617.合并二叉树.md | 2 +- problems/0647.回文子串.md | 4 +- problems/0654.最大二叉树.md | 2 +- problems/0669.修剪二叉搜索树.md | 35 ++-- .../0673.最长递增子序列的个数.md | 2 +- problems/0674.最长连续递增序列.md | 3 +- problems/0700.二叉搜索树中的搜索.md | 5 +- .../0701.二叉搜索树中的插入操作.md | 3 +- problems/0704.二分查找.md | 5 +- problems/0707.设计链表.md | 6 +- problems/0718.最长重复子数组.md | 6 +- problems/0739.每日温度.md | 90 +++++----- problems/0763.划分字母区间.md | 3 +- problems/0968.监控二叉树.md | 57 +++--- problems/1035.不相交的线.md | 3 +- .../1049.最后一块石头的重量II.md | 2 +- problems/1143.最长公共子序列.md | 5 +- ...时了,此时的n究竟是多大?.md | 15 +- problems/二叉树理论基础.md | 40 +++-- problems/二叉树的迭代遍历.md | 2 +- ...杂度,你不知道的都在这里!.md | 6 +- ...时了,此时的n究竟是多大?.md | 14 +- problems/前序/代码风格.md | 2 +- ...杂度,你不知道的都在这里!.md | 6 +- ...了解自己代码的内存消耗么?.md | 12 +- problems/前序/程序员简历.md | 2 +- ...算法的时间与空间复杂度分析.md | 9 +- ...一讲递归算法的时间复杂度!.md | 4 +- .../20201003二叉树周末总结.md | 2 +- .../周总结/20201107回溯周末总结.md | 11 +- .../周总结/20201112回溯周末总结.md | 16 +- .../周总结/20201203贪心周末总结.md | 11 +- .../20201210复杂度分析周末总结.md | 3 +- .../周总结/20201217贪心周末总结.md | 5 +- .../周总结/20201224贪心周末总结.md | 10 +- .../周总结/20210114动规周末总结.md | 13 +- .../周总结/20210121动规周末总结.md | 10 +- .../周总结/20210128动规周末总结.md | 5 +- .../周总结/20210225动规周末总结.md | 13 +- .../周总结/20210304动规周末总结.md | 8 +- problems/哈希表理论基础.md | 34 ++-- problems/回溯总结.md | 86 +++++---- ...溯算法去重问题的另一种写法.md | 5 +- problems/回溯算法理论基础.md | 4 +- problems/数组理论基础.md | 6 +- problems/栈与队列理论基础.md | 7 +- ...高重建队列(vector原理讲解).md | 15 +- problems/背包总结篇.md | 2 +- problems/背包理论基础01背包-1.md | 72 ++++---- problems/背包理论基础01背包-2.md | 2 +- .../背包问题理论基础完全背包.md | 6 +- problems/链表理论基础.md | 21 ++- 134 files changed, 1169 insertions(+), 829 deletions(-) diff --git a/problems/0005.最长回文子串.md b/problems/0005.最长回文子串.md index 4d48f184..b1987b87 100644 --- a/problems/0005.最长回文子串.md +++ b/problems/0005.最长回文子串.md @@ -108,7 +108,7 @@ dp[i][j]可以初始化为true么? 当然不行,怎能刚开始就全都匹 dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图: - + 如果这矩阵是从上到下,从左到右遍历,那么会用到没有计算过的dp[i + 1][j - 1],也就是根据不确定是不是回文的区间[i+1,j-1],来判断了[i,j]是不是回文,那结果一定是不对的。 @@ -142,7 +142,7 @@ for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 举例,输入:"aaa",dp[i][j]状态如下: - + **注意因为dp[i][j]的定义,所以j一定是大于等于i的,那么在填充dp[i][j]的时候一定是只填充右上半部分**。 diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index df038806..d1135497 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -13,7 +13,7 @@ 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 - + 示例: * 输入:"23" @@ -66,7 +66,7 @@ const string letterMap[10] = { 例如:输入:"23",抽象为树形结构,如图所示: - + 图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。 diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index b0641b5f..48c6dd5d 100644 --- a/problems/0019.删除链表的倒数第N个节点.md +++ b/problems/0019.删除链表的倒数第N个节点.md @@ -17,7 +17,8 @@ 示例 1: - + + 输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5] diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index 737fab86..a25129c5 100644 --- a/problems/0020.有效的括号.md +++ b/problems/0020.有效的括号.md @@ -80,14 +80,17 @@ cd a/b/c/../../ 先来分析一下 这里有三种不匹配的情况, + 1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。 - + 2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。 - + 3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。 - + + + 我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。 diff --git a/problems/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md index 4d9ee74f..58340c21 100644 --- a/problems/0035.搜索插入位置.md +++ b/problems/0035.搜索插入位置.md @@ -1,3 +1,4 @@ +
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益! 参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!
@@ -7,6 +8,7 @@
+
# 35.搜索插入位置
[力扣题目链接](https://leetcode.cn/problems/search-insert-position/)
@@ -16,18 +18,22 @@
你可以假设数组中无重复元素。
示例 1:
+
* 输入: [1,3,5,6], 5
* 输出: 2
-示例 2:
+示例 2:
+
* 输入: [1,3,5,6], 2
* 输出: 1
示例 3:
+
* 输入: [1,3,5,6], 7
* 输出: 4
示例 4:
+
* 输入: [1,3,5,6], 0
* 输出: 0
@@ -37,7 +43,7 @@
这道题目,要在数组中插入目标值,无非是这四种情况。
-
+
* 目标值在数组所有元素之前
* 目标值等于数组中某一个元素
@@ -78,13 +84,14 @@ public:
效率如下:
-
+
### 二分法
-既然暴力解法的时间复杂度是$O(n)$,就要尝试一下使用二分查找法。
+既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
-
+
+
大家注意这道题目的前提是数组是有序数组,这也是使用二分查找的基础条件。
@@ -94,7 +101,7 @@ public:
大体讲解一下二分法的思路,这里来举一个例子,例如在这个数组中,使用二分法寻找元素为5的位置,并返回其下标。
-
+
二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。
@@ -145,7 +152,7 @@ public:
* 空间复杂度:O(1)
效率如下:
-
+
### 二分法第二种写法
@@ -199,7 +206,7 @@ public:
## 其他语言版本
-### Java
+### Java
```java
class Solution {
@@ -226,11 +233,12 @@ class Solution {
}
}
```
+
```java
//第二种二分法:左闭右开
public int searchInsert(int[] nums, int target) {
int left = 0;
- int right = nums.length;
+ int right = nums.length;
while (left < right) { //左闭右开 [left, right)
int middle = left + ((right - left) >> 1);
if (nums[middle] > target) {
@@ -290,7 +298,8 @@ impl Solution {
}
```
-### Python
+### Python
+
```python
class Solution:
def searchInsert(self, nums: List[int], target: int) -> int:
@@ -308,7 +317,8 @@ class Solution:
return right + 1
```
-### JavaScript
+### JavaScript
+
```js
var searchInsert = function (nums, target) {
let l = 0, r = nums.length - 1, ans = nums.length;
@@ -350,7 +360,7 @@ function searchInsert(nums: number[], target: number): number {
};
```
-### Swift
+### Swift
```swift
// 暴力法
@@ -383,7 +393,9 @@ func searchInsert(_ nums: [Int], _ target: Int) -> Int {
return right + 1
}
```
+
### Scala
+
```scala
object Solution {
def searchInsert(nums: Array[Int], target: Int): Int = {
@@ -404,7 +416,7 @@ object Solution {
}
```
-### PHP
+### PHP
```php
// 二分法(1):[左闭右闭]
@@ -429,11 +441,13 @@ function searchInsert($nums, $target)
return $r + 1;
}
```
+
### C
+
```c
//版本一 [left, right]左闭右闭区间
int searchInsert(int* nums, int numsSize, int target){
- //左闭右开区间 [0 , numsSize-1]
+ //左闭右开区间 [0 , numsSize-1]
int left =0;
int mid =0;
int right = numsSize - 1;
@@ -451,14 +465,15 @@ int searchInsert(int* nums, int numsSize, int target){
}
}
//数组中未找到target元素
- //target在数组所有元素之后,[left, right]是右闭区间,需要返回 right +1
+ //target在数组所有元素之后,[left, right]是右闭区间,需要返回 right +1
return right + 1;
}
```
+
```c
//版本二 [left, right]左闭右开区间
int searchInsert(int* nums, int numsSize, int target){
- //左闭右开区间 [0 , numsSize)
+ //左闭右开区间 [0 , numsSize)
int left =0;
int mid =0;
int right = numsSize;
diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md
index 18a96d58..6edd3c5b 100644
--- a/problems/0037.解数独.md
+++ b/problems/0037.解数独.md
@@ -1,3 +1,4 @@
+
@@ -5,6 +6,7 @@
@@ -5,40 +6,43 @@
@@ -5,9 +6,10 @@
@@ -5,6 +6,7 @@
@@ -7,31 +8,32 @@
+
# 第77题. 组合
[力扣题目链接](https://leetcode.cn/problems/combinations/ )
-给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
+给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。
-示例:
-输入: n = 4, k = 2
-输出:
-[
- [2,4],
- [3,4],
- [2,3],
- [1,2],
- [1,3],
- [1,4],
-]
+示例:
+输入: n = 4, k = 2
+输出:
+[
+ [2,4],
+ [3,4],
+ [2,3],
+ [1,2],
+ [1,3],
+ [1,4],
+]
-# 算法公开课
+# 算法公开课
**《代码随想录》算法视频公开课:[带你学透回溯算法-组合问题(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv),[组合问题的剪枝操作](https://www.bilibili.com/video/BV1wi4y157er),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
-# 思路
+# 思路
本题是回溯法的经典题目。
@@ -39,6 +41,7 @@
直接的解法当然是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。
代码如下:
+
```CPP
int n = 4;
for (int i = 1; i <= n; i++) {
@@ -66,7 +69,7 @@ for (int i = 1; i <= n; i++) {
**此时就会发现虽然想暴力搜索,但是用for循环嵌套连暴力都写不出来!**
-咋整?
+咋整?
回溯搜索法来了,虽然回溯法也是暴力,但至少能写出来,不像for循环嵌套k层让人绝望。
@@ -86,7 +89,7 @@ for (int i = 1; i <= n; i++) {
那么我把组合问题抽象为如下树形结构:
-
+
可以看出这棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不再重复取。
@@ -94,7 +97,7 @@ for (int i = 1; i <= n; i++) {
**每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围**。
-**图中可以发现n相当于树的宽度,k相当于树的深度**。
+**图中可以发现n相当于树的宽度,k相当于树的深度**。
那么如何在这个树上遍历,然后收集到我们要的结果集呢?
@@ -107,7 +110,7 @@ for (int i = 1; i <= n; i++) {
## 回溯法三部曲
-* 递归函数的返回值以及参数
+* 递归函数的返回值以及参数
在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。
@@ -124,25 +127,25 @@ vector
> result = new ArrayList<>();
@@ -370,7 +376,7 @@ class Solution {
}
```
-### Python
+### Python
```python
class Solution(object):
@@ -417,6 +423,7 @@ class Solution:
```
剪枝:
+
```python
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
@@ -434,7 +441,8 @@ class Solution:
return res
```
-### Go
+### Go
+
```Go
var (
path []int
@@ -452,7 +460,7 @@ func dfs(n int, k int, start int) {
tmp := make([]int, k)
copy(tmp, path)
res = append(res, tmp)
- return
+ return
}
for i := start; i <= n; i++ { // 从start开始,不往回走,避免出现重复组合
if n - i + 1 < k - len(path) { // 剪枝
@@ -465,9 +473,10 @@ func dfs(n int, k int, start int) {
}
```
-### javascript
+### javascript
剪枝:
+
```javascript
let result = []
let path = []
@@ -536,6 +545,7 @@ impl Solution {
```
剪枝
+
```Rust
impl Solution {
fn backtracking(result: &mut Vec
> levelOrderBottom(TreeNode root) {
// 利用链表可以进行 O(1) 头部插入, 这样最后答案不需要再反转
LinkedList
> ans = new LinkedList<>();
-
+
Queue
@@ -5,6 +6,7 @@
@@ -5,6 +6,7 @@
@@ -6,6 +7,7 @@
+
> 找到有没有环已经很不容易了,还要让我找到环的入口?
@@ -14,13 +16,13 @@
[力扣题目链接](https://leetcode.cn/problems/linked-list-cycle-ii/)
题意:
-给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
+给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
**说明**:不允许修改给定的链表。
-
+
## 思路
@@ -48,7 +50,7 @@
会发现最终都是这种情况, 如下图:
-
+
fast和slow各自再走一步, fast和slow就相遇了
@@ -149,20 +151,20 @@ public:
即文章[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中如下的地方:
-
+
首先slow进环的时候,fast一定是先进环来了。
如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子:
-
+
可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。
重点来了,slow进环的时候,fast一定是在环的任意一个位置,如图:
-
+
那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。
@@ -187,6 +189,7 @@ public:
Java:
+
```java
public class Solution {
public ListNode detectCycle(ListNode head) {
@@ -235,6 +238,7 @@ class Solution:
```
Go:
+
```go
func detectCycle(head *ListNode) *ListNode {
slow, fast := head, head
@@ -267,7 +271,7 @@ var detectCycle = function(head) {
let slow =head.next, fast = head.next.next;
while(fast && fast.next && fast!== slow) {
slow = slow.next;
- fast = fast.next.next;
+ fast = fast.next.next;
}
if(!fast || !fast.next ) return null;
slow = head;
@@ -374,6 +378,7 @@ ListNode *detectCycle(ListNode *head) {
```
Scala:
+
```scala
object Solution {
def detectCycle(head: ListNode): ListNode = {
diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md
index 695fac35..4fdd7bf4 100644
--- a/problems/0188.买卖股票的最佳时机IV.md
+++ b/problems/0188.买卖股票的最佳时机IV.md
@@ -129,7 +129,7 @@ for (int j = 1; j < 2 * k; j += 2) {
以输入[1,2,3,4,5],k=2为例。
-
+
最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]即红色部分就是最后求解。
diff --git a/problems/0198.打家劫舍.md b/problems/0198.打家劫舍.md
index 6002cd3a..fdb2dabf 100644
--- a/problems/0198.打家劫舍.md
+++ b/problems/0198.打家劫舍.md
@@ -85,7 +85,7 @@ for (int i = 2; i < nums.size(); i++) {
以示例二,输入[2,7,9,3,1]为例。
-
+
红框dp[nums.size() - 1]为结果。
diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md
index 88387667..99bd3580 100644
--- a/problems/0203.移除链表元素.md
+++ b/problems/0203.移除链表元素.md
@@ -1,3 +1,4 @@
+
@@ -5,6 +6,7 @@
diff --git a/problems/0206.翻转链表.md b/problems/0206.翻转链表.md
index b87c7cba..8bf61c3f 100644
--- a/problems/0206.翻转链表.md
+++ b/problems/0206.翻转链表.md
@@ -25,7 +25,8 @@
其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示:
-
+
+
之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。
diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md
index a25fc2f5..5a8f91af 100644
--- a/problems/0209.长度最小的子数组.md
+++ b/problems/0209.长度最小的子数组.md
@@ -103,7 +103,7 @@ public:
解题的关键在于 窗口的起始位置如何移动,如图所示:
-
+
可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。**
diff --git a/problems/0213.打家劫舍II.md b/problems/0213.打家劫舍II.md
index e595d2fd..0627eedb 100644
--- a/problems/0213.打家劫舍II.md
+++ b/problems/0213.打家劫舍II.md
@@ -39,15 +39,15 @@
* 情况一:考虑不包含首尾元素
-
+
* 情况二:考虑包含首元素,不包含尾元素
-
+
* 情况三:考虑包含尾元素,不包含首元素
-
+
**注意我这里用的是"考虑"**,例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 对于情况三,取nums[1] 和 nums[3]就是最大的。
diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md
index 5ca7bb90..f631c3cd 100644
--- a/problems/0216.组合总和III.md
+++ b/problems/0216.组合总和III.md
@@ -1,3 +1,4 @@
+
@@ -7,17 +8,19 @@
+
> 别看本篇选的是组合总和III,而不是组合总和,本题和上一篇77.组合相比难度刚刚好!
# 216.组合总和III
[力扣题目链接](https://leetcode.cn/problems/combination-sum-iii/)
-找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
+找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
+
* 所有数字都是正整数。
-* 解集不能包含重复的组合。
+* 解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
@@ -46,7 +49,7 @@
选取过程如图:
-
+
图中,可以看出,只有最后取到集合(1,3)和为4 符合条件。
@@ -80,6 +83,7 @@ vector
> result = new ArrayList<>();
@@ -317,6 +321,7 @@ class Solution {
```
其他方法
+
```java
class Solution {
List
> res = new ArrayList<>();
@@ -429,12 +434,12 @@ var combinationSum3 = function(k, n) {
const dfs = (path,index) => {
// 剪枝操作
if (sum > n){
- return
+ return
}
if (path.length == k) {
if(sum == n){
res.push([...path]);
- return
+ return
}
}
for (let i = index; i <= 9 - (k-path.length) + 1;i++) {
diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md
index 10ac8264..c346b6ff 100644
--- a/problems/0222.完全二叉树的节点个数.md
+++ b/problems/0222.完全二叉树的节点个数.md
@@ -152,7 +152,7 @@ public:
我来举一个典型的例子如题:
-
+
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
@@ -161,10 +161,10 @@ public:
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完全二叉树(一)如图:
-
+
完全二叉树(二)如图:
-
+
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
diff --git a/problems/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md
index 1ea9a3e6..16a5be57 100644
--- a/problems/0226.翻转二叉树.md
+++ b/problems/0226.翻转二叉树.md
@@ -11,7 +11,8 @@
翻转一棵二叉树。
-
+
+
这道题目背后有一个让程序员心酸的故事,听说 Homebrew的作者Max Howell,就是因为没在白板上写出翻转二叉树,最后被Google拒绝了。(真假不做判断,权当一个乐子哈)
@@ -33,7 +34,8 @@
如果要从整个树来看,翻转还真的挺复杂,整个树以中间分割线进行翻转,如图:
-
+
+
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md
index 1c21f2be..8353303a 100644
--- a/problems/0235.二叉搜索树的最近公共祖先.md
+++ b/problems/0235.二叉搜索树的最近公共祖先.md
@@ -15,7 +15,8 @@
例如,给定如下二叉搜索树: root = [6,2,8,0,4,7,9,null,null,3,5]
-
+
+
示例 1:
diff --git a/problems/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md
index 7b163ee5..33201def 100644
--- a/problems/0236.二叉树的最近公共祖先.md
+++ b/problems/0236.二叉树的最近公共祖先.md
@@ -17,7 +17,8 @@
例如,给定如下二叉树: root = [3,5,1,6,2,0,8,null,null,7,4]
-
+
+
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
@@ -130,7 +131,7 @@ left与right的逻辑处理; // 中
如图:
-
+
就像图中一样直接返回7,多美滋滋。
@@ -163,7 +164,7 @@ TreeNode* right = lowestCommonAncestor(root->right, p, q);
如图:
-
+
图中节点10的左子树返回null,右子树返回目标值7,那么此时节点10的处理逻辑就是把右子树的返回值(最近公共祖先7)返回上去!
@@ -184,7 +185,7 @@ else { // (left == NULL && right == NULL)
那么寻找最小公共祖先,完整流程图如下:
-
+
**从图中,大家可以看到,我们是如何回溯遍历整棵二叉树,将结果返回给头结点的!**
diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md
index 68434479..8c542cea 100644
--- a/problems/0257.二叉树的所有路径.md
+++ b/problems/0257.二叉树的所有路径.md
@@ -16,7 +16,7 @@
说明: 叶子节点是指没有子节点的节点。
示例:
-
+
# 思路
@@ -28,7 +28,7 @@
前序遍历以及回溯的过程如图:
-
+
我们先使用递归的方式,来做前序遍历。**要知道递归和回溯就是一家的,本题也需要回溯。**
diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md
index 0654e494..c329156b 100644
--- a/problems/0279.完全平方数.md
+++ b/problems/0279.完全平方数.md
@@ -94,7 +94,8 @@ for (int i = 0; i <= n; i++) { // 遍历背包
已输入n为5例,dp状态图如下:
-
+
+
dp[0] = 0
dp[1] = min(dp[0] + 1) = 1
diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md
index f4fe1c31..478837cc 100644
--- a/problems/0300.最长上升子序列.md
+++ b/problems/0300.最长上升子序列.md
@@ -82,7 +82,7 @@ for (int i = 1; i < nums.size(); i++) {
输入:[0,1,0,3,2],dp数组的变化如下:
-
+
如果代码写出来,但一直AC不了,那么就把dp数组打印出来,看看对不对!
diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md
index b4825894..d62e91c7 100644
--- a/problems/0309.最佳买卖股票时机含冷冻期.md
+++ b/problems/0309.最佳买卖股票时机含冷冻期.md
@@ -45,7 +45,7 @@ dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。
* 状态三:今天卖出股票
* 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!
-
+
j的状态为:
@@ -133,7 +133,8 @@ dp[i][3] = dp[i - 1][2];
以 [1,2,3,0,2] 为例,dp数组如下:
-
+
+
最后结果是取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。
diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md
index c718ef47..3be72565 100644
--- a/problems/0322.零钱兑换.md
+++ b/problems/0322.零钱兑换.md
@@ -106,7 +106,7 @@ dp[0] = 0;
以输入:coins = [1, 2, 5], amount = 5为例
-
+
dp[amount]为最终结果。
diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md
index 17ae9641..9bd5df7a 100644
--- a/problems/0332.重新安排行程.md
+++ b/problems/0332.重新安排行程.md
@@ -57,7 +57,7 @@
对于死循环,我来举一个有重复机场的例子:
-
+
为什么要举这个例子呢,就是告诉大家,出发机场和到达机场也会重复的,**如果在解题的过程中没有对集合元素处理好,就会死循环。**
@@ -111,7 +111,7 @@ void backtracking(参数) {
本题以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下:
-
+
开始回溯三部曲讲解:
@@ -137,7 +137,7 @@ bool backtracking(int ticketNum, vector
@@ -5,6 +6,7 @@
@@ -5,15 +6,16 @@
@@ -5,6 +6,7 @@
+
说到二叉树,大家对于二叉树其实都很熟悉了,本文呢我也不想教科书式的把二叉树的基础内容再啰嗦一遍,所以以下我讲的都是一些比较重点的内容。
@@ -28,7 +30,7 @@
如图所示:
-
+
这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
@@ -37,13 +39,13 @@
什么是完全二叉树?
-完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
+完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
**大家要自己看完全二叉树的定义,很多同学对完全二叉树其实不是真正的懂了。**
我来举一个典型的例子如题:
-
+
相信不少同学最后一个二叉树是不是完全二叉树都中招了。
@@ -60,16 +62,16 @@
下面这两棵树都是搜索树
-
+
### 平衡二叉搜索树
-平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
+平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
如图:
-
+
最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。
@@ -88,13 +90,13 @@
链式存储如图:
-
+
链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢?
其实就是用数组来存储二叉树,顺序存储的方式如图:
-
+
用数组来存储二叉树如何遍历的呢?
@@ -113,6 +115,7 @@
我这里把二叉树的几种遍历方式列出来,大家就可以一一串起来了。
二叉树主要有两种遍历方式:
+
1. 深度优先遍历:先往深走,遇到叶子节点再往回走。
2. 广度优先遍历:一层一层的去遍历。
@@ -121,11 +124,11 @@
那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
* 深度优先遍历
- * 前序遍历(递归法,迭代法)
- * 中序遍历(递归法,迭代法)
- * 后序遍历(递归法,迭代法)
+ * 前序遍历(递归法,迭代法)
+ * 中序遍历(递归法,迭代法)
+ * 后序遍历(递归法,迭代法)
* 广度优先遍历
- * 层次遍历(迭代法)
+ * 层次遍历(迭代法)
在深度优先遍历中:有三个顺序,前中后序遍历, 有同学总分不清这三个顺序,经常搞混,我这里教大家一个技巧。
@@ -140,7 +143,7 @@
大家可以对着如下图,看看自己理解的前后中序有没有问题。
-
+
最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
@@ -206,8 +209,9 @@ public class TreeNode {
Python:
+
```python
-class TreeNode:
+class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
@@ -215,6 +219,7 @@ class TreeNode:
```
Go:
+
```go
type TreeNode struct {
Val int
@@ -224,6 +229,7 @@ type TreeNode struct {
```
JavaScript:
+
```javascript
function TreeNode(val, left, right) {
this.val = (val===undefined ? 0 : val)
@@ -263,7 +269,9 @@ class TreeNode
@@ -6,6 +7,7 @@
+
## 哈希表
首先什么是 哈希表,哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。
@@ -16,7 +18,7 @@
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示:
-
+
那么哈希表能解决什么问题呢,**一般哈希表都是用来快速判断一个元素是否出现集合里。**
@@ -34,7 +36,7 @@
哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
-
+
如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?
@@ -50,7 +52,7 @@
如图所示,小李和小王都映射到了索引下标 1 的位置,**这一现象叫做哈希碰撞**。
-
+
一般哈希碰撞有两种解决方法, 拉链法和线性探测法。
@@ -58,7 +60,7 @@
刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了
-
+
(数据规模是dataSize, 哈希表的大小为tableSize)
@@ -70,7 +72,7 @@
例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:
-
+
其实关于哈希碰撞还有非常多的细节,感兴趣的同学可以再好好研究一下,这里我就不再赘述了。
@@ -86,19 +88,19 @@
在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示:
-|集合 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率|
-|---|---| --- |---| --- | --- | ---|
-|std::set |红黑树 |有序 |否 |否 | O(log n)|O(log n) |
-|std::multiset | 红黑树|有序 |是 | 否| O(logn) |O(logn) |
-|std::unordered_set |哈希表 |无序 |否 |否 |O(1) | O(1)|
+| 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
+| ------------------ | -------- | -------- | ---------------- | ------------ | -------- | -------- |
+| std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) |
+| std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) |
+| std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) |
std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。
-|映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率|
-|---|---| --- |---| --- | --- | ---|
-|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(logn)|O(logn) |
-|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(log n) |O(log n) |
-|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)|
+| 映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 |
+| ------------------ | -------- | -------- | ---------------- | ------------ | -------- | -------- |
+| std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) |
+| std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) |
+| std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) |
std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。
@@ -114,7 +116,7 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底
实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。
-
+
## 总结
diff --git a/problems/回溯总结.md b/problems/回溯总结.md
index 58fb42f8..21d78bc2 100644
--- a/problems/回溯总结.md
+++ b/problems/回溯总结.md
@@ -1,12 +1,14 @@
+
+
可以配合我的B站视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/) 一起学习!
@@ -110,7 +110,7 @@ if (终止条件) {
如图:
-
+
注意图中,我特意举例集合大小和孩子的数量是相等的!
diff --git a/problems/数组理论基础.md b/problems/数组理论基础.md
index c49e9ec8..67b7b20d 100644
--- a/problems/数组理论基础.md
+++ b/problems/数组理论基础.md
@@ -82,7 +82,8 @@ int main() {
如图:
-
+
+
**所以可以看出在C++中二维数组在地址空间上是连续的**。
@@ -112,7 +113,8 @@ public static void test_arr() {
所以Java的二维数组可能是如下排列的方式:
-
+
+
这里面试中数组相关的理论知识就介绍完了。
diff --git a/problems/栈与队列理论基础.md b/problems/栈与队列理论基础.md
index ee4506f4..0075deb6 100644
--- a/problems/栈与队列理论基础.md
+++ b/problems/栈与队列理论基础.md
@@ -10,7 +10,7 @@
如图所示:
-
+
那么我这里再列出四个关于栈的问题,大家可以思考一下。以下是以C++为例,使用其他编程语言的同学也对应思考一下,自己使用的编程语言里栈和队列是什么样的。
@@ -44,7 +44,8 @@ C++标准库是有多个版本的,要知道我们使用的STL是哪个版本
来说一说栈,栈先进后出,如图所示:
-
+
+
栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。
@@ -56,8 +57,8 @@ C++标准库是有多个版本的,要知道我们使用的STL是哪个版本
从下图中可以看出,栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。
-
+
**我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。**
diff --git a/problems/根据身高重建队列(vector原理讲解).md b/problems/根据身高重建队列(vector原理讲解).md
index 315b6da1..94d94e38 100644
--- a/problems/根据身高重建队列(vector原理讲解).md
+++ b/problems/根据身高重建队列(vector原理讲解).md
@@ -1,3 +1,4 @@
+
@@ -5,6 +6,7 @@
@@ -5,6 +6,7 @@