Merge branch 'youngyangyang04:master' into master

This commit is contained in:
sharky7pb 2023-04-18 15:25:28 +08:00 committed by GitHub
commit 7e34e0242f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
175 changed files with 3402 additions and 1357 deletions

View File

@ -66,7 +66,7 @@
**这里每一篇题解,都是精品,值得仔细琢磨**。 **这里每一篇题解,都是精品,值得仔细琢磨**。
我在题目讲解中统一使用C++但你会发现下面几乎每篇题解都配有其他语言版本Java、Python、Go、JavaScript等等正是这些[热心小伙们](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)贡献的代码,当然我也会严格把控代码质量。 我在题目讲解中统一使用C++但你会发现下面几乎每篇题解都配有其他语言版本Java、Python、Go、JavaScript等等正是这些[热心小伙们](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)贡献的代码,当然我也会严格把控代码质量。
**所以也欢迎大家参与进来,完善题解的各个语言版本,拥抱开源,让更多小伙伴们受益**。 **所以也欢迎大家参与进来,完善题解的各个语言版本,拥抱开源,让更多小伙伴们受益**。

BIN
problems/.DS_Store vendored

Binary file not shown.

View File

@ -109,6 +109,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 总结 ## 总结
本题其实有四个重点: 本题其实有四个重点:
@ -213,6 +216,26 @@ impl Solution {
} }
} }
``` ```
Rust
```
use std::collections::HashMap;
impl Solution {
pub fn two_sum(nums: Vec<i32>, target: i32) -> Vec<i32> {
let mut hm: HashMap<i32, i32> = HashMap::new();
for i in 0..nums.len() {
let j = target - nums[i];
if hm.contains_key(&j) {
return vec![*hm.get(&j).unwrap(), i as i32]
} else {
hm.insert(nums[i], i as i32);
}
}
vec![-1, -1]
}
}
```
Javascript Javascript

View File

@ -108,7 +108,7 @@ dp[i][j]可以初始化为true么 当然不行,怎能刚开始就全都匹
dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图: dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图:
![647.回文子串](https://img-blog.csdnimg.cn/20210121171032473.jpg) ![647.回文子串](https://code-thinking-1253855093.file.myqcloud.com/pics/20210121171032473.jpg)
如果这矩阵是从上到下从左到右遍历那么会用到没有计算过的dp[i + 1][j - 1],也就是根据不确定是不是回文的区间[i+1,j-1],来判断了[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]状态如下: 举例,输入:"aaa"dp[i][j]状态如下:
![647.回文子串1](https://img-blog.csdnimg.cn/20210121171059951.jpg) ![647.回文子串1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210121171059951.jpg)
**注意因为dp[i][j]的定义所以j一定是大于等于i的那么在填充dp[i][j]的时候一定是只填充右上半部分**。 **注意因为dp[i][j]的定义所以j一定是大于等于i的那么在填充dp[i][j]的时候一定是只填充右上半部分**。

View File

@ -83,6 +83,10 @@ public:
}; };
``` ```
* 时间复杂度: O(n^2)
* 空间复杂度: O(n),额外的 set 开销
## 双指针 ## 双指针
**其实这道题目使用哈希法并不十分合适**因为在去重的操作中有很多细节需要注意在面试中很难直接写出没有bug的代码。 **其实这道题目使用哈希法并不十分合适**因为在去重的操作中有很多细节需要注意在面试中很难直接写出没有bug的代码。
@ -158,6 +162,10 @@ public:
}; };
``` ```
* 时间复杂度: O(n^2)
* 空间复杂度: O(1)
## 去重逻辑的思考 ## 去重逻辑的思考
### a的去重 ### a的去重

View File

@ -13,7 +13,7 @@
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
![17.电话号码的字母组合](https://img-blog.csdnimg.cn/2020102916424043.png) ![17.电话号码的字母组合](https://code-thinking-1253855093.file.myqcloud.com/pics/2020102916424043.png)
示例: 示例:
* 输入:"23" * 输入:"23"
@ -66,7 +66,7 @@ const string letterMap[10] = {
例如:输入:"23",抽象为树形结构,如图所示: 例如:输入:"23",抽象为树形结构,如图所示:
![17. 电话号码的字母组合](https://img-blog.csdnimg.cn/20201123200304469.png) ![17. 电话号码的字母组合](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123200304469.png)
图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。 图中可以看出遍历的深度,就是输入"23"的长度,而叶子节点就是我们要收集的结果,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。

View File

@ -121,6 +121,10 @@ public:
``` ```
* 时间复杂度: O(n^3)
* 空间复杂度: O(1)
## 补充 ## 补充
二级剪枝的部分: 二级剪枝的部分:

View File

@ -17,7 +17,8 @@
示例 1 示例 1
![19.删除链表的倒数第N个节点](https://img-blog.csdnimg.cn/20210510085957392.png)
![19.删除链表的倒数第N个节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20210510085957392.png)
输入head = [1,2,3,4,5], n = 2 输入head = [1,2,3,4,5], n = 2
输出:[1,2,3,5] 输出:[1,2,3,5]
@ -86,6 +87,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 其他语言版本 ## 其他语言版本
@ -129,10 +133,10 @@ class Solution:
head_dummy.next = head head_dummy.next = head
slow, fast = head_dummy, head_dummy slow, fast = head_dummy, head_dummy
while(n!=0): #fast先往前走n步 while(n>=0): #fast先往前走n+1步
fast = fast.next fast = fast.next
n -= 1 n -= 1
while(fast.next!=None): while(fast!=None):
slow = slow.next slow = slow.next
fast = fast.next fast = fast.next
#fast 走到结尾后slow的下一个节点为倒数第N个节点 #fast 走到结尾后slow的下一个节点为倒数第N个节点

View File

@ -80,14 +80,17 @@ cd a/b/c/../../
先来分析一下 这里有三种不匹配的情况, 先来分析一下 这里有三种不匹配的情况,
1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。 1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
![括号匹配1](https://img-blog.csdnimg.cn/2020080915505387.png) ![括号匹配1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020080915505387.png)
2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。 2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。
![括号匹配2](https://img-blog.csdnimg.cn/20200809155107397.png) ![括号匹配2](https://code-thinking-1253855093.file.myqcloud.com/pics/20200809155107397.png)
3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。 3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。
![括号匹配3](https://img-blog.csdnimg.cn/20200809155115779.png) ![括号匹配3](https://code-thinking-1253855093.file.myqcloud.com/pics/20200809155115779.png)
我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。 我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。
@ -132,6 +135,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
技巧性的东西没有固定的学习方法,还是要多看多练,自己灵活运用了。 技巧性的东西没有固定的学习方法,还是要多看多练,自己灵活运用了。

View File

@ -444,6 +444,8 @@ public:
}; };
``` ```
* 时间复杂度: O(n + m)
* 空间复杂度: O(m), 只需要保存字符串needle的前缀表
# 前缀表不减一C++实现 # 前缀表不减一C++实现
@ -540,6 +542,9 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n + m)
* 空间复杂度: O(m)
# 总结 # 总结

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -7,6 +8,7 @@
# 35.搜索插入位置 # 35.搜索插入位置
[力扣题目链接](https://leetcode.cn/problems/search-insert-position/) [力扣题目链接](https://leetcode.cn/problems/search-insert-position/)
@ -16,18 +18,22 @@
你可以假设数组中无重复元素。 你可以假设数组中无重复元素。
示例 1: 示例 1:
* 输入: [1,3,5,6], 5 * 输入: [1,3,5,6], 5
* 输出: 2 * 输出: 2
示例 2: 示例 2:
* 输入: [1,3,5,6], 2 * 输入: [1,3,5,6], 2
* 输出: 1 * 输出: 1
示例 3: 示例 3:
* 输入: [1,3,5,6], 7 * 输入: [1,3,5,6], 7
* 输出: 4 * 输出: 4
示例 4: 示例 4:
* 输入: [1,3,5,6], 0 * 输入: [1,3,5,6], 0
* 输出: 0 * 输出: 0
@ -37,7 +43,7 @@
这道题目,要在数组中插入目标值,无非是这四种情况。 这道题目,要在数组中插入目标值,无非是这四种情况。
![35_搜索插入位置3](https://img-blog.csdnimg.cn/20201216232148471.png) ![35_搜索插入位置3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201216232148471.png)
* 目标值在数组所有元素之前 * 目标值在数组所有元素之前
* 目标值等于数组中某一个元素 * 目标值等于数组中某一个元素
@ -78,13 +84,14 @@ public:
效率如下: 效率如下:
![35_搜索插入位置](https://img-blog.csdnimg.cn/20201216232127268.png) ![35_搜索插入位置](https://code-thinking-1253855093.file.myqcloud.com/pics/20201216232127268.png)
### 二分法 ### 二分法
既然暴力解法的时间复杂度是$O(n)$,就要尝试一下使用二分查找法。 既然暴力解法的时间复杂度是O(n),就要尝试一下使用二分查找法。
![35_搜索插入位置4](https://img-blog.csdnimg.cn/202012162326354.png)
![35_搜索插入位置4](https://code-thinking-1253855093.file.myqcloud.com/pics/202012162326354.png)
大家注意这道题目的前提是数组是有序数组,这也是使用二分查找的基础条件。 大家注意这道题目的前提是数组是有序数组,这也是使用二分查找的基础条件。
@ -94,7 +101,7 @@ public:
大体讲解一下二分法的思路这里来举一个例子例如在这个数组中使用二分法寻找元素为5的位置并返回其下标。 大体讲解一下二分法的思路这里来举一个例子例如在这个数组中使用二分法寻找元素为5的位置并返回其下标。
![35_搜索插入位置5](https://img-blog.csdnimg.cn/20201216232659199.png) ![35_搜索插入位置5](https://code-thinking-1253855093.file.myqcloud.com/pics/20201216232659199.png)
二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。 二分查找涉及的很多的边界条件,逻辑比较简单,就是写不好。
@ -145,7 +152,7 @@ public:
* 空间复杂度O(1) * 空间复杂度O(1)
效率如下: 效率如下:
![35_搜索插入位置2](https://img-blog.csdnimg.cn/2020121623272877.png) ![35_搜索插入位置2](https://code-thinking-1253855093.file.myqcloud.com/pics/2020121623272877.png)
### 二分法第二种写法 ### 二分法第二种写法
@ -226,6 +233,7 @@ class Solution {
} }
} }
``` ```
```java ```java
//第二种二分法:左闭右开 //第二种二分法:左闭右开
public int searchInsert(int[] nums, int target) { public int searchInsert(int[] nums, int target) {
@ -291,6 +299,7 @@ impl Solution {
``` ```
### Python ### Python
```python ```python
class Solution: class Solution:
def searchInsert(self, nums: List[int], target: int) -> int: def searchInsert(self, nums: List[int], target: int) -> int:
@ -309,6 +318,7 @@ class Solution:
``` ```
### JavaScript ### JavaScript
```js ```js
var searchInsert = function (nums, target) { var searchInsert = function (nums, target) {
let l = 0, r = nums.length - 1, ans = nums.length; let l = 0, r = nums.length - 1, ans = nums.length;
@ -383,7 +393,9 @@ func searchInsert(_ nums: [Int], _ target: Int) -> Int {
return right + 1 return right + 1
} }
``` ```
### Scala ### Scala
```scala ```scala
object Solution { object Solution {
def searchInsert(nums: Array[Int], target: Int): Int = { def searchInsert(nums: Array[Int], target: Int): Int = {
@ -429,7 +441,9 @@ function searchInsert($nums, $target)
return $r + 1; return $r + 1;
} }
``` ```
### C ### C
```c ```c
//版本一 [left, right]左闭右闭区间 //版本一 [left, right]左闭右闭区间
int searchInsert(int* nums, int numsSize, int target){ int searchInsert(int* nums, int numsSize, int target){
@ -455,6 +469,7 @@ int searchInsert(int* nums, int numsSize, int target){
return right + 1; return right + 1;
} }
``` ```
```c ```c
//版本二 [left, right]左闭右开区间 //版本二 [left, right]左闭右开区间
int searchInsert(int* nums, int numsSize, int target){ int searchInsert(int* nums, int numsSize, int target){

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,6 +6,7 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg) 如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg)
# 37. 解数独 # 37. 解数独
@ -14,20 +16,21 @@
编写一个程序,通过填充空格来解决数独问题。 编写一个程序,通过填充空格来解决数独问题。
一个数独的解法需遵循如下规则: 一个数独的解法需遵循如下规则:
数字 1-9 在每一行只能出现一次。 数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。 数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
空白格用 '.' 表示。 空白格用 '.' 表示。
![解数独](https://img-blog.csdnimg.cn/202011171912586.png) ![解数独](https://code-thinking-1253855093.file.myqcloud.com/pics/202011171912586.png)
一个数独。 一个数独。
![解数独](https://img-blog.csdnimg.cn/20201117191340669.png) ![解数独](https://code-thinking-1253855093.file.myqcloud.com/pics/20201117191340669.png)
答案被标成红色。 答案被标成红色。
提示: 提示:
* 给定的数独序列只包含数字 1-9 和字符 '.' 。 * 给定的数独序列只包含数字 1-9 和字符 '.' 。
* 你可以假设给定的数独只有唯一解。 * 你可以假设给定的数独只有唯一解。
* 给定数独永远是 9x9 形式的。 * 给定数独永远是 9x9 形式的。
@ -54,7 +57,7 @@
因为这个树形结构太大了,我抽取一部分,如图所示: 因为这个树形结构太大了,我抽取一部分,如图所示:
![37.解数独](https://img-blog.csdnimg.cn/2020111720451790.png) ![37.解数独](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111720451790-20230310131816104.png)
### 回溯三部曲 ### 回溯三部曲
@ -85,7 +88,7 @@ bool backtracking(vector<vector<char>>& board)
* 递归单层搜索逻辑 * 递归单层搜索逻辑
![37.解数独](https://img-blog.csdnimg.cn/2020111720451790.png) ![37.解数独](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111720451790-20230310131822254.png)
在树形图中可以看出我们需要的是一个二维的递归也就是两个for循环嵌套着递归 在树形图中可以看出我们需要的是一个二维的递归也就是两个for循环嵌套着递归
@ -224,6 +227,7 @@ public:
### Java ### Java
```java ```java
class Solution { class Solution {
public void solveSudoku(char[][] board) { public void solveSudoku(char[][] board) {
@ -292,6 +296,7 @@ class Solution {
``` ```
### Python ### Python
```python ```python
class Solution: class Solution:
def solveSudoku(self, board: List[List[str]]) -> None: def solveSudoku(self, board: List[List[str]]) -> None:
@ -393,6 +398,7 @@ func isvalid(row, col int, k byte, board [][]byte) bool {
### Javascript ### Javascript
```Javascript ```Javascript
var solveSudoku = function(board) { var solveSudoku = function(board) {
function isValid(row, col, val, board) { function isValid(row, col, val, board) {
@ -660,6 +666,7 @@ func solveSudoku(_ board: inout [[Character]]) {
### Scala ### Scala
详细写法: 详细写法:
```scala ```scala
object Solution { object Solution {
@ -717,6 +724,7 @@ object Solution {
``` ```
遵循Scala至简原则写法 遵循Scala至简原则写法
```scala ```scala
object Solution { object Solution {

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,20 +6,22 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 39. 组合总和 # 39. 组合总和
[力扣题目链接](https://leetcode.cn/problems/combination-sum/) [力扣题目链接](https://leetcode.cn/problems/combination-sum/)
给定一个无重复元素的数组 candidates 和一个目标数 target 找出 candidates 中所有可以使数字和为 target 的组合。 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。 candidates 中的数字可以无限制重复被选取。
说明: 说明:
* 所有数字(包括 target都是正整数。 * 所有数字(包括 target都是正整数。
* 解集不能包含重复的组合。  * 解集不能包含重复的组合。
示例 1 示例 1
* 输入candidates = [2,3,6,7], target = 7, * 输入candidates = [2,3,6,7], target = 7,
* 所求解集为: * 所求解集为:
[ [
@ -26,13 +29,14 @@ candidates 中的数字可以无限制重复被选取。
[2,2,3] [2,2,3]
] ]
示例 2 示例 2
* 输入candidates = [2,3,5], target = 8, * 输入candidates = [2,3,5], target = 8,
* 所求解集为: * 所求解集为:
[ [
  [2,2,2,2], [2,2,2,2],
  [2,3,3], [2,3,3],
  [3,5] [3,5]
] ]
# 算法公开课 # 算法公开课
@ -48,7 +52,7 @@ candidates 中的数字可以无限制重复被选取。
本题搜索的过程抽象成树形结构如下: 本题搜索的过程抽象成树形结构如下:
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png) ![39.组合总和](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223170730367.png)
注意图中叶子节点的返回条件因为本题没有组合数量要求仅仅是总和的限制所以递归没有层数的限制只要选取的元素总和超过target就返回 注意图中叶子节点的返回条件因为本题没有组合数量要求仅仅是总和的限制所以递归没有层数的限制只要选取的元素总和超过target就返回
而在[77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层因为要取k个元素的组合。 而在[77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层因为要取k个元素的组合。
@ -83,7 +87,7 @@ void backtracking(vector<int>& candidates, int target, int sum, int startIndex)
在如下树形结构中: 在如下树形结构中:
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png) ![39.组合总和](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223170730367-20230310135337214.png)
从叶子节点可以清晰看到终止只有两种情况sum大于target和sum等于target。 从叶子节点可以清晰看到终止只有两种情况sum大于target和sum等于target。
@ -156,7 +160,7 @@ public:
在这个树形结构中: 在这个树形结构中:
![39.组合总和](https://img-blog.csdnimg.cn/20201223170730367.png) ![39.组合总和](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223170730367-20230310135342472.png)
以及上面的版本一的代码大家可以看到对于sum已经大于target的情况其实是依然进入了下一层递归只是下一层递归结束判断的时候会判断sum > target的话就返回。 以及上面的版本一的代码大家可以看到对于sum已经大于target的情况其实是依然进入了下一层递归只是下一层递归结束判断的时候会判断sum > target的话就返回。
@ -169,7 +173,7 @@ public:
如图: 如图:
![39.组合总和1](https://img-blog.csdnimg.cn/20201223170809182.png) ![39.组合总和1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223170809182.png)
for循环剪枝代码如下 for循环剪枝代码如下
@ -236,6 +240,7 @@ public:
## Java ## Java
```Java ```Java
// 剪枝优化 // 剪枝优化
class Solution { class Solution {
@ -265,7 +270,9 @@ class Solution {
``` ```
## Python ## Python
**回溯** **回溯**
```python ```python
class Solution: class Solution:
def __init__(self): def __init__(self):
@ -297,7 +304,9 @@ class Solution:
sum_ -= candidates[i] # 回溯 sum_ -= candidates[i] # 回溯
self.path.pop() # 回溯 self.path.pop() # 回溯
``` ```
**剪枝回溯** **剪枝回溯**
```python ```python
class Solution: class Solution:
def __init__(self): def __init__(self):
@ -334,6 +343,7 @@ class Solution:
``` ```
## Go ## Go
主要在于递归中传递下一个数字 主要在于递归中传递下一个数字
```go ```go

View File

@ -82,7 +82,7 @@ candidates 中的每个数字在每个组合中只能使用一次。
选择过程树形结构如图所示: 选择过程树形结构如图所示:
![40.组合总和II](https://img-blog.csdnimg.cn/20201123202736384.png) ![40.组合总和II](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000918.png)
可以看到图中,每个节点相对于 [39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)我多加了used数组这个used数组下面会重点介绍。 可以看到图中,每个节点相对于 [39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)我多加了used数组这个used数组下面会重点介绍。
@ -132,7 +132,7 @@ if (sum == target) {
这块比较抽象,如图: 这块比较抽象,如图:
![40.组合总和II1](https://img-blog.csdnimg.cn/20201123202817973.png) ![40.组合总和II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000954.png)
我在图中将used的变化用橘黄色标注上可以看出在candidates[i] == candidates[i - 1]相同的情况下: 我在图中将used的变化用橘黄色标注上可以看出在candidates[i] == candidates[i - 1]相同的情况下:

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,6 +6,7 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 这个图就是大厂面试经典题目,接雨水! 最常青藤的一道题,面试官百出不厌! > 这个图就是大厂面试经典题目,接雨水! 最常青藤的一道题,面试官百出不厌!
# 42. 接雨水 # 42. 接雨水
@ -32,6 +34,7 @@
接雨水问题在面试中还是常见题目的,有必要好好讲一讲。 接雨水问题在面试中还是常见题目的,有必要好好讲一讲。
本文深度讲解如下三种方法: 本文深度讲解如下三种方法:
* 双指针法 * 双指针法
* 动态规划 * 动态规划
* 单调栈 * 单调栈
@ -43,10 +46,10 @@
首先要明确,要按照行来计算,还是按照列来计算。 首先要明确,要按照行来计算,还是按照列来计算。
按照行来计算如图: 按照行来计算如图:
![42.接雨水2](https://img-blog.csdnimg.cn/20210402091118927.png) ![42.接雨水2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210402091118927.png)
按照列来计算如图: 按照列来计算如图:
![42.接雨水1](https://img-blog.csdnimg.cn/20210402091208445.png) ![42.接雨水1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210402091208445.png)
一些同学在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。 一些同学在实现的时候,很容易一会按照行来计算一会按照列来计算,这样就会越写越乱。
@ -58,7 +61,7 @@
这句话可以有点绕来举一个理解例如求列4的雨水高度如图 这句话可以有点绕来举一个理解例如求列4的雨水高度如图
![42.接雨水3](https://img-blog.csdnimg.cn/20210223092732301.png) ![42.接雨水3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223092732301.png)
列4 左侧最高的柱子是列3高度为2以下用lHeight表示 列4 左侧最高的柱子是列3高度为2以下用lHeight表示
@ -197,7 +200,7 @@ public:
1. 首先单调栈是按照行方向来计算雨水,如图: 1. 首先单调栈是按照行方向来计算雨水,如图:
![42.接雨水2](https://img-blog.csdnimg.cn/20210223092629946.png) ![42.接雨水2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223092629946.png)
知道这一点,后面的就可以理解了。 知道这一点,后面的就可以理解了。
@ -211,7 +214,7 @@ public:
如图: 如图:
![42.接雨水4](https://img-blog.csdnimg.cn/2021022309321229.png) ![42.接雨水4](https://code-thinking-1253855093.file.myqcloud.com/pics/2021022309321229.png)
关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。 关于单调栈的顺序给大家一个总结: [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 中求一个元素右边第一个更大元素,单调栈就是递增的,[84.柱状图中最大的矩形](https://programmercarl.com/0084.柱状图中最大的矩形.html)求一个元素右边第一个更小元素,单调栈就是递减的。
@ -225,7 +228,7 @@ public:
如图所示: 如图所示:
![42.接雨水5](https://img-blog.csdnimg.cn/20210223094619398.png) ![42.接雨水5](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223094619398.png)
4. 栈里要保存什么数值 4. 栈里要保存什么数值
@ -280,7 +283,7 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况
如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示: 如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了,如图所示:
![42.接雨水4](https://img-blog.csdnimg.cn/2021022309321229.png) ![42.接雨水4](https://code-thinking-1253855093.file.myqcloud.com/pics/2021022309321229-20230310123027977.png)
取栈顶元素将栈顶元素弹出这个就是凹槽的底部也就是中间位置下标记为mid对应的高度为height[mid]就是图中的高度1 取栈顶元素将栈顶元素弹出这个就是凹槽的底部也就是中间位置下标记为mid对应的高度为height[mid]就是图中的高度1
@ -378,6 +381,7 @@ public:
### Java: ### Java:
暴力解法: 暴力解法:
```java ```java
class Solution { class Solution {
public int trap(int[] height) { public int trap(int[] height) {
@ -404,6 +408,7 @@ class Solution {
``` ```
双指针: 双指针:
```java ```java
class Solution { class Solution {
public int trap(int[] height) { public int trap(int[] height) {
@ -432,6 +437,7 @@ class Solution {
``` ```
单调栈法 单调栈法
```java ```java
class Solution { class Solution {
public int trap(int[] height){ public int trap(int[] height){
@ -481,6 +487,7 @@ class Solution {
### Python: ### Python:
暴力解法: 暴力解法:
```Python ```Python
class Solution: class Solution:
def trap(self, height: List[int]) -> int: def trap(self, height: List[int]) -> int:
@ -502,6 +509,7 @@ class Solution:
``` ```
双指针: 双指针:
```python ```python
class Solution: class Solution:
def trap(self, height: List[int]) -> int: def trap(self, height: List[int]) -> int:
@ -520,7 +528,9 @@ class Solution:
result += summ result += summ
return result return result
``` ```
单调栈 单调栈
```Python ```Python
class Solution: class Solution:
def trap(self, height: List[int]) -> int: def trap(self, height: List[int]) -> int:
@ -652,6 +662,7 @@ func min(a,b int)int{
``` ```
单调栈解法 单调栈解法
```go ```go
func trap(height []int) int { func trap(height []int) int {
if len(height) <= 2 { if len(height) <= 2 {

View File

@ -4,7 +4,6 @@
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不少,做好心里准备! > 相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不少,做好心里准备!
# 45.跳跃游戏 II # 45.跳跃游戏 II
@ -18,13 +17,17 @@
你的目标是使用最少的跳跃次数到达数组的最后一个位置。 你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例: 示例:
* 输入: [2,3,1,1,4]
* 输出: 2 - 输入: [2,3,1,1,4]
* 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置 1 然后跳 3 步到达数组的最后一个位置。 - 输出: 2
- 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳  1  步,然后跳  3  步到达数组的最后一个位置。
说明: 说明:
假设你总是可以到达数组的最后一个位置。 假设你总是可以到达数组的最后一个位置。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,最少跳几步还得看覆盖范围 | LeetCode 45.跳跃游戏 II](https://www.bilibili.com/video/BV1Y24y1r7XZ),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
@ -46,7 +49,7 @@
如图: 如图:
![45.跳跃游戏II](https://img-blog.csdnimg.cn/20201201232309103.png) ![45.跳跃游戏II](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232309103.png)
**图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)** **图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)**
@ -56,8 +59,8 @@
这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时 这里还是有个特殊情况需要考虑,当移动下标达到了当前覆盖的最远距离下标时
* 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。 - 如果当前覆盖最远距离下标不是是集合终点,步数就加一,还需要继续走。
* 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。 - 如果当前覆盖最远距离下标就是是集合终点,步数不用加一,因为不能再往后走了。
C++代码如下:(详细注释) C++代码如下:(详细注释)
@ -73,11 +76,9 @@ public:
for (int i = 0; i < nums.size(); i++) { for (int i = 0; i < nums.size(); i++) {
nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖最远距离下标 nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖最远距离下标
if (i == curDistance) { // 遇到当前覆盖最远距离下标 if (i == curDistance) { // 遇到当前覆盖最远距离下标
if (curDistance < nums.size() - 1) { // 如果当前覆盖最远距离下标不是终点
ans++; // 需要走下一步 ans++; // 需要走下一步
curDistance = nextDistance; // 更新当前覆盖最远距离下标(相当于加油了) curDistance = nextDistance; // 更新当前覆盖最远距离下标(相当于加油了)
if (nextDistance >= nums.size() - 1) break; // 下一步的覆盖范围已经可以达到终点,结束循环 if (nextDistance >= nums.size() - 1) break; // 当前覆盖最远距到达集合终点不用做ans++操作了,直接结束
} else break; // 当前覆盖最远距到达集合终点不用做ans++操作了,直接结束
} }
} }
return ans; return ans;
@ -85,6 +86,10 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 方法二 ## 方法二
依然是贪心,思路和方法一差不多,代码可以简洁一些。 依然是贪心,思路和方法一差不多,代码可以简洁一些。
@ -95,12 +100,12 @@ public:
因为当移动下标指向 nums.size - 2 时: 因为当移动下标指向 nums.size - 2 时:
* 如果移动下标等于当前覆盖最大距离下标, 需要再走一步即ans++),因为最后一步一定是可以到的终点。(题目假设总是可以到达数组的最后一个位置),如图: - 如果移动下标等于当前覆盖最大距离下标, 需要再走一步(即 ans++),因为最后一步一定是可以到的终点。(题目假设总是可以到达数组的最后一个位置),如图:
![45.跳跃游戏II2](https://img-blog.csdnimg.cn/20201201232445286.png) ![45.跳跃游戏II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232445286.png)
* 如果移动下标不等于当前覆盖最大距离下标,说明当前覆盖最远距离就可以直接达到终点了,不需要再走一步。如图: - 如果移动下标不等于当前覆盖最大距离下标,说明当前覆盖最远距离就可以直接达到终点了,不需要再走一步。如图:
![45.跳跃游戏II1](https://img-blog.csdnimg.cn/20201201232338693.png) ![45.跳跃游戏II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232338693.png)
代码如下: 代码如下:
@ -124,6 +129,11 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
可以看出版本二的代码相对于版本一简化了不少! 可以看出版本二的代码相对于版本一简化了不少!
**其精髓在于控制移动下标 i 只移动到 nums.size() - 2 的位置**,所以移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不用考虑别的了。 **其精髓在于控制移动下标 i 只移动到 nums.size() - 2 的位置**,所以移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不用考虑别的了。
@ -136,11 +146,10 @@ public:
理解本题的关键在于:**以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点**,这个范围内最小步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。 理解本题的关键在于:**以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点**,这个范围内最小步数一定可以跳到,不用管具体是怎么跳的,不纠结于一步究竟跳一个单位还是两个单位。
## 其他语言版本 ## 其他语言版本
### Java ### Java
```Java ```Java
// 版本一 // 版本一
class Solution { class Solution {
@ -229,6 +238,7 @@ class Solution:
step += 1 step += 1
return step return step
``` ```
```python ```python
# 动态规划做法 # 动态规划做法
class Solution: class Solution:
@ -243,7 +253,6 @@ class Solution:
``` ```
### Go ### Go
```go ```go
@ -344,7 +353,7 @@ function jump(nums: number[]): number {
curIndex++; curIndex++;
} }
return stepNum; return stepNum;
}; }
``` ```
### Scala ### Scala
@ -378,23 +387,25 @@ object Solution {
```Rust ```Rust
//版本一 //版本一
impl Solution { impl Solution {
fn max(a: i32, b:i32) -> i32 {
if a > b { a } else { b }
}
pub fn jump(nums: Vec<i32>) -> i32 { pub fn jump(nums: Vec<i32>) -> i32 {
if nums.len() == 0 { return 0; } if nums.len() == 1 {
let mut cur_distance: i32 = 0; return 0;
let mut ans: i32 = 0; }
let mut next_distance: i32 = 0; let mut cur_distance = 0;
for i in 0..nums.len() { let mut ans = 0;
next_distance = Self::max(nums[i] + i as i32, next_distance); let mut next_distance = 0;
if i as i32 == cur_distance { for (i, &n) in nums.iter().enumerate().take(nums.len() - 1) {
if cur_distance != (nums.len() - 1) as i32 { next_distance = (n as usize + i).max(next_distance);
if i == cur_distance {
if cur_distance < nums.len() - 1 {
ans += 1; ans += 1;
cur_distance = next_distance; cur_distance = next_distance;
if next_distance == (nums.len() - 1) as i32 { break; } if next_distance >= nums.len() - 1 {
break;
};
} else {
break;
} }
else { break; }
} }
} }
ans ans
@ -405,16 +416,16 @@ impl Solution {
```Rust ```Rust
//版本二 //版本二
impl Solution { impl Solution {
fn max(a: i32, b:i32) -> i32 {
if a > b { a } else { b }
}
pub fn jump(nums: Vec<i32>) -> i32 { pub fn jump(nums: Vec<i32>) -> i32 {
let mut cur_distance: i32 = 0; if nums.len() == 1 {
let mut ans: i32 = 0; return 0;
let mut next_distance: i32 = 0; }
for i in 0..nums.len() - 1 { let mut cur_distance = 0;
next_distance = Self::max(nums[i] + i as i32, next_distance); let mut ans = 0;
if i as i32 == cur_distance { let mut next_distance = 0;
for (i, &n) in nums.iter().enumerate().take(nums.len() - 1) {
next_distance = (n as usize + i).max(next_distance);
if i == cur_distance {
cur_distance = next_distance; cur_distance = next_distance;
ans += 1; ans += 1;
} }
@ -424,7 +435,6 @@ impl Solution {
} }
``` ```
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -66,7 +66,7 @@ void backtracking (vector<int>& nums, vector<bool>& used)
* 递归终止条件 * 递归终止条件
![46.全排列](https://img-blog.csdnimg.cn/20201209174225145.png) ![46.全排列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209174225145.png)
可以看出叶子节点,就是收割结果的地方。 可以看出叶子节点,就是收割结果的地方。

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,6 +6,7 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 47.全排列 II # 47.全排列 II
[力扣题目链接](https://leetcode.cn/problems/permutations-ii/) [力扣题目链接](https://leetcode.cn/problems/permutations-ii/)
@ -12,6 +14,7 @@
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1 示例 1
* 输入nums = [1,1,2] * 输入nums = [1,1,2]
* 输出: * 输出:
[[1,1,2], [[1,1,2],
@ -19,10 +22,12 @@
[2,1,1]] [2,1,1]]
示例 2 示例 2
* 输入nums = [1,2,3] * 输入nums = [1,2,3]
* 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] * 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
提示: 提示:
* 1 <= nums.length <= 8 * 1 <= nums.length <= 8
* -10 <= nums[i] <= 10 * -10 <= nums[i] <= 10
@ -45,7 +50,7 @@
我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图: 我以示例中的 [1,1,2]为例 (为了方便举例,已经排序)抽象为一棵树,去重过程如图:
![47.全排列II1](https://img-blog.csdnimg.cn/20201124201331223.png) ![47.全排列II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201331223.png)
图中我们对同一树层前一位也就是nums[i-1])如果使用过,那么就进行去重。 图中我们对同一树层前一位也就是nums[i-1])如果使用过,那么就进行去重。
@ -123,23 +128,26 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
树层上去重(used[i - 1] == false),的树形结构如下: 树层上去重(used[i - 1] == false),的树形结构如下:
![47.全排列II2](https://img-blog.csdnimg.cn/20201124201406192.png) ![47.全排列II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201406192.png)
树枝上去重used[i - 1] == true的树型结构如下 树枝上去重used[i - 1] == true的树型结构如下
![47.全排列II3](https://img-blog.csdnimg.cn/20201124201431571.png) ![47.全排列II3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201431571.png)
大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。 大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。
## 总结 ## 总结
这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写: 这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写:
```cpp ```cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue; continue;
} }
``` ```
和这么写: 和这么写:
```cpp ```cpp
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) {
continue; continue;
@ -264,7 +272,6 @@ func dfs(nums []int, cur int) {
### Javascript ### Javascript
```javascript ```javascript
var permuteUnique = function (nums) { var permuteUnique = function (nums) {
nums.sort((a, b) => { nums.sort((a, b) => {
return a - b return a - b
@ -392,6 +399,7 @@ impl Solution {
``` ```
### C ### C
```c ```c
//临时数组 //临时数组
int *path; int *path;

View File

@ -47,7 +47,7 @@ n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,
下面我用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图: 下面我用一个 3 * 3 的棋盘,将搜索过程抽象为一棵树,如图:
![51.N皇后](https://img-blog.csdnimg.cn/20210130182532303.jpg) ![51.N皇后](https://code-thinking-1253855093.file.myqcloud.com/pics/20210130182532303.jpg)
从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。 从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。
@ -87,7 +87,7 @@ void backtracking(int n, int row, vector<string>& chessboard) {
* 递归终止条件 * 递归终止条件
在如下树形结构中: 在如下树形结构中:
![51.N皇后](https://img-blog.csdnimg.cn/20210130182532303.jpg) ![51.N皇后](https://code-thinking-1253855093.file.myqcloud.com/pics/20210130182532303-20230310122134167.jpg)
可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。 可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。

View File

@ -13,7 +13,9 @@
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
上图为 8 皇后问题的一种解法。 上图为 8 皇后问题的一种解法。
![51n皇后](https://img-blog.csdnimg.cn/20200821152118456.png)
![51n皇后](https://code-thinking-1253855093.file.myqcloud.com/pics/20200821152118456.png)
给定一个整数 n返回 n 皇后不同的解决方案的数量。 给定一个整数 n返回 n 皇后不同的解决方案的数量。

View File

@ -4,7 +4,6 @@
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 53. 最大子序和 # 53. 最大子序和
[力扣题目链接](https://leetcode.cn/problems/maximum-subarray/) [力扣题目链接](https://leetcode.cn/problems/maximum-subarray/)
@ -12,17 +11,19 @@
给定一个整数数组 nums 找到一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。 给定一个整数数组 nums 找到一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。
示例: 示例:
* 输入: [-2,1,-3,4,-1,2,1,-5,4]
* 输出: 6
* 解释: 连续子数组 [4,-1,2,1] 的和最大 6。
- 输入: [-2,1,-3,4,-1,2,1,-5,4]
- 输出: 6
- 解释:  连续子数组  [4,-1,2,1] 的和最大,为  6。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法的巧妙需要慢慢体会LeetCode53. 最大子序和](https://www.bilibili.com/video/BV1aY4y1Z7ya),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 暴力解法 ## 暴力解法
暴力解法的思路,第一层 for 就是设置起始位置,第二层 for 循环遍历数组寻找最大值 暴力解法的思路,第一层 for 就是设置起始位置,第二层 for 循环遍历数组寻找最大值
* 时间复杂度O(n^2)
* 空间复杂度O(1)
```CPP ```CPP
class Solution { class Solution {
@ -41,6 +42,9 @@ public:
} }
}; };
``` ```
* 时间复杂度O(n^2)
* 空间复杂度O(1)
以上暴力的解法 C++勉强可以过,其他语言就不确定了。 以上暴力的解法 C++勉强可以过,其他语言就不确定了。
@ -56,12 +60,10 @@ public:
**局部最优的情况下,并记录最大的“连续和”,可以推出全局最优**。 **局部最优的情况下,并记录最大的“连续和”,可以推出全局最优**。
从代码角度上来讲:遍历 nums从头开始用 count 累积,如果 count 一旦加上 nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积 count 了,因为已经变为负数的 count只会拖累总和。 从代码角度上来讲:遍历 nums从头开始用 count 累积,如果 count 一旦加上 nums[i]变为负数,那么就应该从 nums[i+1]开始从 0 累积 count 了,因为已经变为负数的 count只会拖累总和。
**这相当于是暴力解法中的不断调整最大子序和区间的起始位置**。 **这相当于是暴力解法中的不断调整最大子序和区间的起始位置**。
**那有同学问了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?** **那有同学问了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?**
区间的终止位置,其实就是如果 count 取到最大值了,及时记录下来了。例如如下代码: 区间的终止位置,其实就是如果 count 取到最大值了,及时记录下来了。例如如下代码:
@ -97,20 +99,17 @@ public:
} }
}; };
``` ```
- 时间复杂度O(n)
* 时间复杂度O(n) - 空间复杂度O(1)
* 空间复杂度O(1)
当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了。 当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了。
## 常见误区 ## 常见误区
误区一: 误区一:
不少同学认为 如果输入用例都是-1或者 都是负数,这个贪心算法跑出来的结果是 0 这是**又一次证明脑洞模拟不靠谱的经典案例**,建议大家把代码运行一下试一试,就知道了,也会理解 为什么 result 要初始化为最小负数了。 不少同学认为 如果输入用例都是-1或者 都是负数,这个贪心算法跑出来的结果是 0 这是**又一次证明脑洞模拟不靠谱的经典案例**,建议大家把代码运行一下试一试,就知道了,也会理解 为什么 result 要初始化为最小负数了。
误区二: 误区二:
大家在使用贪心算法求解本题,经常陷入的误区,就是分不清,是遇到 负数就选择起始位置,还是连续和为负选择起始位置。 大家在使用贪心算法求解本题,经常陷入的误区,就是分不清,是遇到 负数就选择起始位置,还是连续和为负选择起始位置。
@ -123,8 +122,6 @@ public:
其实并不会,因为还有一个变量 result 一直在更新 最大的连续和只要有更大的连续和出现result 就更新了,那么 result 已经把 4 更新了,后面 连续和变成 3也不会对最后结果有影响。 其实并不会,因为还有一个变量 result 一直在更新 最大的连续和只要有更大的连续和出现result 就更新了,那么 result 已经把 4 更新了,后面 连续和变成 3也不会对最后结果有影响。
## 动态规划 ## 动态规划
当然本题还可以用动态规划来做,当前[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)主要讲解贪心系列,后续到动态规划系列的时候会详细讲解本题的 dp 方法。 当然本题还可以用动态规划来做,当前[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)主要讲解贪心系列,后续到动态规划系列的时候会详细讲解本题的 dp 方法。
@ -148,8 +145,8 @@ public:
}; };
``` ```
* 时间复杂度O(n) - 时间复杂度O(n)
* 空间复杂度O(n) - 空间复杂度O(n)
## 总结 ## 总结
@ -159,8 +156,8 @@ public:
## 其他语言版本 ## 其他语言版本
### Java ### Java
```java ```java
class Solution { class Solution {
public int maxSubArray(int[] nums) { public int maxSubArray(int[] nums) {
@ -201,6 +198,7 @@ class Solution {
``` ```
### Python ### Python
```python ```python
class Solution: class Solution:
def maxSubArray(self, nums: List[int]) -> int: def maxSubArray(self, nums: List[int]) -> int:
@ -233,6 +231,7 @@ func maxSubArray(nums []int) int {
``` ```
### Rust ### Rust
```rust ```rust
pub fn max_sub_array(nums: Vec<i32>) -> i32 { pub fn max_sub_array(nums: Vec<i32>) -> i32 {
let mut max_sum = i32::MIN; let mut max_sum = i32::MIN;
@ -247,6 +246,7 @@ pub fn max_sub_array(nums: Vec<i32>) -> i32 {
``` ```
### Javascript: ### Javascript:
```Javascript ```Javascript
var maxSubArray = function(nums) { var maxSubArray = function(nums) {
let result = -Infinity let result = -Infinity
@ -264,9 +264,10 @@ var maxSubArray = function(nums) {
}; };
``` ```
### C: ### C:
贪心: 贪心:
```c ```c
int maxSubArray(int* nums, int numsSize){ int maxSubArray(int* nums, int numsSize){
int maxVal = INT_MIN; int maxVal = INT_MIN;
@ -286,6 +287,7 @@ int maxSubArray(int* nums, int numsSize){
``` ```
动态规划: 动态规划:
```c ```c
/** /**
* 解题思路:动态规划: * 解题思路:动态规划:
@ -332,7 +334,7 @@ function maxSubArray(nums: number[]): number {
if (curSum < 0) curSum = 0; if (curSum < 0) curSum = 0;
} }
return resMax; return resMax;
}; }
``` ```
**动态规划** **动态规划**
@ -350,7 +352,7 @@ function maxSubArray(nums: number[]): number {
resMax = Math.max(resMax, dp[i]); resMax = Math.max(resMax, dp[i]);
} }
return resMax; return resMax;
}; }
``` ```
### Scala ### Scala

View File

@ -51,7 +51,7 @@ dp[0]应该是多少呢?
5. 举例推导dp数组 5. 举例推导dp数组
以示例一为例输入nums = [-2,1,-3,4,-1,2,1,-5,4]对应的dp状态如下 以示例一为例输入nums = [-2,1,-3,4,-1,2,1,-5,4]对应的dp状态如下
![53.最大子序和(动态规划)](https://img-blog.csdnimg.cn/20210303104129101.png) ![53.最大子序和(动态规划)](https://code-thinking-1253855093.file.myqcloud.com/pics/20210303104129101.png)
**注意最后的结果可不是dp[nums.size() - 1]** 而是dp[6]。 **注意最后的结果可不是dp[nums.size() - 1]** 而是dp[6]。

View File

@ -37,7 +37,8 @@
由外向内一圈一圈这么画下去,如下所示: 由外向内一圈一圈这么画下去,如下所示:
![螺旋矩阵](https://img-blog.csdnimg.cn/2020121623550681.png)
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220922102236.png)
这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。 这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。

View File

@ -4,7 +4,6 @@
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 55. 跳跃游戏 # 55. 跳跃游戏
[力扣题目链接](https://leetcode.cn/problems/jump-game/) [力扣题目链接](https://leetcode.cn/problems/jump-game/)
@ -16,15 +15,20 @@
判断你是否能够到达最后一个位置。 判断你是否能够到达最后一个位置。
示例  1: 示例  1:
* 输入: [2,3,1,1,4]
* 输出: true - 输入: [2,3,1,1,4]
* 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。 - 输出: true
- 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例  2: 示例  2:
* 输入: [3,2,1,0,4]
* 输出: false
* 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 所以你永远不可能到达最后一个位置。
- 输入: [3,2,1,0,4]
- 输出: false
- 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 所以你永远不可能到达最后一个位置。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,怎么跳跃不重要,关键在覆盖范围 | LeetCode55.跳跃游戏](https://www.bilibili.com/video/BV1VG4y1X7kB),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
@ -46,7 +50,7 @@
如图: 如图:
![55.跳跃游戏](https://img-blog.csdnimg.cn/20201124154758229.png) ![55.跳跃游戏](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124154758229-20230310135019977.png)
i 每次移动只能在 cover 的范围内移动每移动一个元素cover 得到该元素数值(新的覆盖范围)的补充,让 i 继续移动下去。 i 每次移动只能在 cover 的范围内移动每移动一个元素cover 得到该元素数值(新的覆盖范围)的补充,让 i 继续移动下去。
@ -70,6 +74,11 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 总结 ## 总结
这道题目关键点在于:不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。 这道题目关键点在于:不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
@ -82,8 +91,8 @@ public:
## 其他语言版本 ## 其他语言版本
### Java ### Java
```Java ```Java
class Solution { class Solution {
public boolean canJump(int[] nums) { public boolean canJump(int[] nums) {
@ -105,6 +114,7 @@ class Solution {
``` ```
### Python ### Python
```python ```python
class Solution: class Solution:
def canJump(self, nums: List[int]) -> bool: def canJump(self, nums: List[int]) -> bool:
@ -155,8 +165,6 @@ func max(a, b int ) int {
} }
``` ```
### Javascript ### Javascript
```Javascript ```Javascript
@ -177,16 +185,16 @@ var canJump = function(nums) {
```Rust ```Rust
impl Solution { impl Solution {
fn max(a: usize, b: usize) -> usize {
if a > b { a } else { b }
}
pub fn can_jump(nums: Vec<i32>) -> bool { pub fn can_jump(nums: Vec<i32>) -> bool {
let mut cover = 0; if nums.len() == 1 {
if (nums.len() == 1) { return true; } return true;
let mut i = 0; }
let (mut i, mut cover) = (0, 0);
while i <= cover { while i <= cover {
cover = Self::max(i + nums[i] as usize, cover); cover = (i + nums[i] as usize).max(cover);
if cover >= nums.len() - 1 { return true; } if cover >= nums.len() - 1 {
return true;
}
i += 1; i += 1;
} }
false false
@ -195,6 +203,7 @@ impl Solution {
``` ```
### C ### C
```c ```c
#define max(a, b) (((a) > (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b))
@ -216,7 +225,6 @@ bool canJump(int* nums, int numsSize){
} }
``` ```
### TypeScript ### TypeScript
```typescript ```typescript
@ -229,10 +237,11 @@ function canJump(nums: number[]): boolean {
cur++; cur++;
} }
return false; return false;
}; }
``` ```
### Scala ### Scala
```scala ```scala
object Solution { object Solution {
def canJump(nums: Array[Int]): Boolean = { def canJump(nums: Array[Int]): Boolean = {
@ -249,7 +258,6 @@ object Solution {
} }
``` ```
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -22,6 +22,9 @@
* 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 * 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。
* 注意输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。 * 注意输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法合并区间有细节LeetCode56.合并区间](https://www.bilibili.com/video/BV1wx4y157nD),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
@ -37,7 +40,7 @@
这么说有点抽象,看图:(**注意图中区间都是按照左边界排序之后了** 这么说有点抽象,看图:(**注意图中区间都是按照左边界排序之后了**
![56.合并区间](https://img-blog.csdnimg.cn/20201223200632791.png) ![56.合并区间](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223200632791.png)
知道如何判断重复之后,剩下的就是合并了,如何去模拟合并区间呢? 知道如何判断重复之后,剩下的就是合并了,如何去模拟合并区间呢?
@ -70,6 +73,10 @@ public:
}; };
``` ```
* 时间复杂度: O(nlogn)
* 空间复杂度: O(logn),排序需要的空间开销
## 其他语言版本 ## 其他语言版本
@ -174,6 +181,34 @@ func max(a, b int) int {
return b return b
} }
``` ```
```go
// 版本2
func merge(intervals [][]int) [][]int {
if len(intervals) == 1 {
return intervals
}
sort.Slice(intervals, func(i, j int) bool {
return intervals[i][0] < intervals[j][0]
})
res := make([][]int, 0)
res = append(res, intervals[0])
for i := 1; i < len(intervals); i++ {
if intervals[i][0] <= res[len(res)-1][1]{
res[len(res)-1][1] = max56(res[len(res)-1][1],intervals[i][1])
} else {
res = append(res, intervals[i])
}
}
return res
}
func max56(a, b int) int {
if a > b {
return a
}
return b
}
```
### Javascript ### Javascript
```javascript ```javascript
@ -277,24 +312,22 @@ object Solution {
```Rust ```Rust
impl Solution { impl Solution {
fn max(a: i32, b: i32) -> i32 { pub fn merge(mut intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
if a > b { a } else { b } let mut res = vec![];
if intervals.is_empty() {
return res;
} }
intervals.sort_by_key(|a| a[0]);
pub fn merge(intervals: Vec<Vec<i32>>) -> Vec<Vec<i32>> { res.push(intervals[0].clone());
let mut intervals = intervals; for interval in intervals.into_iter().skip(1) {
let mut result = Vec::new(); let res_last_ele = res.last_mut().unwrap();
if intervals.len() == 0 { return result; } if res_last_ele[1] >= interval[0] {
intervals.sort_by(|a, b| a[0].cmp(&b[0])); res_last_ele[1] = interval[1].max(res_last_ele[1]);
result.push(intervals[0].clone());
for i in 1..intervals.len() {
if result.last_mut().unwrap()[1] >= intervals[i][0] {
result.last_mut().unwrap()[1] = Self::max(result.last_mut().unwrap()[1], intervals[i][1]);
} else { } else {
result.push(intervals[i].clone()); res.push(interval);
} }
} }
result res
} }
} }
``` ```

View File

@ -117,6 +117,9 @@ public:
}; };
``` ```
* 时间复杂度 O(n^2): 模拟遍历二维矩阵的时间
* 空间复杂度 O(1)
## 类似题目 ## 类似题目
* 54.螺旋矩阵 * 54.螺旋矩阵

View File

@ -1,14 +1,16 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 62.不同路径 # 62.不同路径
[力扣题目链接](https://leetcode.cn/problems/unique-paths/) [力扣题目链接](https://leetcode.cn/problems/unique-paths/)
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
@ -16,30 +18,35 @@
示例 1 示例 1
![](https://img-blog.csdnimg.cn/20210110174033215.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110174033215.png)
* 输入m = 3, n = 7 * 输入m = 3, n = 7
* 输出28 * 输出28
示例 2 示例 2
* 输入m = 2, n = 3 * 输入m = 2, n = 3
* 输出3 * 输出3
解释: 从左上角开始,总共有 3 条路径可以到达右下角。 解释: 从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下 1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右 2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右 3. 向下 -> 向右 -> 向右
示例 3 示例 3
* 输入m = 7, n = 3 * 输入m = 7, n = 3
* 输出28 * 输出28
示例 4 示例 4
* 输入m = 3, n = 3 * 输入m = 3, n = 3
* 输出6 * 输出6
提示: 提示:
* 1 <= m, n <= 100 * 1 <= m, n <= 100
* 题目数据保证答案小于等于 2 * 10^9 * 题目数据保证答案小于等于 2 * 10^9
@ -57,7 +64,7 @@
如图举例: 如图举例:
![62.不同路径](https://img-blog.csdnimg.cn/20201209113602700.png) ![62.不同路径](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209113602700.png)
此时问题就可以转化为求二叉树叶子节点的个数,代码如下: 此时问题就可以转化为求二叉树叶子节点的个数,代码如下:
@ -126,7 +133,7 @@ for (int j = 0; j < n; j++) dp[0][j] = 1;
如图所示: 如图所示:
![62.不同路径1](https://img-blog.csdnimg.cn/20201209113631392.png) ![62.不同路径1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209113631392.png)
以上动规五部曲分析完毕C++代码如下: 以上动规五部曲分析完毕C++代码如下:
@ -175,7 +182,7 @@ public:
在这个图中可以看出一共mn的话无论怎么走走到终点都需要 m + n - 2 步。 在这个图中可以看出一共mn的话无论怎么走走到终点都需要 m + n - 2 步。
![62.不同路径](https://img-blog.csdnimg.cn/20201209113602700.png) ![62.不同路径](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209113602700-20230310120944078.png)
在这m + n - 2 步中,一定有 m - 1 步是要向下走的,不用管什么时候向下走。 在这m + n - 2 步中,一定有 m - 1 步是要向下走的,不用管什么时候向下走。
@ -185,7 +192,7 @@ public:
那么答案,如图所示: 那么答案,如图所示:
![62.不同路径2](https://img-blog.csdnimg.cn/20201209113725324.png) ![62.不同路径2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209113725324.png)
**求组合的时候要防止两个int相乘溢出** 所以不能把算式的分子都算出来,分母都算出来再做除法。 **求组合的时候要防止两个int相乘溢出** 所以不能把算式的分子都算出来,分母都算出来再做除法。
@ -246,6 +253,7 @@ public:
### Java ### Java
```java ```java
/** /**
* 1. 确定dp数组下标含义 dp[i][j] 到每一个坐标可能的路径种类 * 1. 确定dp数组下标含义 dp[i][j] 到每一个坐标可能的路径种类
@ -279,6 +287,7 @@ public:
``` ```
### Python ### Python
```python ```python
class Solution: # 动态规划 class Solution: # 动态规划
def uniquePaths(self, m: int, n: int) -> int: def uniquePaths(self, m: int, n: int) -> int:
@ -290,6 +299,7 @@ class Solution: # 动态规划
``` ```
### Go ### Go
```Go ```Go
func uniquePaths(m int, n int) int { func uniquePaths(m int, n int) int {
dp := make([][]int, m) dp := make([][]int, m)
@ -310,6 +320,7 @@ func uniquePaths(m int, n int) int {
``` ```
### Javascript ### Javascript
```Javascript ```Javascript
var uniquePaths = function(m, n) { var uniquePaths = function(m, n) {
const dp = Array(m).fill().map(item => Array(n)) const dp = Array(m).fill().map(item => Array(n))
@ -330,7 +341,9 @@ var uniquePaths = function(m, n) {
return dp[m - 1][n - 1] return dp[m - 1][n - 1]
}; };
``` ```
>版本二直接将dp数值值初始化为1 >版本二直接将dp数值值初始化为1
```javascript ```javascript
/** /**
* @param {number} m * @param {number} m
@ -440,6 +453,7 @@ int uniquePaths(int m, int n){
``` ```
滚动数组解法: 滚动数组解法:
```c ```c
int uniquePaths(int m, int n){ int uniquePaths(int m, int n){
int i, j; int i, j;

View File

@ -1,9 +1,11 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 63. 不同路径 II # 63. 不同路径 II
[力扣题目链接](https://leetcode.cn/problems/unique-paths-ii/) [力扣题目链接](https://leetcode.cn/problems/unique-paths-ii/)
@ -14,13 +16,13 @@
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
![](https://img-blog.csdnimg.cn/20210111204901338.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210111204901338.png)
网格中的障碍物和空位置分别用 1 和 0 来表示。 网格中的障碍物和空位置分别用 1 和 0 来表示。
示例 1 示例 1
![](https://img-blog.csdnimg.cn/20210111204939971.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210111204939971.png)
* 输入obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]] * 输入obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
* 输出2 * 输出2
@ -32,14 +34,15 @@
示例 2 示例 2
![](https://img-blog.csdnimg.cn/20210111205857918.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210111205857918.png)
* 输入obstacleGrid = [[0,1],[0,0]] * 输入obstacleGrid = [[0,1],[0,0]]
* 输出1 * 输出1
提示: 提示:
* m == obstacleGrid.length
* n == obstacleGrid[i].length * m == obstacleGrid.length
* n == obstacleGrid[i].length
* 1 <= m, n <= 100 * 1 <= m, n <= 100
* obstacleGrid[i][j] 为 0 或 1 * obstacleGrid[i][j] 为 0 或 1
@ -92,7 +95,7 @@ for (int j = 0; j < n; j++) dp[0][j] = 1;
如图: 如图:
![63.不同路径II](https://img-blog.csdnimg.cn/20210104114513928.png) ![63.不同路径II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114513928.png)
下标(0, j)的初始化情况同理。 下标(0, j)的初始化情况同理。
@ -126,13 +129,13 @@ for (int i = 1; i < m; i++) {
拿示例1来举例如题 拿示例1来举例如题
![63.不同路径II1](https://img-blog.csdnimg.cn/20210104114548983.png) ![63.不同路径II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114548983.png)
对应的dp table 如图: 对应的dp table 如图:
![63.不同路径II2](https://img-blog.csdnimg.cn/20210104114610256.png) ![63.不同路径II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114610256.png)
如果这个图看不同,建议在理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下 如果这个图看不同,建议在理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下!
动规五部分分析完毕对应C++代码如下: 动规五部分分析完毕对应C++代码如下:
@ -163,6 +166,7 @@ public:
同样我们给出空间优化版本: 同样我们给出空间优化版本:
```CPP ```CPP
class Solution { class Solution {
public: public:
@ -369,6 +373,7 @@ func uniquePathsWithObstacles(obstacleGrid [][]int) int {
``` ```
### Javascript ### Javascript
```Javascript ```Javascript
var uniquePathsWithObstacles = function(obstacleGrid) { var uniquePathsWithObstacles = function(obstacleGrid) {
const m = obstacleGrid.length const m = obstacleGrid.length
@ -545,6 +550,7 @@ int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obst
``` ```
空间优化版本: 空间优化版本:
```c ```c
int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize){ int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize){
int m = obstacleGridSize; int m = obstacleGridSize;

View File

@ -72,7 +72,7 @@ dp[i] 爬到第i层楼梯有dp[i]种方法
3. dp数组如何初始化 3. dp数组如何初始化
在回顾一下dp[i]的定义爬到第i层楼梯有dp[i]中方法。 再回顾一下dp[i]的定义爬到第i层楼梯有dp[i]种方法。
那么i为0dp[i]应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。 那么i为0dp[i]应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。
@ -102,7 +102,8 @@ dp[i] 爬到第i层楼梯有dp[i]种方法
举例当n为5的时候dp tabledp数组应该是这样的 举例当n为5的时候dp tabledp数组应该是这样的
![70.爬楼梯](https://img-blog.csdnimg.cn/20210105202546299.png)
![70.爬楼梯](https://code-thinking-1253855093.file.myqcloud.com/pics/20210105202546299.png)
如果代码出问题了就把dp table 打印出来,看看究竟是不是和自己推导的一样。 如果代码出问题了就把dp table 打印出来,看看究竟是不是和自己推导的一样。

View File

@ -101,6 +101,11 @@ public:
}; };
``` ```
* 时间复杂度: O(nm)
* 空间复杂度: O(n)
代码中m表示最多可以爬m个台阶代码中把m改成2就是本题70.爬楼梯可以AC的代码了。 代码中m表示最多可以爬m个台阶代码中把m改成2就是本题70.爬楼梯可以AC的代码了。
## 总结 ## 总结
@ -128,12 +133,12 @@ Java
class Solution { class Solution {
public int climbStairs(int n) { public int climbStairs(int n) {
int[] dp = new int[n + 1]; int[] dp = new int[n + 1];
int[] weight = {1,2}; int m = 2;
dp[0] = 1; dp[0] = 1;
for (int i = 0; i <= n; i++) { for (int i = 1; i <= n; i++) { // 遍历背包
for (int j = 0; j < weight.length; j++) { for (int j = 1; j <= m; j++) { //遍历物品
if (i >= weight[j]) dp[i] += dp[i - weight[j]]; if (i >= j) dp[i] += dp[i - j];
} }
} }
@ -227,3 +232,4 @@ function climbStairs(n: number): number {
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a> </a>

View File

@ -169,7 +169,7 @@ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;
可以看出dp[i][j]是依赖左方,上方和左上方元素的,如图: 可以看出dp[i][j]是依赖左方,上方和左上方元素的,如图:
![72.编辑距离](https://img-blog.csdnimg.cn/20210114162113131.jpg) ![72.编辑距离](https://code-thinking-1253855093.file.myqcloud.com/pics/20210114162113131.jpg)
所以在dp矩阵中一定是从左到右从上到下去遍历。 所以在dp矩阵中一定是从左到右从上到下去遍历。
@ -193,7 +193,7 @@ for (int i = 1; i <= word1.size(); i++) {
以示例1为例输入`word1 = "horse", word2 = "ros"`为例dp矩阵状态图如下 以示例1为例输入`word1 = "horse", word2 = "ros"`为例dp矩阵状态图如下
![72.编辑距离1](https://img-blog.csdnimg.cn/20210114162132300.jpg) ![72.编辑距离1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210114162132300.jpg)
以上动规五部分析完毕C++代码如下: 以上动规五部分析完毕C++代码如下:
@ -218,6 +218,10 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n * m)
* 空间复杂度: O(n * m)
## 其他语言版本 ## 其他语言版本

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -7,6 +8,7 @@
# 第77题. 组合 # 第77题. 组合
[力扣题目链接](https://leetcode.cn/problems/combinations/ ) [力扣题目链接](https://leetcode.cn/problems/combinations/ )
@ -14,7 +16,7 @@
给定两个整数 n 和 k返回 1 ... n 中所有可能的 k 个数的组合。 给定两个整数 n 和 k返回 1 ... n 中所有可能的 k 个数的组合。
示例: 示例:
输入: n = 4, k = 2 输入: n = 4, k = 2
输出: 输出:
[ [
[2,4], [2,4],
@ -39,6 +41,7 @@
直接的解法当然是使用for循环例如示例中k为2很容易想到 用两个for循环这样就可以输出 和示例中一样的结果。 直接的解法当然是使用for循环例如示例中k为2很容易想到 用两个for循环这样就可以输出 和示例中一样的结果。
代码如下: 代码如下:
```CPP ```CPP
int n = 4; int n = 4;
for (int i = 1; i <= n; i++) { for (int i = 1; i <= n; i++) {
@ -86,7 +89,7 @@ for (int i = 1; i <= n; i++) {
那么我把组合问题抽象为如下树形结构: 那么我把组合问题抽象为如下树形结构:
![77.组合](https://img-blog.csdnimg.cn/20201123195223940.png) ![77.组合](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195223940.png)
可以看出这棵树,一开始集合是 1234 从左向右取数,取过的数,不再重复取。 可以看出这棵树,一开始集合是 1234 从左向右取数,取过的数,不再重复取。
@ -130,7 +133,7 @@ vector<int> path; // 用来存放符合条件结果
从下图中红线部分可以看出,在集合[1,2,3,4]取1之后下一层递归就要在[2,3,4]中取数了,那么下一层递归如何知道从[2,3,4]中取数呢靠的就是startIndex。 从下图中红线部分可以看出,在集合[1,2,3,4]取1之后下一层递归就要在[2,3,4]中取数了,那么下一层递归如何知道从[2,3,4]中取数呢靠的就是startIndex。
![77.组合2](https://img-blog.csdnimg.cn/20201123195328976.png) ![77.组合2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195328976.png)
所以需要startIndex来记录下一层递归搜索的起始位置。 所以需要startIndex来记录下一层递归搜索的起始位置。
@ -150,7 +153,7 @@ path这个数组的大小如果达到k说明我们找到了一个子集大小
如图红色部分: 如图红色部分:
![77.组合3](https://img-blog.csdnimg.cn/20201123195407907.png) ![77.组合3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195407907.png)
此时用result二维数组把path保存起来并终止本层递归。 此时用result二维数组把path保存起来并终止本层递归。
@ -167,7 +170,7 @@ if (path.size() == k) {
回溯法的搜索过程就是一个树型结构的遍历过程在如下图中可以看出for循环用来横向遍历递归的过程是纵向遍历。 回溯法的搜索过程就是一个树型结构的遍历过程在如下图中可以看出for循环用来横向遍历递归的过程是纵向遍历。
![77.组合1](https://img-blog.csdnimg.cn/20201123195242899.png) ![77.组合1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195242899.png)
如此我们才遍历完图中的这棵树。 如此我们才遍历完图中的这棵树。
@ -219,6 +222,7 @@ public:
还记得我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的回溯法模板么? 还记得我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的回溯法模板么?
如下: 如下:
``` ```
void backtracking(参数) { void backtracking(参数) {
if (终止条件) { if (终止条件) {
@ -266,7 +270,7 @@ for (int i = startIndex; i <= n; i++) {
这么说有点抽象,如图所示: 这么说有点抽象,如图所示:
![77.组合4](https://img-blog.csdnimg.cn/20210130194335207.png) ![77.组合4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210130194335207-20230310134409532.png)
图中每一个节点图中为矩形就代表本层的一个for循环那么每一层的for循环从第二个数开始遍历的话都没有意义都是无效遍历。 图中每一个节点图中为矩形就代表本层的一个for循环那么每一层的for循环从第二个数开始遍历的话都没有意义都是无效遍历。
@ -275,6 +279,7 @@ for (int i = startIndex; i <= n; i++) {
**如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了**。 **如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了**。
注意代码中i就是for循环里选择的起始位置。 注意代码中i就是for循环里选择的起始位置。
``` ```
for (int i = startIndex; i <= n; i++) { for (int i = startIndex; i <= n; i++) {
``` ```
@ -342,6 +347,7 @@ public:
### Java ### Java
```java ```java
class Solution { class Solution {
List<List<Integer>> result = new ArrayList<>(); List<List<Integer>> result = new ArrayList<>();
@ -417,6 +423,7 @@ class Solution:
``` ```
剪枝: 剪枝:
```python ```python
class Solution: class Solution:
def combine(self, n: int, k: int) -> List[List[int]]: def combine(self, n: int, k: int) -> List[List[int]]:
@ -435,6 +442,7 @@ class Solution:
``` ```
### Go ### Go
```Go ```Go
var ( var (
path []int path []int
@ -468,6 +476,7 @@ func dfs(n int, k int, start int) {
### javascript ### javascript
剪枝: 剪枝:
```javascript ```javascript
let result = [] let result = []
let path = [] let path = []
@ -536,6 +545,7 @@ impl Solution {
``` ```
剪枝 剪枝
```Rust ```Rust
impl Solution { impl Solution {
fn backtracking(result: &mut Vec<Vec<i32>>, path: &mut Vec<i32>, n: i32, k: i32, start_index: i32) { fn backtracking(result: &mut Vec<Vec<i32>>, path: &mut Vec<i32>, n: i32, k: i32, start_index: i32) {
@ -561,6 +571,7 @@ impl Solution {
``` ```
### C ### C
```c ```c
int* path; int* path;
int pathTop; int pathTop;
@ -615,6 +626,7 @@ int** combine(int n, int k, int* returnSize, int** returnColumnSizes){
``` ```
剪枝: 剪枝:
```c ```c
int* path; int* path;
int pathTop; int pathTop;
@ -701,6 +713,7 @@ func combine(_ n: Int, _ k: Int) -> [[Int]] {
### Scala ### Scala
暴力: 暴力:
```scala ```scala
object Solution { object Solution {
import scala.collection.mutable // 导包 import scala.collection.mutable // 导包

View File

@ -66,7 +66,7 @@ for (int i = startIndex; i <= n; i++) {
这么说有点抽象,如图所示: 这么说有点抽象,如图所示:
![77.组合4](https://img-blog.csdnimg.cn/20210130194335207.png) ![77.组合4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210130194335207.png)
图中每一个节点图中为矩形就代表本层的一个for循环那么每一层的for循环从第二个数开始遍历的话都没有意义都是无效遍历。 图中每一个节点图中为矩形就代表本层的一个for循环那么每一层的for循环从第二个数开始遍历的话都没有意义都是无效遍历。

View File

@ -48,7 +48,7 @@
以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下: 以示例中nums = [1,2,3]为例把求子集抽象为树型结构,如下:
![78.子集](https://img-blog.csdnimg.cn/202011232041348.png) ![78.子集](https://code-thinking.cdn.bcebos.com/pics/78.%E5%AD%90%E9%9B%86.png)
从图中红线部分,可以看出**遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合**。 从图中红线部分,可以看出**遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合**。
@ -72,7 +72,7 @@ void backtracking(vector<int>& nums, int startIndex) {
从图中可以看出: 从图中可以看出:
![78.子集](https://img-blog.csdnimg.cn/202011232041348.png) ![78.子集](https://code-thinking.cdn.bcebos.com/pics/78.%E5%AD%90%E9%9B%86.png)
剩余集合为空的时候,就是叶子节点。 剩余集合为空的时候,就是叶子节点。

View File

@ -42,7 +42,7 @@
用示例中的[1, 2, 2] 来举例,如图所示: **注意去重需要先对集合排序** 用示例中的[1, 2, 2] 来举例,如图所示: **注意去重需要先对集合排序**
![90.子集II](https://img-blog.csdnimg.cn/20201124195411977.png) ![90.子集II](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124195411977.png)
从图中可以看出同一树层上重复取2 就要过滤掉同一树枝上就可以重复取2因为同一树枝上元素的集合才是唯一子集 从图中可以看出同一树层上重复取2 就要过滤掉同一树枝上就可以重复取2因为同一树枝上元素的集合才是唯一子集

View File

@ -59,7 +59,8 @@
切割问题可以抽象为树型结构,如图: 切割问题可以抽象为树型结构,如图:
![93.复原IP地址](https://img-blog.csdnimg.cn/20201123203735933.png)
![93.复原IP地址](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123203735933.png)
## 回溯三部曲 ## 回溯三部曲
@ -110,7 +111,8 @@ if (pointNum == 3) { // 逗点数量为3时分隔结束
如果不合法就结束本层循环,如图中剪掉的分支: 如果不合法就结束本层循环,如图中剪掉的分支:
![93.复原IP地址](https://img-blog.csdnimg.cn/20201123203735933.png)
![93.复原IP地址](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123203735933-20230310132314109.png)
然后就是递归和回溯的过程: 然后就是递归和回溯的过程:

View File

@ -1,9 +1,11 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 96.不同的二叉搜索树 # 96.不同的二叉搜索树
[力扣题目链接](https://leetcode.cn/problems/unique-binary-search-trees/) [力扣题目链接](https://leetcode.cn/problems/unique-binary-search-trees/)
@ -12,7 +14,7 @@
示例: 示例:
![](https://img-blog.csdnimg.cn/20210113161941835.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210113161941835.png)
# 算法公开课 # 算法公开课
@ -27,11 +29,11 @@
了解了二叉搜索树之后,我们应该先举几个例子,画画图,看看有没有什么规律,如图: 了解了二叉搜索树之后,我们应该先举几个例子,画画图,看看有没有什么规律,如图:
![96.不同的二叉搜索树](https://img-blog.csdnimg.cn/20210107093106367.png) ![96.不同的二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210107093106367.png)
n为1的时候有一棵树n为2有两棵树这个是很直观的。 n为1的时候有一棵树n为2有两棵树这个是很直观的。
![96.不同的二叉搜索树1](https://img-blog.csdnimg.cn/20210107093129889.png) ![96.不同的二叉搜索树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210107093129889.png)
来看看n为3的时候有哪几种情况。 来看看n为3的时候有哪几种情况。
@ -65,7 +67,7 @@ dp[3],就是 元素1为头结点搜索树的数量 + 元素2为头结点搜索
如图所示: 如图所示:
![96.不同的二叉搜索树2](https://img-blog.csdnimg.cn/20210107093226241.png) ![96.不同的二叉搜索树2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210107093226241.png)
此时我们已经找到递推关系了,那么可以用动规五部曲再系统分析一遍。 此时我们已经找到递推关系了,那么可以用动规五部曲再系统分析一遍。
@ -118,7 +120,7 @@ for (int i = 1; i <= n; i++) {
n为5时候的dp数组状态如图 n为5时候的dp数组状态如图
![96.不同的二叉搜索树3](https://img-blog.csdnimg.cn/20210107093253987.png) ![96.不同的二叉搜索树3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210107093253987.png)
当然如果自己画图举例的话基本举例到n为3就可以了n为4的时候画图已经比较麻烦了。 当然如果自己画图举例的话基本举例到n为3就可以了n为4的时候画图已经比较麻烦了。
@ -169,6 +171,7 @@ public:
### Java ### Java
```Java ```Java
class Solution { class Solution {
public int numTrees(int n) { public int numTrees(int n) {
@ -190,6 +193,7 @@ class Solution {
``` ```
### Python ### Python
```python ```python
class Solution: class Solution:
def numTrees(self, n: int) -> int: def numTrees(self, n: int) -> int:
@ -202,6 +206,7 @@ class Solution:
``` ```
### Go ### Go
```Go ```Go
func numTrees(n int)int{ func numTrees(n int)int{
dp := make([]int, n+1) dp := make([]int, n+1)
@ -216,6 +221,7 @@ func numTrees(n int)int{
``` ```
### Javascript ### Javascript
```Javascript ```Javascript
const numTrees =(n) => { const numTrees =(n) => {
let dp = new Array(n+1).fill(0); let dp = new Array(n+1).fill(0);

View File

@ -18,7 +18,7 @@
* 节点的右子树只包含大于当前节点的数。 * 节点的右子树只包含大于当前节点的数。
* 所有左子树和右子树自身必须也是二叉搜索树。 * 所有左子树和右子树自身必须也是二叉搜索树。
![98.验证二叉搜索树](https://img-blog.csdnimg.cn/20210203144334501.png) ![98.验证二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000750.png)
# 视频讲解 # 视频讲解
@ -104,7 +104,7 @@ if (root->val > root->left->val && root->val < root->right->val) {
例如: [10,5,15,null,null,6,20] 这个case 例如: [10,5,15,null,null,6,20] 这个case
![二叉搜索树](https://img-blog.csdnimg.cn/20200812191501419.png) ![二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000824.png)
节点10大于左节点5小于右节点15但右子树里出现了一个6 这就不符合了! 节点10大于左节点5小于右节点15但右子树里出现了一个6 这就不符合了!

View File

@ -11,7 +11,7 @@
给定一个二叉树,检查它是否是镜像对称的。 给定一个二叉树,检查它是否是镜像对称的。
![101. 对称二叉树](https://img-blog.csdnimg.cn/20210203144607387.png) ![101. 对称二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203144607387.png)
# 思路 # 思路
@ -25,7 +25,7 @@
比较的是两个子树的里侧和外侧的元素是否相等。如图所示: 比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
![101. 对称二叉树1](https://img-blog.csdnimg.cn/20210203144624414.png) ![101. 对称二叉树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203144624414.png)
那么遍历的顺序应该是什么样的呢? 那么遍历的顺序应该是什么样的呢?

View File

@ -1,9 +1,11 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 二叉树层序遍历登场! # 二叉树层序遍历登场!
《代码随想录》算法视频公开课:[讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode102.二叉树的层序遍历](https://www.bilibili.com/video/BV1GY4y1u7b2),相信结合视频在看本篇题解,更有助于大家对本题的理解。 《代码随想录》算法视频公开课:[讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode102.二叉树的层序遍历](https://www.bilibili.com/video/BV1GY4y1u7b2),相信结合视频在看本篇题解,更有助于大家对本题的理解。
@ -35,7 +37,7 @@
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
![102.二叉树的层序遍历](https://img-blog.csdnimg.cn/20210203144842988.png) ![102.二叉树的层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203144842988.png)
思路: 思路:
@ -87,6 +89,7 @@ public:
} }
}; };
``` ```
```CPP ```CPP
# 递归法 # 递归法
class Solution { class Solution {
@ -168,7 +171,6 @@ python3代码
```python ```python
class Solution: class Solution:
"""二叉树层序遍历迭代解法""" """二叉树层序遍历迭代解法"""
@ -194,6 +196,7 @@ class Solution:
return results return results
``` ```
```python ```python
# 递归法 # 递归法
class Solution: class Solution:
@ -387,7 +390,9 @@ func levelOrder(_ root: TreeNode?) -> [[Int]] {
return result return result
} }
``` ```
Scala: Scala:
```scala ```scala
// 102.二叉树的层序遍历 // 102.二叉树的层序遍历
object Solution { object Solution {
@ -455,7 +460,7 @@ impl Solution {
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
![107.二叉树的层次遍历II](https://img-blog.csdnimg.cn/20210203151058308.png) ![107.二叉树的层次遍历II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203151058308.png)
思路: 思路:
@ -488,6 +493,7 @@ public:
} }
}; };
``` ```
python代码 python代码
```python ```python
@ -719,6 +725,7 @@ func levelOrderBottom(_ root: TreeNode?) -> [[Int]] {
Scala: Scala:
```scala ```scala
// 107.二叉树的层次遍历II // 107.二叉树的层次遍历II
object Solution { object Solution {
@ -781,7 +788,7 @@ impl Solution {
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
![199.二叉树的右视图](https://img-blog.csdnimg.cn/20210203151307377.png) ![199.二叉树的右视图](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203151307377.png)
思路: 思路:
@ -810,6 +817,7 @@ public:
} }
}; };
``` ```
python代码 python代码
```python ```python
@ -892,7 +900,6 @@ public class N0199 {
go: go:
```GO ```GO
/** /**
199. 二叉树的右视图 199. 二叉树的右视图
*/ */
@ -997,6 +1004,7 @@ func rightSideView(_ root: TreeNode?) -> [Int] {
``` ```
Scala: Scala:
```scala ```scala
// 199.二叉树的右视图 // 199.二叉树的右视图
object Solution { object Solution {
@ -1060,7 +1068,7 @@ impl Solution {
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
![637.二叉树的层平均值](https://img-blog.csdnimg.cn/20210203151350500.png) ![637.二叉树的层平均值](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203151350500.png)
思路: 思路:
@ -1125,7 +1133,6 @@ class Solution:
java: java:
```java ```java
// 637. 二叉树的层平均值 // 637. 二叉树的层平均值
public class N0637 { public class N0637 {
@ -1280,7 +1287,9 @@ func averageOfLevels(_ root: TreeNode?) -> [Double] {
return result return result
} }
``` ```
Scala: Scala:
```scala ```scala
// 637.二叉树的层平均值 // 637.二叉树的层平均值
object Solution { object Solution {
@ -1346,7 +1355,7 @@ impl Solution {
例如,给定一个 3叉树 : 例如,给定一个 3叉树 :
![429. N叉树的层序遍历](https://img-blog.csdnimg.cn/20210203151439168.png) ![429. N叉树的层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203151439168.png)
返回其层序遍历: 返回其层序遍历:
@ -1432,10 +1441,10 @@ class Solution:
traversal(root,0) traversal(root,0)
return result return result
``` ```
java: java:
```java ```java
// 429. N 叉树的层序遍历 // 429. N 叉树的层序遍历
public class N0429 { public class N0429 {
/** /**
@ -1602,6 +1611,7 @@ func levelOrder(_ root: Node?) -> [[Int]] {
``` ```
Scala: Scala:
```scala ```scala
// 429.N叉树的层序遍历 // 429.N叉树的层序遍历
object Solution { object Solution {
@ -1683,7 +1693,7 @@ impl Solution {
您需要在二叉树的每一行中找到最大的值。 您需要在二叉树的每一行中找到最大的值。
![515.在每个树行中找最大值](https://img-blog.csdnimg.cn/20210203151532153.png) ![515.在每个树行中找最大值](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203151532153.png)
思路: 思路:
@ -1714,6 +1724,7 @@ public:
} }
}; };
``` ```
python代码 python代码
```python ```python
@ -1734,6 +1745,7 @@ class Solution:
out_list.append(max(in_list)) out_list.append(max(in_list))
return out_list return out_list
``` ```
java代码 java代码
```java ```java
@ -1884,6 +1896,7 @@ func largestValues(_ root: TreeNode?) -> [Int] {
``` ```
Scala: Scala:
```scala ```scala
// 515.在每个树行中找最大值 // 515.在每个树行中找最大值
object Solution { object Solution {
@ -1959,9 +1972,9 @@ struct Node {
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。 填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。 初始状态下,所有 next 指针都被设置为 NULL。
![116.填充每个节点的下一个右侧节点指针](https://img-blog.csdnimg.cn/20210203152044855.jpg) ![116.填充每个节点的下一个右侧节点指针](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203152044855.jpg)
思路: 思路:
@ -2067,6 +2080,7 @@ class Solution:
first = first.left # 从本层扩展到下一层 first = first.left # 从本层扩展到下一层
return root return root
``` ```
go: go:
```GO ```GO
@ -2108,8 +2122,8 @@ func connect(root *Node) *Node {
``` ```
JavaScript: JavaScript:
```javascript
```javascript
/** /**
* // Definition for a Node. * // Definition for a Node.
* function Node(val, left, right, next) { * function Node(val, left, right, next) {
@ -2142,6 +2156,7 @@ var connect = function(root) {
}; };
``` ```
TypeScript: TypeScript:
```typescript ```typescript
@ -2200,6 +2215,7 @@ func connect(_ root: Node?) -> Node? {
``` ```
Scala: Scala:
```scala ```scala
// 116.填充每个节点的下一个右侧节点指针 // 116.填充每个节点的下一个右侧节点指针
object Solution { object Solution {
@ -2228,6 +2244,7 @@ object Solution {
} }
} }
``` ```
# 117.填充每个节点的下一个右侧节点指针II # 117.填充每个节点的下一个右侧节点指针II
[力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) [力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/)
@ -2307,6 +2324,7 @@ class Solution {
} }
} }
``` ```
python代码 python代码
```python ```python
@ -2329,6 +2347,7 @@ class Solution:
return root return root
``` ```
go: go:
```GO ```GO
@ -2369,6 +2388,7 @@ func connect(root *Node) *Node {
``` ```
JavaScript: JavaScript:
```javascript ```javascript
/** /**
* // Definition for a Node. * // Definition for a Node.
@ -2401,6 +2421,7 @@ var connect = function(root) {
return root; return root;
}; };
``` ```
TypeScript: TypeScript:
```typescript ```typescript
@ -2459,6 +2480,7 @@ func connect(_ root: Node?) -> Node? {
``` ```
Scala: Scala:
```scala ```scala
// 117.填充每个节点的下一个右侧节点指针II // 117.填充每个节点的下一个右侧节点指针II
object Solution { object Solution {
@ -2487,6 +2509,7 @@ object Solution {
} }
} }
``` ```
# 104.二叉树的最大深度 # 104.二叉树的最大深度
[力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) [力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-binary-tree/)
@ -2501,7 +2524,7 @@ object Solution {
给定二叉树 [3,9,20,null,null,15,7] 给定二叉树 [3,9,20,null,null,15,7]
![104. 二叉树的最大深度](https://img-blog.csdnimg.cn/20210203153031914.png) ![104. 二叉树的最大深度](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203153031914-20230310134849764.png)
返回它的最大深度 3 。 返回它的最大深度 3 。
@ -2511,7 +2534,7 @@ object Solution {
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示: 在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
![层序遍历](https://img-blog.csdnimg.cn/20200810193056585.png) ![层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20200810193056585-20230310134854803.png)
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
@ -2541,6 +2564,7 @@ public:
``` ```
Java Java
```Java ```Java
class Solution { class Solution {
public int maxDepth(TreeNode root) { public int maxDepth(TreeNode root) {
@ -2566,6 +2590,7 @@ class Solution {
``` ```
Python Python
```python 3 ```python 3
class Solution: class Solution:
def maxDepth(self, root: TreeNode) -> int: def maxDepth(self, root: TreeNode) -> int:
@ -2623,6 +2648,7 @@ func maxDepth(root *TreeNode) int {
``` ```
JavaScript JavaScript
```javascript ```javascript
/** /**
* Definition for a binary tree node. * Definition for a binary tree node.
@ -2700,6 +2726,7 @@ func maxDepth(_ root: TreeNode?) -> Int {
``` ```
Scala: Scala:
```scala ```scala
// 104.二叉树的最大深度 // 104.二叉树的最大深度
object Solution { object Solution {
@ -2790,6 +2817,7 @@ public:
``` ```
Java Java
```java ```java
class Solution { class Solution {
public int minDepth(TreeNode root){ public int minDepth(TreeNode root){
@ -2890,6 +2918,7 @@ func minDepth(root *TreeNode) int {
``` ```
JavaScript JavaScript
```javascript ```javascript
/** /**
* Definition for a binary tree node. * Definition for a binary tree node.
@ -2972,6 +3001,7 @@ func minDepth(_ root: TreeNode?) -> Int {
``` ```
Scala: Scala:
```scala ```scala
// 111.二叉树的最小深度 // 111.二叉树的最小深度
object Solution { object Solution {

View File

@ -18,7 +18,8 @@
示例: 示例:
给定二叉树 [3,9,20,null,null,15,7] 给定二叉树 [3,9,20,null,null,15,7]
![104. 二叉树的最大深度](https://img-blog.csdnimg.cn/20210203153031914.png)
![104. 二叉树的最大深度](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203153031914-20230310121809902.png)
返回它的最大深度 3 。 返回它的最大深度 3 。
@ -49,7 +50,7 @@
代码如下: 代码如下:
```CPP ```CPP
int getdepth(treenode* node) int getdepth(TreeNode* node)
``` ```
2. 确定终止条件如果为空节点的话就返回0表示高度为0。 2. 确定终止条件如果为空节点的话就返回0表示高度为0。
@ -75,14 +76,14 @@ return depth;
```CPP ```CPP
class solution { class solution {
public: public:
int getdepth(treenode* node) { int getdepth(TreeNode* node) {
if (node == NULL) return 0; if (node == NULL) return 0;
int leftdepth = getdepth(node->left); // 左 int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右 int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中 int depth = 1 + max(leftdepth, rightdepth); // 中
return depth; return depth;
} }
int maxdepth(treenode* root) { int maxDepth(TreeNode* root) {
return getdepth(root); return getdepth(root);
} }
}; };
@ -92,9 +93,9 @@ public:
```CPP ```CPP
class solution { class solution {
public: public:
int maxdepth(treenode* root) { int maxDepth(TreeNode* root) {
if (root == null) return 0; if (root == null) return 0;
return 1 + max(maxdepth(root->left), maxdepth(root->right)); return 1 + max(maxDepth(root->left), maxDepth(root->right));
} }
}; };
@ -109,7 +110,7 @@ public:
class solution { class solution {
public: public:
int result; int result;
void getdepth(treenode* node, int depth) { void getdepth(TreeNode* node, int depth) {
result = depth > result ? depth : result; // 中 result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ; if (node->left == NULL && node->right == NULL) return ;
@ -126,7 +127,7 @@ public:
} }
return ; return ;
} }
int maxdepth(treenode* root) { int maxDepth(TreeNode* root) {
result = 0; result = 0;
if (root == NULL) return result; if (root == NULL) return result;
getdepth(root, 1); getdepth(root, 1);
@ -143,7 +144,7 @@ public:
class solution { class solution {
public: public:
int result; int result;
void getdepth(treenode* node, int depth) { void getdepth(TreeNode* node, int depth) {
result = depth > result ? depth : result; // 中 result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ; if (node->left == NULL && node->right == NULL) return ;
if (node->left) { // 左 if (node->left) { // 左
@ -154,7 +155,7 @@ public:
} }
return ; return ;
} }
int maxdepth(treenode* root) { int maxDepth(TreeNode* root) {
result = 0; result = 0;
if (root == 0) return result; if (root == 0) return result;
getdepth(root, 1); getdepth(root, 1);
@ -169,7 +170,8 @@ public:
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示: 在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
![层序遍历](https://img-blog.csdnimg.cn/20200810193056585.png)
![层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20200810193056585.png)
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
@ -180,16 +182,16 @@ c++代码如下:
```CPP ```CPP
class solution { class solution {
public: public:
int maxdepth(treenode* root) { int maxDepth(TreeNode* root) {
if (root == NULL) return 0; if (root == NULL) return 0;
int depth = 0; int depth = 0;
queue<treenode*> que; queue<TreeNode*> que;
que.push(root); que.push(root);
while(!que.empty()) { while(!que.empty()) {
int size = que.size(); int size = que.size();
depth++; // 记录深度 depth++; // 记录深度
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
treenode* node = que.front(); TreeNode* node = que.front();
que.pop(); que.pop();
if (node->left) que.push(node->left); if (node->left) que.push(node->left);
if (node->right) que.push(node->right); if (node->right) que.push(node->right);
@ -213,7 +215,7 @@ public:
例如,给定一个 3叉树 : 例如,给定一个 3叉树 :
![559.n叉树的最大深度](https://img-blog.csdnimg.cn/2021020315313214.png) ![559.n叉树的最大深度](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020315313214.png)
我们应返回其最大深度3。 我们应返回其最大深度3。
@ -228,11 +230,11 @@ c++代码:
```CPP ```CPP
class solution { class solution {
public: public:
int maxdepth(node* root) { int maxDepth(Node* root) {
if (root == 0) return 0; if (root == 0) return 0;
int depth = 0; int depth = 0;
for (int i = 0; i < root->children.size(); i++) { for (int i = 0; i < root->children.size(); i++) {
depth = max (depth, maxdepth(root->children[i])); depth = max (depth, maxDepth(root->children[i]));
} }
return depth + 1; return depth + 1;
} }
@ -245,15 +247,15 @@ public:
```CPP ```CPP
class solution { class solution {
public: public:
int maxdepth(node* root) { int maxDepth(Node* root) {
queue<node*> que; queue<Node*> que;
if (root != NULL) que.push(root); if (root != NULL) que.push(root);
int depth = 0; int depth = 0;
while (!que.empty()) { while (!que.empty()) {
int size = que.size(); int size = que.size();
depth++; // 记录深度 depth++; // 记录深度
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
node* node = que.front(); Node* node = que.front();
que.pop(); que.pop();
for (int j = 0; j < node->children.size(); j++) { for (int j = 0; j < node->children.size(); j++) {
if (node->children[j]) que.push(node->children[j]); if (node->children[j]) que.push(node->children[j]);

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,6 +6,7 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
看完本文,可以一起解决如下两道题目 看完本文,可以一起解决如下两道题目
* 106.从中序与后序遍历序列构造二叉树 * 106.从中序与后序遍历序列构造二叉树
@ -21,11 +23,11 @@
例如,给出 例如,给出
* 中序遍历 inorder = [9,3,15,20,7] * 中序遍历 inorder = [9,3,15,20,7]
* 后序遍历 postorder = [9,15,7,20,3] * 后序遍历 postorder = [9,15,7,20,3]
返回如下的二叉树: 返回如下的二叉树:
![106. 从中序与后序遍历序列构造二叉树1](https://img-blog.csdnimg.cn/20210203154316774.png) ![106. 从中序与后序遍历序列构造二叉树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203154316774.png)
# 视频讲解 # 视频讲解
@ -40,7 +42,7 @@
流程如图: 流程如图:
![106.从中序与后序遍历序列构造二叉树](https://img-blog.csdnimg.cn/20210203154249860.png) ![106.从中序与后序遍历序列构造二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203154249860.png)
那么代码应该怎么写呢? 那么代码应该怎么写呢?
@ -280,6 +282,7 @@ public:
下面给出用下标索引写出的代码版本思路是一样的只不过不用重复定义vector了每次用下标索引来分割 下面给出用下标索引写出的代码版本思路是一样的只不过不用重复定义vector了每次用下标索引来分割
### C++优化版本 ### C++优化版本
```CPP ```CPP
class Solution { class Solution {
private: private:
@ -411,11 +414,11 @@ public:
例如,给出 例如,给出
前序遍历 preorder = [3,9,20,15,7] 前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7] 中序遍历 inorder = [9,3,15,20,7]
返回如下的二叉树: 返回如下的二叉树:
![105. 从前序与中序遍历序列构造二叉树](https://img-blog.csdnimg.cn/20210203154626672.png) ![105. 从前序与中序遍历序列构造二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203154626672.png)
## 思路 ## 思路
@ -558,7 +561,7 @@ public:
举一个例子: 举一个例子:
![106.从中序与后序遍历序列构造二叉树2](https://img-blog.csdnimg.cn/20210203154720326.png) ![106.从中序与后序遍历序列构造二叉树2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203154720326.png)
tree1 的前序遍历是[1 2 3] 后序遍历是[3 2 1]。 tree1 的前序遍历是[1 2 3] 后序遍历是[3 2 1]。
@ -653,6 +656,7 @@ class Solution {
``` ```
## Python ## Python
```python ```python
class Solution: class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:

View File

@ -17,7 +17,8 @@
示例: 示例:
![108.将有序数组转换为二叉搜索树](https://img-blog.csdnimg.cn/20201022164420763.png)
![108.将有序数组转换为二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20201022164420763.png)
# 算法公开课 # 算法公开课

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,6 +6,7 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 求高度还是求深度,你搞懂了不? > 求高度还是求深度,你搞懂了不?
# 110.平衡二叉树 # 110.平衡二叉树
@ -13,13 +15,13 @@
给定一个二叉树,判断它是否是高度平衡的二叉树。 给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
示例 1: 示例 1:
给定二叉树 [3,9,20,null,null,15,7] 给定二叉树 [3,9,20,null,null,15,7]
![110.平衡二叉树](https://img-blog.csdnimg.cn/2021020315542230.png) ![110.平衡二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020315542230.png)
返回 true 。 返回 true 。
@ -27,7 +29,7 @@
给定二叉树 [1,2,2,3,3,null,null,4,4] 给定二叉树 [1,2,2,3,3,null,null,4,4]
![110.平衡二叉树1](https://img-blog.csdnimg.cn/20210203155447919.png) ![110.平衡二叉树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203155447919.png)
返回 false 。 返回 false 。
@ -45,7 +47,7 @@
但leetcode中强调的深度和高度很明显是按照节点来计算的如图 但leetcode中强调的深度和高度很明显是按照节点来计算的如图
![110.平衡二叉树2](https://img-blog.csdnimg.cn/20210203155515650.png) ![110.平衡二叉树2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203155515650.png)
关于根节点的深度究竟是1 还是 0不同的地方有不一样的标准leetcode的题目中都是以节点为一度即根节点深度是1。但维基百科上定义用边为一度即根节点的深度是0我们暂时以leetcode为准毕竟要在这上面刷题 关于根节点的深度究竟是1 还是 0不同的地方有不一样的标准leetcode的题目中都是以节点为一度即根节点深度是1。但维基百科上定义用边为一度即根节点的深度是0我们暂时以leetcode为准毕竟要在这上面刷题
@ -499,6 +501,7 @@ class Solution {
### Python ### Python
递归法: 递归法:
```python ```python
# Definition for a binary tree node. # Definition for a binary tree node.
# class TreeNode: # class TreeNode:
@ -531,6 +534,7 @@ class Solution:
``` ```
迭代法: 迭代法:
```python ```python
class Solution: class Solution:
def isBalanced(self, root: Optional[TreeNode]) -> bool: def isBalanced(self, root: Optional[TreeNode]) -> bool:
@ -557,6 +561,7 @@ class Solution:
### Go ### Go
```Go ```Go
func isBalanced(root *TreeNode) bool { func isBalanced(root *TreeNode) bool {
h := getHeight(root) h := getHeight(root)
@ -588,7 +593,9 @@ func max(a, b int) int {
``` ```
### JavaScript ### JavaScript
递归法: 递归法:
```javascript ```javascript
var isBalanced = function(root) { var isBalanced = function(root) {
//还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1 //还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1
@ -614,6 +621,7 @@ var isBalanced = function(root) {
``` ```
迭代法: 迭代法:
```javascript ```javascript
// 获取当前节点的高度 // 获取当前节点的高度
var getHeight = function (curNode) { var getHeight = function (curNode) {
@ -676,6 +684,7 @@ function isBalanced(root: TreeNode | null): boolean {
### C ### C
递归法: 递归法:
```c ```c
int getDepth(struct TreeNode* node) { int getDepth(struct TreeNode* node) {
//如果结点不存在返回0 //如果结点不存在返回0
@ -706,6 +715,7 @@ bool isBalanced(struct TreeNode* root) {
``` ```
迭代法: 迭代法:
```c ```c
//计算结点深度 //计算结点深度
int getDepth(struct TreeNode* node) { int getDepth(struct TreeNode* node) {
@ -780,6 +790,7 @@ bool isBalanced(struct TreeNode* root){
### Swift: ### Swift:
>递归 >递归
```swift ```swift
func isBalanced(_ root: TreeNode?) -> Bool { func isBalanced(_ root: TreeNode?) -> Bool {
// -1 已经不是平衡二叉树 // -1 已经不是平衡二叉树

View File

@ -21,7 +21,8 @@
给定二叉树 [3,9,20,null,null,15,7], 给定二叉树 [3,9,20,null,null,15,7],
![111.二叉树的最小深度1](https://img-blog.csdnimg.cn/2021020315582586.png)
![111.二叉树的最小深度1](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020315582586.png)
返回它的最小深度 2. 返回它的最小深度 2.
@ -45,7 +46,7 @@
本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图: 本题还有一个误区,在处理节点的过程中,最大深度很容易理解,最小深度就不那么好理解,如图:
![111.二叉树的最小深度](https://img-blog.csdnimg.cn/20210203155800503.png) ![111.二叉树的最小深度](https://code-thinking.cdn.bcebos.com/pics/111.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.png)
这就重新审题了,题目中说的是:**最小深度是从根节点到最近叶子节点的最短路径上的节点数量。**,注意是**叶子节点**。 这就重新审题了,题目中说的是:**最小深度是从根节点到最近叶子节点的最短路径上的节点数量。**,注意是**叶子节点**。
@ -87,7 +88,7 @@ return result;
这个代码就犯了此图中的误区: 这个代码就犯了此图中的误区:
![111.二叉树的最小深度](https://img-blog.csdnimg.cn/20210203155800503.png) ![111.二叉树的最小深度](https://code-thinking.cdn.bcebos.com/pics/111.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E6%B7%B1%E5%BA%A6.png)
如果这么求的话,没有左孩子的分支会算为最短深度。 如果这么求的话,没有左孩子的分支会算为最短深度。

View File

@ -1,18 +1,20 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 112. 路径总和 # 112. 路径总和
[力扣题目链接](https://leetcode.cn/problems/path-sum/) [力扣题目链接](https://leetcode.cn/problems/path-sum/)
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。 说明: 叶子节点是指没有子节点的节点。
示例:  示例:
给定如下二叉树,以及目标和 sum = 22 给定如下二叉树,以及目标和 sum = 22
![112.路径总和1](https://img-blog.csdnimg.cn/20210203160355234.png) ![112.路径总和1](https://img-blog.csdnimg.cn/20210203160355234.png)
@ -53,7 +55,7 @@
如图所示: 如图所示:
![112.路径总和](https://img-blog.csdnimg.cn/2021020316051216.png) ![112.路径总和](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020316051216.png)
图中可以看出遍历的路线并不要遍历整棵树所以递归函数需要返回值可以用bool类型表示。 图中可以看出遍历的路线并不要遍历整棵树所以递归函数需要返回值可以用bool类型表示。
@ -222,13 +224,13 @@ public:
给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。 说明: 叶子节点是指没有子节点的节点。
示例: 示例:
给定如下二叉树,以及目标和 sum = 22 给定如下二叉树,以及目标和 sum = 22
![113.路径总和ii1.png](https://img-blog.csdnimg.cn/20210203160854654.png) ![113.路径总和ii1.png](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203160854654.png)
## 思路 ## 思路
@ -237,7 +239,7 @@ public:
如图: 如图:
![113.路径总和ii](https://img-blog.csdnimg.cn/20210203160922745.png) ![113.路径总和ii](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203160922745.png)
为了尽可能的把细节体现出来,我写出如下代码(**这份代码并不简洁,但是逻辑非常清晰** 为了尽可能的把细节体现出来,我写出如下代码(**这份代码并不简洁,但是逻辑非常清晰**
@ -248,7 +250,7 @@ private:
vector<vector<int>> result; vector<vector<int>> result;
vector<int> path; vector<int> path;
// 递归函数不需要返回值,因为我们要遍历整个树 // 递归函数不需要返回值,因为我们要遍历整个树
void traversal(treenode* cur, int count) { void traversal(TreeNode* cur, int count) {
if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径 if (!cur->left && !cur->right && count == 0) { // 遇到了叶子节点且找到了和为sum的路径
result.push_back(path); result.push_back(path);
return; return;
@ -274,10 +276,10 @@ private:
} }
public: public:
vector<vector<int>> pathsum(treenode* root, int sum) { vector<vector<int>> pathSum(TreeNode* root, int sum) {
result.clear(); result.clear();
path.clear(); path.clear();
if (root == null) return result; if (root == NULL) return result;
path.push_back(root->val); // 把根节点放进路径 path.push_back(root->val); // 把根节点放进路径
traversal(root, sum - root->val); traversal(root, sum - root->val);
return result; return result;
@ -303,6 +305,7 @@ public:
## java ## java
### 0112.路径总和 ### 0112.路径总和
```java ```java
class solution { class solution {
public boolean haspathsum(treenode root, int targetsum) { public boolean haspathsum(treenode root, int targetsum) {
@ -344,7 +347,9 @@ class solution {
} }
} }
``` ```
迭代 迭代
```java ```java
class solution { class solution {
public boolean haspathsum(treenode root, int targetsum) { public boolean haspathsum(treenode root, int targetsum) {
@ -447,6 +452,7 @@ class Solution {
### 0112.路径总和 ### 0112.路径总和
**递归** **递归**
```python ```python
class solution: class solution:
def haspathsum(self, root: treenode, targetsum: int) -> bool: def haspathsum(self, root: treenode, targetsum: int) -> bool:
@ -469,9 +475,16 @@ class solution:
return false # 别忘记处理空treenode return false # 别忘记处理空treenode
else: else:
return isornot(root, targetsum - root.val) return isornot(root, targetsum - root.val)
class Solution: # 简洁版
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
if not root: return False
if root.left==root.right==None and root.val == targetSum: return True
return self.hasPathSum(root.left,targetSum-root.val) or self.hasPathSum(root.right,targetSum-root.val)
``` ```
**迭代 - 层序遍历** **迭代 - 层序遍历**
```python ```python
class solution: class solution:
def haspathsum(self, root: treenode, targetsum: int) -> bool: def haspathsum(self, root: treenode, targetsum: int) -> bool:
@ -499,6 +512,7 @@ class solution:
### 0113.路径总和-ii ### 0113.路径总和-ii
**递归** **递归**
```python ```python
class solution: class solution:
def pathsum(self, root: treenode, targetsum: int) -> list[list[int]]: def pathsum(self, root: treenode, targetsum: int) -> list[list[int]]:
@ -528,6 +542,7 @@ class solution:
``` ```
**迭代法,用第二个队列保存目前的总和与路径** **迭代法,用第二个队列保存目前的总和与路径**
```python ```python
class Solution: class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]: def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
@ -551,6 +566,26 @@ class Solution:
return result return result
``` ```
**迭代法,前序遍历**
```python
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
if not root: return []
stack, path_stack,result = [[root,root.val]],[[root.val]],[]
while stack:
cur,cursum = stack.pop()
path = path_stack.pop()
if cur.left==cur.right==None:
if cursum==targetSum: result.append(path)
if cur.right:
stack.append([cur.right,cursum+cur.right.val])
path_stack.append(path+[cur.right.val])
if cur.left:
stack.append([cur.left,cursum+cur.left.val])
path_stack.append(path+[cur.left.val])
return result
```
## go ## go
### 112. 路径总和 ### 112. 路径总和
@ -623,6 +658,7 @@ func traverse(node *TreeNode, result *[][]int, currPath *[]int, targetSum int) {
### 0112.路径总和 ### 0112.路径总和
**递归** **递归**
```javascript ```javascript
/** /**
* @param {treenode} root * @param {treenode} root
@ -652,7 +688,9 @@ let haspathsum = function (root, targetsum) {
// return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val); // return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
}; };
``` ```
**迭代** **迭代**
```javascript ```javascript
let hasPathSum = function(root, targetSum) { let hasPathSum = function(root, targetSum) {
if(root === null) return false; if(root === null) return false;
@ -684,6 +722,7 @@ let hasPathSum = function(root, targetSum) {
### 0113.路径总和-ii ### 0113.路径总和-ii
**递归** **递归**
```javascript ```javascript
let pathsum = function (root, targetsum) { let pathsum = function (root, targetsum) {
// 递归法 // 递归法
@ -715,7 +754,9 @@ let pathsum = function (root, targetsum) {
return res; return res;
}; };
``` ```
**递归 精简版** **递归 精简版**
```javascript ```javascript
var pathsum = function(root, targetsum) { var pathsum = function(root, targetsum) {
//递归方法 //递归方法
@ -739,7 +780,9 @@ var pathsum = function(root, targetsum) {
return resPath; return resPath;
}; };
``` ```
**迭代** **迭代**
```javascript ```javascript
let pathSum = function(root, targetSum) { let pathSum = function(root, targetSum) {
if(root === null) return []; if(root === null) return [];
@ -933,7 +976,9 @@ func traversal(_ cur: TreeNode?, _ count: Int) -> Bool {
return false return false
} }
``` ```
**迭代** **迭代**
```swift ```swift
func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool { func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool {
guard let root = root else { guard let root = root else {
@ -1015,8 +1060,10 @@ func traversal(_ cur: TreeNode?, count: Int) {
``` ```
## C ## C
> 0112.路径总和 > 0112.路径总和
递归法: > 递归法:
```c ```c
bool hasPathSum(struct TreeNode* root, int targetSum){ bool hasPathSum(struct TreeNode* root, int targetSum){
// 递归结束条件若当前节点不存在返回false // 递归结束条件若当前节点不存在返回false
@ -1032,6 +1079,7 @@ bool hasPathSum(struct TreeNode* root, int targetSum){
``` ```
迭代法: 迭代法:
```c ```c
// 存储一个节点以及当前的和 // 存储一个节点以及当前的和
struct Pair { struct Pair {
@ -1070,7 +1118,9 @@ bool hasPathSum(struct TreeNode* root, int targetSum){
return false; return false;
} }
``` ```
> 0113.路径总和 II > 0113.路径总和 II
```c ```c
int** ret; int** ret;
int* path; int* path;
@ -1139,6 +1189,7 @@ int** pathSum(struct TreeNode* root, int targetSum, int* returnSize, int** retur
### 0112.路径总和 ### 0112.路径总和
**递归:** **递归:**
```scala ```scala
object Solution { object Solution {
def hasPathSum(root: TreeNode, targetSum: Int): Boolean = { def hasPathSum(root: TreeNode, targetSum: Int): Boolean = {
@ -1163,6 +1214,7 @@ object Solution {
``` ```
**迭代:** **迭代:**
```scala ```scala
object Solution { object Solution {
import scala.collection.mutable import scala.collection.mutable
@ -1187,6 +1239,7 @@ object Solution {
### 0113.路径总和 II ### 0113.路径总和 II
**递归:** **递归:**
```scala ```scala
object Solution { object Solution {
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer

View File

@ -149,6 +149,11 @@ public:
}; };
``` ```
* 时间复杂度: O(n * m)
* 空间复杂度: O(n * m)
## 其他语言版本 ## 其他语言版本

View File

@ -127,7 +127,8 @@ dp[0][1]表示第0天不持有股票不持有股票那么现金就是0
以示例1输入[7,1,5,3,6,4]为例dp数组状态如下 以示例1输入[7,1,5,3,6,4]为例dp数组状态如下
![121.买卖股票的最佳时机](https://img-blog.csdnimg.cn/20210224225642465.png)
![121.买卖股票的最佳时机](https://code-thinking-1253855093.file.myqcloud.com/pics/20210224225642465.png)
dp[5][1]就是最终结果。 dp[5][1]就是最终结果。

View File

@ -4,7 +4,6 @@
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 122.买卖股票的最佳时机 II # 122.买卖股票的最佳时机 II
[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) [力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/)
@ -15,32 +14,39 @@
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1: 示例 1:
* 输入: [7,1,5,3,6,4]
* 输出: 7 - 输入: [7,1,5,3,6,4]
* 解释: 在第 2 天(股票价格 = 1的时候买入在第 3 天(股票价格 = 5的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后在第 4 天(股票价格 = 3的时候买入在第 5 天(股票价格 = 6的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。 - 输出: 7
- 解释: 在第 2 天(股票价格 = 1的时候买入在第 3 天(股票价格 = 5的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后在第 4 天(股票价格 = 3的时候买入在第 5 天(股票价格 = 6的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2: 示例 2:
* 输入: [1,2,3,4,5]
* 输出: 4 - 输入: [1,2,3,4,5]
* 解释: 在第 1 天(股票价格 = 1的时候买入在第 5 天 (股票价格 = 5的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。 - 输出: 4
- 解释: 在第 1 天(股票价格 = 1的时候买入在第 5 天 (股票价格 = 5的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例  3: 示例  3:
* 输入: [7,6,4,3,1]
* 输出: 0 - 输入: [7,6,4,3,1]
* 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。 - 输出: 0
- 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示: 提示:
* 1 <= prices.length <= 3 * 10 ^ 4
* 0 <= prices[i] <= 10 ^ 4 - 1 <= prices.length <= 3 \* 10 ^ 4
- 0 <= prices[i] <= 10 ^ 4
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法也能解决股票问题LeetCode122.买卖股票最佳时机 II](https://www.bilibili.com/video/BV1ev4y1C7na),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
本题首先要清楚两点: 本题首先要清楚两点:
* 只有一只股票! - 只有一只股票!
* 当前只有买股票或者卖股票的操作 - 当前只有买股票或者卖股票的操作
想获得利润至少要两天为一个交易单元。 想获得利润至少要两天为一个交易单元。
@ -62,7 +68,7 @@
如图: 如图:
![122.买卖股票的最佳时机II](https://img-blog.csdnimg.cn/2020112917480858.png) ![122.买卖股票的最佳时机II](https://code-thinking-1253855093.file.myqcloud.com/pics/2020112917480858-20230310134659477.png)
一些同学陷入:第一天怎么就没有利润呢,第一天到底算不算的困惑中。 一些同学陷入:第一天怎么就没有利润呢,第一天到底算不算的困惑中。
@ -91,8 +97,8 @@ public:
}; };
``` ```
* 时间复杂度O(n) - 时间复杂度O(n)
* 空间复杂度O(1) - 空间复杂度O(1)
### 动态规划 ### 动态规划
@ -118,8 +124,8 @@ public:
}; };
``` ```
* 时间复杂度:$O(n)$ - 时间复杂度:$O(n)$
* 空间复杂度:$O(n)$ - 空间复杂度:$O(n)$
## 总结 ## 总结
@ -136,6 +142,7 @@ public:
### Java: ### Java:
贪心: 贪心:
```java ```java
// 贪心思路 // 贪心思路
class Solution { class Solution {
@ -150,6 +157,7 @@ class Solution {
``` ```
动态规划: 动态规划:
```java ```java
class Solution { // 动态规划 class Solution { // 动态规划
public int maxProfit(int[] prices) { public int maxProfit(int[] prices) {
@ -172,7 +180,9 @@ class Solution { // 动态规划
``` ```
### Python: ### Python:
贪心: 贪心:
```python ```python
class Solution: class Solution:
def maxProfit(self, prices: List[int]) -> int: def maxProfit(self, prices: List[int]) -> int:
@ -183,6 +193,7 @@ class Solution:
``` ```
动态规划: 动态规划:
```python ```python
class Solution: class Solution:
def maxProfit(self, prices: List[int]) -> int: def maxProfit(self, prices: List[int]) -> int:
@ -199,6 +210,7 @@ class Solution:
### Go: ### Go:
贪心算法 贪心算法
```go ```go
func maxProfit(prices []int) int { func maxProfit(prices []int) int {
var sum int var sum int
@ -211,7 +223,9 @@ func maxProfit(prices []int) int {
return sum return sum
} }
``` ```
动态规划 动态规划
```go ```go
func maxProfit(prices []int) int { func maxProfit(prices []int) int {
dp := make([][]int, len(prices)) dp := make([][]int, len(prices))
@ -238,6 +252,7 @@ func max(a, b int) int {
### Javascript: ### Javascript:
贪心 贪心
```Javascript ```Javascript
var maxProfit = function(prices) { var maxProfit = function(prices) {
let result = 0 let result = 0
@ -249,6 +264,7 @@ var maxProfit = function(prices) {
``` ```
动态规划 动态规划
```javascript ```javascript
const maxProfit = (prices) => { const maxProfit = (prices) => {
let dp = Array.from(Array(prices.length), () => Array(2).fill(0)); let dp = Array.from(Array(prices.length), () => Array(2).fill(0));
@ -274,6 +290,7 @@ const maxProfit = (prices) => {
### TypeScript ### TypeScript
贪心
```typescript ```typescript
function maxProfit(prices: number[]): number { function maxProfit(prices: number[]): number {
let resProfit: number = 0; let resProfit: number = 0;
@ -281,12 +298,28 @@ function maxProfit(prices: number[]): number {
resProfit += Math.max(prices[i] - prices[i - 1], 0); resProfit += Math.max(prices[i] - prices[i - 1], 0);
} }
return resProfit; return resProfit;
}; }
```
动态规划
```typescript
function maxProfit(prices: number[]): number {
const dp = Array(prices.length)
.fill(0)
.map(() => Array(2).fill(0))
dp[0][0] = -prices[0]
for (let i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i])
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i])
}
return dp[prices.length - 1][1]
}
``` ```
### Rust ### Rust
贪心: 贪心:
```Rust ```Rust
impl Solution { impl Solution {
fn max(a: i32, b: i32) -> i32 { fn max(a: i32, b: i32) -> i32 {
@ -303,6 +336,7 @@ impl Solution {
``` ```
动态规划: 动态规划:
```Rust ```Rust
impl Solution { impl Solution {
fn max(a: i32, b: i32) -> i32 { fn max(a: i32, b: i32) -> i32 {
@ -322,7 +356,9 @@ impl Solution {
``` ```
### C: ### C:
贪心: 贪心:
```c ```c
int maxProfit(int* prices, int pricesSize){ int maxProfit(int* prices, int pricesSize){
int result = 0; int result = 0;
@ -338,6 +374,7 @@ int maxProfit(int* prices, int pricesSize){
``` ```
动态规划: 动态规划:
```c ```c
#define max(a, b) (((a) > (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b))
@ -362,6 +399,7 @@ int maxProfit(int* prices, int pricesSize){
### Scala ### Scala
贪心: 贪心:
```scala ```scala
object Solution { object Solution {
def maxProfit(prices: Array[Int]): Int = { def maxProfit(prices: Array[Int]): Int = {

View File

@ -116,7 +116,8 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]);
以输入[1,2,3,4,5]为例 以输入[1,2,3,4,5]为例
![123.买卖股票的最佳时机III](https://img-blog.csdnimg.cn/20201228181724295.png)
![123.买卖股票的最佳时机III](https://code-thinking-1253855093.file.myqcloud.com/pics/20201228181724295-20230310134201291.png)
大家可以看到红色框为最后两次卖出的状态。 大家可以看到红色框为最后两次卖出的状态。

View File

@ -16,7 +16,7 @@
* 转换过程中的中间单词必须是字典 wordList 中的单词。 * 转换过程中的中间单词必须是字典 wordList 中的单词。
* 给你两个单词 beginWord 和 endWord 和一个字典 wordList 找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。 * 给你两个单词 beginWord 和 endWord 和一个字典 wordList 找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
 
示例 1 示例 1
* 输入beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] * 输入beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
@ -134,7 +134,70 @@ public int ladderLength(String beginWord, String endWord, List<String> wordList)
} }
``` ```
```java
// Java 双向BFS
class Solution {
// 判断单词之间是否之差了一个字母
public boolean isValid(String currentWord, String chooseWord) {
int count = 0;
for (int i = 0; i < currentWord.length(); i++)
if (currentWord.charAt(i) != chooseWord.charAt(i)) ++count;
return count == 1;
}
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (!wordList.contains(endWord)) return 0; // 如果 endWord 不在 wordList 中,那么无法成功转换,返回 0
// ansLeft 记录从 beginWord 开始 BFS 时能组成的单词数目
// ansRight 记录从 endWord 开始 BFS 时能组成的单词数目
int ansLeft = 0, ansRight = 0;
// queueLeft 表示从 beginWord 开始 BFS 时使用的队列
// queueRight 表示从 endWord 开始 BFS 时使用的队列
Queue<String> queueLeft = new ArrayDeque<>(), queueRight = new ArrayDeque<>();
queueLeft.add(beginWord);
queueRight.add(endWord);
// 从 beginWord 开始 BFS 时把遍历到的节点存入 hashSetLeft 中
// 从 endWord 开始 BFS 时把遍历到的节点存入 hashSetRight 中
Set<String> hashSetLeft = new HashSet<>(), hashSetRight = new HashSet<>();
hashSetLeft.add(beginWord);
hashSetRight.add(endWord);
// 只要有一个队列为空,说明 beginWord 无法转换到 endWord
while (!queueLeft.isEmpty() && !queueRight.isEmpty()) {
++ansLeft;
int size = queueLeft.size();
for (int i = 0; i < size; i++) {
String currentWord = queueLeft.poll();
// 只要 hashSetRight 中存在 currentWord说明从 currentWord 可以转换到 endWord
if (hashSetRight.contains(currentWord)) return ansRight + ansLeft;
for (String chooseWord : wordList) {
if (hashSetLeft.contains(chooseWord) || !isValid(currentWord, chooseWord)) continue;
hashSetLeft.add(chooseWord);
queueLeft.add(chooseWord);
}
}
++ansRight;
size = queueRight.size();
for (int i = 0; i < size; i++) {
String currentWord = queueRight.poll();
// 只要 hashSetLeft 中存在 currentWord说明从 currentWord 可以转换到 beginWord
if (hashSetLeft.contains(currentWord)) return ansLeft + ansRight;
for (String chooseWord : wordList) {
if (hashSetRight.contains(chooseWord) || !isValid(currentWord, chooseWord)) continue;
hashSetRight.add(chooseWord);
queueRight.add(chooseWord);
}
}
}
return 0;
}
}
```
## Python ## Python
``` ```
class Solution: class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
@ -301,3 +364,4 @@ function diffonechar(word1: string, word2: string): boolean {
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a> </a>

View File

@ -84,7 +84,221 @@ public:
## 其他语言版本 ## 其他语言版本
### Java
```Java
// 广度优先遍历
// 使用 visited 数组进行标记
class Solution {
private static final int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 四个方向
public void solve(char[][] board) {
// rowSize行的长度colSize列的长度
int rowSize = board.length, colSize = board[0].length;
boolean[][] visited = new boolean[rowSize][colSize];
Queue<int[]> queue = new ArrayDeque<>();
// 从左侧边,和右侧边遍历
for (int row = 0; row < rowSize; row++) {
if (board[row][0] == 'O') {
visited[row][0] = true;
queue.add(new int[]{row, 0});
}
if (board[row][colSize - 1] == 'O') {
visited[row][colSize - 1] = true;
queue.add(new int[]{row, colSize - 1});
}
}
// 从上边和下边遍历,在对左侧边和右侧边遍历时我们已经遍历了矩阵的四个角
// 所以在遍历上边和下边时可以不用遍历四个角
for (int col = 1; col < colSize - 1; col++) {
if (board[0][col] == 'O') {
visited[0][col] = true;
queue.add(new int[]{0, col});
}
if (board[rowSize - 1][col] == 'O') {
visited[rowSize - 1][col] = true;
queue.add(new int[]{rowSize - 1, col});
}
}
// 广度优先遍历,把没有被 'X' 包围的 'O' 进行标记
while (!queue.isEmpty()) {
int[] current = queue.poll();
for (int[] pos: position) {
int row = current[0] + pos[0], col = current[1] + pos[1];
// 如果范围越界、位置已被访问过、该位置的值不是 'O',就直接跳过
if (row < 0 || row >= rowSize || col < 0 || col >= colSize) continue;
if (visited[row][col] || board[row][col] != 'O') continue;
visited[row][col] = true;
queue.add(new int[]{row, col});
}
}
// 遍历数组,把没有被标记的 'O' 修改成 'X'
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < colSize; col++) {
if (board[row][col] == 'O' && !visited[row][col]) board[row][col] = 'X';
}
}
}
}
```
```Java
// 广度优先遍历
// 直接修改 board 的值为其他特殊值
class Solution {
private static final int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 四个方向
public void solve(char[][] board) {
// rowSize行的长度colSize列的长度
int rowSize = board.length, colSize = board[0].length;
Queue<int[]> queue = new ArrayDeque<>();
// 从左侧边,和右侧边遍历
for (int row = 0; row < rowSize; row++) {
if (board[row][0] == 'O')
queue.add(new int[]{row, 0});
if (board[row][colSize - 1] == 'O')
queue.add(new int[]{row, colSize - 1});
}
// 从上边和下边遍历,在对左侧边和右侧边遍历时我们已经遍历了矩阵的四个角
// 所以在遍历上边和下边时可以不用遍历四个角
for (int col = 1; col < colSize - 1; col++) {
if (board[0][col] == 'O')
queue.add(new int[]{0, col});
if (board[rowSize - 1][col] == 'O')
queue.add(new int[]{rowSize - 1, col});
}
// 广度优先遍历,把没有被 'X' 包围的 'O' 修改成特殊值
while (!queue.isEmpty()) {
int[] current = queue.poll();
board[current[0]][current[1]] = 'A';
for (int[] pos: position) {
int row = current[0] + pos[0], col = current[1] + pos[1];
// 如果范围越界、该位置的值不是 'O',就直接跳过
if (row < 0 || row >= rowSize || col < 0 || col >= colSize) continue;
if (board[row][col] != 'O') continue;
queue.add(new int[]{row, col});
}
}
// 遍历数组,把 'O' 修改成 'X',特殊值修改成 'O'
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < colSize; col++) {
if (board[row][col] == 'A') board[row][col] = 'O';
else if (board[row][col] == 'O') board[row][col] = 'X';
}
}
}
}
```
```Java
// 深度优先遍历
// 使用 visited 数组进行标记
class Solution {
private static final int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 四个方向
public void dfs(char[][] board, int row, int col, boolean[][] visited) {
for (int[] pos: position) {
int nextRow = row + pos[0], nextCol = col + pos[1];
// 位置越界
if (nextRow < 0 || nextRow >= board.length || nextCol < 0 || nextCol >= board[0].length)
continue;
// 位置已被访问过、新位置值不是 'O'
if (visited[nextRow][nextCol] || board[nextRow][nextCol] != 'O') continue;
visited[nextRow][nextCol] = true;
dfs(board, nextRow, nextCol, visited);
}
}
public void solve(char[][] board) {
int rowSize = board.length, colSize = board[0].length;
boolean[][] visited = new boolean[rowSize][colSize];
// 从左侧遍、右侧遍遍历
for (int row = 0; row < rowSize; row++) {
if (board[row][0] == 'O' && !visited[row][0]) {
visited[row][0] = true;
dfs(board, row, 0, visited);
}
if (board[row][colSize - 1] == 'O' && !visited[row][colSize - 1]) {
visited[row][colSize - 1] = true;
dfs(board, row, colSize - 1, visited);
}
}
// 从上边和下边遍历,在对左侧边和右侧边遍历时我们已经遍历了矩阵的四个角
// 所以在遍历上边和下边时可以不用遍历四个角
for (int col = 1; col < colSize - 1; col++) {
if (board[0][col] == 'O' && !visited[0][col]) {
visited[0][col] = true;
dfs(board, 0, col, visited);
}
if (board[rowSize - 1][col] == 'O' && !visited[rowSize - 1][col]) {
visited[rowSize - 1][col] = true;
dfs(board, rowSize - 1, col, visited);
}
}
// 遍历数组,把没有被标记的 'O' 修改成 'X'
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < colSize; col++) {
if (board[row][col] == 'O' && !visited[row][col]) board[row][col] = 'X';
}
}
}
}
```
```Java
// 深度优先遍历
// // 直接修改 board 的值为其他特殊值
class Solution {
private static final int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; // 四个方向
public void dfs(char[][] board, int row, int col) {
for (int[] pos: position) {
int nextRow = row + pos[0], nextCol = col + pos[1];
// 位置越界
if (nextRow < 0 || nextRow >= board.length || nextCol < 0 || nextCol >= board[0].length)
continue;
// 新位置值不是 'O'
if (board[nextRow][nextCol] != 'O') continue;
board[nextRow][nextCol] = 'A'; // 修改为特殊值
dfs(board, nextRow, nextCol);
}
}
public void solve(char[][] board) {
int rowSize = board.length, colSize = board[0].length;
// 从左侧遍、右侧遍遍历
for (int row = 0; row < rowSize; row++) {
if (board[row][0] == 'O') {
board[row][0] = 'A';
dfs(board, row, 0);
}
if (board[row][colSize - 1] == 'O') {
board[row][colSize - 1] = 'A';
dfs(board, row, colSize - 1);
}
}
// 从上边和下边遍历,在对左侧边和右侧边遍历时我们已经遍历了矩阵的四个角
// 所以在遍历上边和下边时可以不用遍历四个角
for (int col = 1; col < colSize - 1; col++) {
if (board[0][col] == 'O') {
board[0][col] = 'A';
dfs(board, 0, col);
}
if (board[rowSize - 1][col] == 'O') {
board[rowSize - 1][col] = 'A';
dfs(board, rowSize - 1, col);
}
}
// 遍历数组,把 'O' 修改成 'X',特殊值修改成 'O'
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < colSize; col++) {
if (board[row][col] == 'O') board[row][col] = 'X';
else if (board[row][col] == 'A') board[row][col] = 'O';
}
}
}
}
```
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a> </a>

View File

@ -163,7 +163,7 @@ for (int i = s.size() - 1; i >= 0; i--) {
以输入:"aabc" 为例: 以输入:"aabc" 为例:
![132.分割回文串II](https://img-blog.csdnimg.cn/20210124182218844.jpg) ![132.分割回文串II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210124182218844.jpg)
以上分析完毕,代码如下: 以上分析完毕,代码如下:

View File

@ -45,6 +45,10 @@
* 解释: * 解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法得这么加油才能跑完全程LeetCode 134.加油站](https://www.bilibili.com/video/BV1jA411r7WX),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 暴力方法 ## 暴力方法

View File

@ -28,6 +28,10 @@
* 输出: 4 * 输出: 4
* 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。 * 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法两者兼顾很容易顾此失彼LeetCode135.分发糖果](https://www.bilibili.com/video/BV1ev4y1r7wN),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
@ -53,7 +57,8 @@ for (int i = 1; i < ratings.size(); i++) {
如图: 如图:
![135.分发糖果](https://img-blog.csdnimg.cn/20201117114916878.png)
![135.分发糖果](https://code-thinking-1253855093.file.myqcloud.com/pics/20201117114916878.png)
再确定左孩子大于右孩子的情况(从后向前遍历) 再确定左孩子大于右孩子的情况(从后向前遍历)
@ -78,7 +83,8 @@ for (int i = 1; i < ratings.size(); i++) {
如图: 如图:
![135.分发糖果1](https://img-blog.csdnimg.cn/20201117115658791.png)
![135.分发糖果1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201117115658791.png)
所以该过程代码如下: 所以该过程代码如下:
@ -115,6 +121,11 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 总结 ## 总结
这在leetcode上是一道困难的题目其难点就在于贪心的策略如果在考虑局部的时候想两边兼顾就会顾此失彼。 这在leetcode上是一道困难的题目其难点就在于贪心的策略如果在考虑局部的时候想两边兼顾就会顾此失彼。

View File

@ -181,7 +181,8 @@ dp[0]表示如果字符串为空的话,说明出现在字典里。
以输入: s = "leetcode", wordDict = ["leet", "code"]为例dp状态如图 以输入: s = "leetcode", wordDict = ["leet", "code"]为例dp状态如图
![139.单词拆分](https://img-blog.csdnimg.cn/20210202162652727.jpg)
![139.单词拆分](https://code-thinking-1253855093.file.myqcloud.com/pics/20210202162652727.jpg)
dp[s.size()]就是最终结果。 dp[s.size()]就是最终结果。

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -6,6 +7,7 @@
> 找到有没有环已经很不容易了,还要让我找到环的入口? > 找到有没有环已经很不容易了,还要让我找到环的入口?
@ -14,13 +16,13 @@
[力扣题目链接](https://leetcode.cn/problems/linked-list-cycle-ii/) [力扣题目链接](https://leetcode.cn/problems/linked-list-cycle-ii/)
题意: 题意:
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1则在该链表中没有环。 为了表示给定链表中的环,使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1则在该链表中没有环。
**说明**:不允许修改给定的链表。 **说明**:不允许修改给定的链表。
![循环链表](https://img-blog.csdnimg.cn/20200816110112704.png) ![循环链表](https://code-thinking-1253855093.file.myqcloud.com/pics/20200816110112704.png)
## 思路 ## 思路
@ -48,7 +50,7 @@
会发现最终都是这种情况, 如下图: 会发现最终都是这种情况, 如下图:
![142环形链表1](https://img-blog.csdnimg.cn/20210318162236720.png) ![142环形链表1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210318162236720.png)
fast和slow各自再走一步 fast和slow就相遇了 fast和slow各自再走一步 fast和slow就相遇了
@ -143,26 +145,29 @@ public:
}; };
``` ```
* 时间复杂度: O(n)快慢指针相遇前指针走的次数小于链表长度快慢指针相遇后两个index指针走的次数也小于链表长度总体为走的次数小于 2n
* 空间复杂度: O(1)
## 补充 ## 补充
在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?** 在推理过程中,大家可能有一个疑问就是:**为什么第一次在环中相遇slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?**
即文章[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中如下的地方: 即文章[链表:环找到了,那入口呢?](https://programmercarl.com/0142.环形链表II.html)中如下的地方:
![142环形链表5](https://img-blog.csdnimg.cn/20210318165123581.png) ![142环形链表5](https://code-thinking-1253855093.file.myqcloud.com/pics/20210318165123581.png)
首先slow进环的时候fast一定是先进环来了。 首先slow进环的时候fast一定是先进环来了。
如果slow进环入口fast也在环入口那么把这个环展开成直线就是如下图的样子 如果slow进环入口fast也在环入口那么把这个环展开成直线就是如下图的样子
![142环形链表3](https://img-blog.csdnimg.cn/2021031816503266.png) ![142环形链表3](https://code-thinking-1253855093.file.myqcloud.com/pics/2021031816503266.png)
可以看出如果slow 和 fast同时在环入口开始走一定会在环入口3相遇slow走了一圈fast走了两圈。 可以看出如果slow 和 fast同时在环入口开始走一定会在环入口3相遇slow走了一圈fast走了两圈。
重点来了slow进环的时候fast一定是在环的任意一个位置如图 重点来了slow进环的时候fast一定是在环的任意一个位置如图
![142环形链表4](https://img-blog.csdnimg.cn/2021031816515727.png) ![142环形链表4](https://code-thinking-1253855093.file.myqcloud.com/pics/2021031816515727.png)
那么fast指针走到环入口3的时候已经走了k + n 个节点slow相应的应该走了(k + n) / 2 个节点。 那么fast指针走到环入口3的时候已经走了k + n 个节点slow相应的应该走了(k + n) / 2 个节点。
@ -187,6 +192,7 @@ public:
Java Java
```java ```java
public class Solution { public class Solution {
public ListNode detectCycle(ListNode head) { public ListNode detectCycle(ListNode head) {
@ -235,6 +241,7 @@ class Solution:
``` ```
Go Go
```go ```go
func detectCycle(head *ListNode) *ListNode { func detectCycle(head *ListNode) *ListNode {
slow, fast := head, head slow, fast := head, head
@ -374,6 +381,7 @@ ListNode *detectCycle(ListNode *head) {
``` ```
Scala: Scala:
```scala ```scala
object Solution { object Solution {
def detectCycle(head: ListNode): ListNode = { def detectCycle(head: ListNode): ListNode = {

View File

@ -113,6 +113,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 题外话 ## 题外话

View File

@ -114,6 +114,7 @@ void removeExtraSpaces(string& s) {
} }
``` ```
有的同学可能发现用erase来移除空格在leetcode上性能也还行。主要是以下几点 有的同学可能发现用erase来移除空格在leetcode上性能也还行。主要是以下几点
1. leetcode上的测试集里字符串的长度不够长如果足够长性能差距会非常明显。 1. leetcode上的测试集里字符串的长度不够长如果足够长性能差距会非常明显。
@ -197,6 +198,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1) 或 O(n),取决于语言中字符串是否可变
## 其他语言版本 ## 其他语言版本
@ -516,6 +520,48 @@ class Solution:
return s[:ps] + s[ps:][::-1] # Must do the last step, because the last word is omit though the pointers are on the correct positions, return s[:ps] + s[ps:][::-1] # Must do the last step, because the last word is omit though the pointers are on the correct positions,
``` ```
```python
class Solution: # 使用双指针法移除空格
def reverseWords(self, s: str) -> str:
def removeextraspace(s):
start = 0; end = len(s)-1
while s[start]==' ':
start+=1
while s[end]==' ':
end-=1
news = list(s[start:end+1])
slow = fast = 0
while fast<len(news):
while fast>0 and news[fast]==news[fast-1]==' ':
fast+=1
news[slow]=news[fast]
slow+=1; fast+=1
#return "".join(news[:slow])
return news[:slow]
def reversestr(s):
left,right = 0,len(s)-1
news = list(s)
while left<right:
news[left],news[right] = news[right],news[left]
left+=1; right-=1
#return "".join(news)
return news
news = removeextraspace(s)
news.append(' ')
fast=slow=0
#print(news)
while fast<len(news):
while news[fast]!=' ':
fast+=1
news[slow:fast] = reversestr(news[slow:fast])
# print(news[slow:fast])
fast=slow=fast+1
news2 = reversestr(news[:-1])
return ''.join(news2)
```
Go Go
```go ```go
@ -924,6 +970,49 @@ pub fn remove_extra_spaces(s: &mut Vec<char>) {
} }
} }
``` ```
C:
```C
// 翻转字符串中指定范围的字符
void reverse(char* s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
// 删除字符串两端和中间多余的空格
void removeExtraSpace(char* s) {
int start = 0; // 指向字符串开头的指针
int end = strlen(s) - 1; // 指向字符串结尾的指针
while (s[start] == ' ') start++; // 移动指针 start直到找到第一个非空格字符
while (s[end] == ' ') end--; // 移动指针 end直到找到第一个非空格字符
int slow = 0; // 指向新字符串的下一个写入位置的指针
for (int i = start; i <= end; i++) { // 遍历整个字符串
if (s[i] == ' ' && s[i+1] == ' ') { // 如果当前字符是空格,并且下一个字符也是空格,则跳过
continue;
}
s[slow] = s[i]; // 否则,将当前字符复制到新字符串的 slow 位置
slow++; // 将 slow 指针向后移动
}
s[slow] = '\0'; // 在新字符串的末尾添加一个空字符
}
// 翻转字符串中的单词
char * reverseWords(char * s){
removeExtraSpace(s); // 先删除字符串两端和中间的多余空格
reverse(s, 0, strlen(s) - 1); // 翻转整个字符串
int slow = 0; // 指向每个单词的开头位置的指针
for (int i = 0; i <= strlen(s); i++) { // 遍历整个字符串
if (s[i] ==' ' || s[i] == '\0') { // 如果当前字符是空格或空字符,说明一个单词结束了
reverse(s, slow, i-1); // 翻转单词
slow = i + 1; // 将 slow 指针指向下一个单词的开头位置
}
}
return s; // 返回处理后的字符串
}
```
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">

View File

@ -129,7 +129,7 @@ for (int j = 1; j < 2 * k; j += 2) {
以输入[1,2,3,4,5]k=2为例。 以输入[1,2,3,4,5]k=2为例。
![188.买卖股票的最佳时机IV](https://img-blog.csdnimg.cn/20201229100358221.png) ![188.买卖股票的最佳时机IV](https://code-thinking-1253855093.file.myqcloud.com/pics/20201229100358221.png)
最后一次卖出一定是利润最大的dp[prices.size() - 1][2 * k]即红色部分就是最后求解。 最后一次卖出一定是利润最大的dp[prices.size() - 1][2 * k]即红色部分就是最后求解。
@ -156,6 +156,11 @@ public:
}; };
``` ```
* 时间复杂度: O(n * k),其中 n 为 prices 的长度
* 空间复杂度: O(n * k)
当然有的解法是定义一个三维数组dp[i][j][k]第i天第j次买卖k表示买还是卖的状态从定义上来讲是比较直观。 当然有的解法是定义一个三维数组dp[i][j][k]第i天第j次买卖k表示买还是卖的状态从定义上来讲是比较直观。
但感觉三维数组操作起来有些麻烦,我是直接用二维数组来模拟三维数组的情况,代码看起来也清爽一些。 但感觉三维数组操作起来有些麻烦,我是直接用二维数组来模拟三维数组的情况,代码看起来也清爽一些。

View File

@ -85,7 +85,7 @@ for (int i = 2; i < nums.size(); i++) {
以示例二,输入[2,7,9,3,1]为例。 以示例二,输入[2,7,9,3,1]为例。
![198.打家劫舍](https://img-blog.csdnimg.cn/20210221170954115.jpg) ![198.打家劫舍](https://code-thinking-1253855093.file.myqcloud.com/pics/20210221170954115.jpg)
红框dp[nums.size() - 1]为结果。 红框dp[nums.size() - 1]为结果。
@ -108,6 +108,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 总结 ## 总结
打家劫舍是DP解决的经典题目这道题也是打家劫舍入门级题目后面我们还会变种方式来打劫的。 打家劫舍是DP解决的经典题目这道题也是打家劫舍入门级题目后面我们还会变种方式来打劫的。

View File

@ -75,6 +75,8 @@ public:
}; };
``` ```
* 时间复杂度: O(logn)
* 空间复杂度: O(logn)
@ -132,6 +134,19 @@ class Solution:
else: else:
record.add(n) record.add(n)
# python的另一种写法 - 通过字符串来计算各位平方和
class Solution:
def isHappy(self, n: int) -> bool:
record = []
while n not in record:
record.append(n)
newn = 0
nn = str(n)
for i in nn:
newn+=int(i)**2
if newn==1: return True
n = newn
return False
``` ```
Go Go

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -5,6 +6,7 @@
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 链表操作中,可以使用原链表来直接进行删除操作,也可以设置一个虚拟头结点再进行删除操作,接下来看一看哪种方式更方便。 > 链表操作中,可以使用原链表来直接进行删除操作,也可以设置一个虚拟头结点再进行删除操作,接下来看一看哪种方式更方便。
# 203.移除链表元素 # 203.移除链表元素
@ -32,11 +34,11 @@
这里以链表 1 4 2 4 来举例移除元素4。 这里以链表 1 4 2 4 来举例移除元素4。
![203_链表删除元素1](https://img-blog.csdnimg.cn/20210316095351161.png) ![203_链表删除元素1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095351161.png)
如果使用CC++编程语言的话,不要忘了还要从内存中删除这两个移除的节点, 清理节点内存之后如图: 如果使用CC++编程语言的话,不要忘了还要从内存中删除这两个移除的节点, 清理节点内存之后如图:
![203_链表删除元素2](https://img-blog.csdnimg.cn/20210316095418280.png) ![203_链表删除元素2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095418280.png)
**当然如果使用java python的话就不用手动管理内存了。** **当然如果使用java python的话就不用手动管理内存了。**
@ -47,23 +49,23 @@
那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢? 那么因为单链表的特殊性,只能指向下一个节点,刚刚删除的是链表的中第二个,和第四个节点,那么如果删除的是头结点又该怎么办呢?
这里就涉及如下链表操作的两种方式: 这里就涉及如下链表操作的两种方式:
* **直接使用原来的链表来进行删除操作。** * **直接使用原来的链表来进行删除操作。**
* **设置一个虚拟头结点在进行删除操作。** * **设置一个虚拟头结点在进行删除操作。**
来看第一种操作:直接使用原来的链表来进行移除。 来看第一种操作:直接使用原来的链表来进行移除。
![203_链表删除元素3](https://img-blog.csdnimg.cn/2021031609544922.png) ![203_链表删除元素3](https://code-thinking-1253855093.file.myqcloud.com/pics/2021031609544922.png)
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。 移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。
所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。 所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。
![203_链表删除元素4](https://img-blog.csdnimg.cn/20210316095512470.png) ![203_链表删除元素4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095512470.png)
依然别忘将原头结点从内存中删掉。 依然别忘将原头结点从内存中删掉。
![203_链表删除元素5](https://img-blog.csdnimg.cn/20210316095543775.png) ![203_链表删除元素5](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095543775.png)
这样移除了一个头结点,是不是发现,在单链表中移除头结点 和 移除其他节点的操作方式是不一样,其实在写代码的时候也会发现,需要单独写一段逻辑来处理移除头结点的情况。 这样移除了一个头结点,是不是发现,在单链表中移除头结点 和 移除其他节点的操作方式是不一样,其实在写代码的时候也会发现,需要单独写一段逻辑来处理移除头结点的情况。
@ -74,7 +76,7 @@
来看看如何设置一个虚拟头。依然还是在这个链表中移除元素1。 来看看如何设置一个虚拟头。依然还是在这个链表中移除元素1。
![203_链表删除元素6](https://img-blog.csdnimg.cn/20210316095619221.png) ![203_链表删除元素6](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095619221.png)
这里来给链表添加一个虚拟头结点为新的头结点此时要移除这个旧头结点元素1。 这里来给链表添加一个虚拟头结点为新的头结点此时要移除这个旧头结点元素1。
@ -116,6 +118,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
**设置一个虚拟头结点在进行移除节点操作:** **设置一个虚拟头结点在进行移除节点操作:**
```CPP ```CPP
@ -142,12 +147,17 @@ public:
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 其他语言版本 ## 其他语言版本
C: C:
用原来的链表操作: 用原来的链表操作:
```c ```c
struct ListNode* removeElements(struct ListNode* head, int val){ struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode* temp; struct ListNode* temp;
@ -178,7 +188,9 @@ struct ListNode* removeElements(struct ListNode* head, int val){
return head; return head;
} }
``` ```
设置一个虚拟头结点: 设置一个虚拟头结点:
```c ```c
/** /**
* Definition for singly-linked list. * Definition for singly-linked list.
@ -212,6 +224,7 @@ struct ListNode* removeElements(struct ListNode* head, int val){
``` ```
Java Java
```java ```java
/** /**
* 添加虚节点方式 * 添加虚节点方式
@ -292,6 +305,7 @@ public ListNode removeElements(ListNode head, int val) {
``` ```
Python Python
```python ```python
# Definition for singly-linked list. # Definition for singly-linked list.
# class ListNode: # class ListNode:
@ -302,8 +316,8 @@ class Solution:
def removeElements(self, head: ListNode, val: int) -> ListNode: def removeElements(self, head: ListNode, val: int) -> ListNode:
dummy_head = ListNode(next=head) #添加一个虚拟节点 dummy_head = ListNode(next=head) #添加一个虚拟节点
cur = dummy_head cur = dummy_head
while(cur.next!=None): while cur.next:
if(cur.next.val == val): if cur.next.val == val:
cur.next = cur.next.next #删除cur.next节点 cur.next = cur.next.next #删除cur.next节点
else: else:
cur = cur.next cur = cur.next
@ -442,6 +456,7 @@ func removeElements(_ head: ListNode?, _ val: Int) -> ListNode? {
``` ```
PHP: PHP:
```php ```php
/** /**
* Definition for singly-linked list. * Definition for singly-linked list.
@ -469,6 +484,7 @@ func removeElements(head *ListNode, val int) *ListNode {
``` ```
RUST: RUST:
```rust ```rust
// Definition for singly-linked list. // Definition for singly-linked list.
// #[derive(PartialEq, Eq, Clone, Debug)] // #[derive(PartialEq, Eq, Clone, Debug)]
@ -504,7 +520,9 @@ impl Solution {
} }
} }
``` ```
Scala: Scala:
```scala ```scala
/** /**
* Definition for singly-linked list. * Definition for singly-linked list.
@ -535,7 +553,9 @@ object Solution {
} }
} }
``` ```
Kotlin: Kotlin:
```kotlin ```kotlin
/** /**
* Example: * Example:
@ -569,6 +589,7 @@ class Solution {
} }
} }
``` ```
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>

View File

@ -25,7 +25,8 @@
其实只需要改变链表的next指针的指向直接将链表反转 ,而不用重新定义一个新的链表,如图所示: 其实只需要改变链表的next指针的指向直接将链表反转 ,而不用重新定义一个新的链表,如图所示:
![206_反转链表](https://img-blog.csdnimg.cn/20210218090901207.png)
![206_反转链表](https://code-thinking-1253855093.file.myqcloud.com/pics/20210218090901207.png)
之前链表的头节点是元素1 反转之后头结点就是元素5 这里并没有添加或者删除节点仅仅是改变next指针的方向。 之前链表的头节点是元素1 反转之后头结点就是元素5 这里并没有添加或者删除节点仅仅是改变next指针的方向。
@ -67,6 +68,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 递归法 ## 递归法
递归法相对抽象一些但是其实和双指针法是一样的逻辑同样是当cur为空的时候循环结束不断将cur指向pre的过程。 递归法相对抽象一些但是其实和双指针法是一样的逻辑同样是当cur为空的时候循环结束不断将cur指向pre的过程。
@ -96,6 +100,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n), 要递归处理链表的每个节点
* 空间复杂度: O(n), 递归调用了 n 层栈空间
我们可以发现,上面的递归写法和双指针法实质上都是从前往后翻转指针指向,其实还有另外一种与双指针法不同思路的递归写法:从后往前翻转指针指向。 我们可以发现,上面的递归写法和双指针法实质上都是从前往后翻转指针指向,其实还有另外一种与双指针法不同思路的递归写法:从后往前翻转指针指向。
具体代码如下(带详细注释): 具体代码如下(带详细注释):
@ -119,6 +126,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 其他语言版本 ## 其他语言版本

View File

@ -103,7 +103,7 @@ public:
解题的关键在于 窗口的起始位置如何移动,如图所示: 解题的关键在于 窗口的起始位置如何移动,如图所示:
![leetcode_209](https://img-blog.csdnimg.cn/20210312160441942.png) ![leetcode_209](https://code-thinking-1253855093.file.myqcloud.com/pics/20210312160441942.png)
可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。** 可以发现**滑动窗口的精妙之处在于根据当前子序列和大小的情况不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。**

View File

@ -39,15 +39,15 @@
* 情况一:考虑不包含首尾元素 * 情况一:考虑不包含首尾元素
![213.打家劫舍II](https://img-blog.csdnimg.cn/20210129160748643.jpg) ![213.打家劫舍II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129160748643-20230310134000692.jpg)
* 情况二:考虑包含首元素,不包含尾元素 * 情况二:考虑包含首元素,不包含尾元素
![213.打家劫舍II1](https://img-blog.csdnimg.cn/20210129160821374.jpg) ![213.打家劫舍II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129160821374-20230310134003961.jpg)
* 情况三:考虑包含尾元素,不包含首元素 * 情况三:考虑包含尾元素,不包含首元素
![213.打家劫舍II2](https://img-blog.csdnimg.cn/20210129160842491.jpg) ![213.打家劫舍II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129160842491-20230310134008133.jpg)
**注意我这里用的是"考虑"**,例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 对于情况三取nums[1] 和 nums[3]就是最大的。 **注意我这里用的是"考虑"**,例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素! 对于情况三取nums[1] 和 nums[3]就是最大的。
@ -82,6 +82,11 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 总结 ## 总结
成环之后还是难了一些的, 不少题解没有把“考虑房间”和“偷房间”说清楚。 成环之后还是难了一些的, 不少题解没有把“考虑房间”和“偷房间”说清楚。

View File

@ -1,3 +1,4 @@
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/xunlianying.html" target="_blank"> <a href="https://programmercarl.com/other/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/> <img src="../pics/训练营.png" width="1000"/>
@ -7,17 +8,19 @@
> 别看本篇选的是组合总和III而不是组合总和本题和上一篇77.组合相比难度刚刚好! > 别看本篇选的是组合总和III而不是组合总和本题和上一篇77.组合相比难度刚刚好!
# 216.组合总和III # 216.组合总和III
[力扣题目链接](https://leetcode.cn/problems/combination-sum-iii/) [力扣题目链接](https://leetcode.cn/problems/combination-sum-iii/)
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明: 说明:
* 所有数字都是正整数。 * 所有数字都是正整数。
* 解集不能包含重复的组合。  * 解集不能包含重复的组合。
示例 1: 示例 1:
输入: k = 3, n = 7 输入: k = 3, n = 7
@ -46,7 +49,7 @@
选取过程如图: 选取过程如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) ![216.组合总和III](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195717975.png)
图中可以看出只有最后取到集合13和为4 符合条件。 图中可以看出只有最后取到集合13和为4 符合条件。
@ -80,6 +83,7 @@ vector<vector<int>> result;
vector<int> path; vector<int> path;
void backtracking(int targetSum, int k, int sum, int startIndex) void backtracking(int targetSum, int k, int sum, int startIndex)
``` ```
其实这里sum这个参数也可以省略每次targetSum减去选取的元素数值然后判断如果targetSum为0了说明收集到符合条件的结果了我这里为了直观便于理解还是加一个sum参数。 其实这里sum这个参数也可以省略每次targetSum减去选取的元素数值然后判断如果targetSum为0了说明收集到符合条件的结果了我这里为了直观便于理解还是加一个sum参数。
还要强调一下,回溯法中递归函数参数很难一次性确定下来,一般先写逻辑,需要啥参数了,填什么参数。 还要强调一下,回溯法中递归函数参数很难一次性确定下来,一般先写逻辑,需要啥参数了,填什么参数。
@ -108,7 +112,7 @@ if (path.size() == k) {
本题和[77. 组合](https://programmercarl.com/0077.组合.html)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9 本题和[77. 组合](https://programmercarl.com/0077.组合.html)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9
如图: 如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) ![216.组合总和III](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195717975-20230310113546003.png)
处理过程就是 path收集每次选取的元素相当于树型结构里的边sum来统计path里元素的总和。 处理过程就是 path收集每次选取的元素相当于树型结构里的边sum来统计path里元素的总和。
@ -166,7 +170,7 @@ public:
这道题目,剪枝操作其实是很容易想到了,想必大家看上面的树形图的时候已经想到了。 这道题目,剪枝操作其实是很容易想到了,想必大家看上面的树形图的时候已经想到了。
如图: 如图:
![216.组合总和III1](https://img-blog.csdnimg.cn/2020112319580476.png) ![216.组合总和III1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020112319580476.png)
已选元素总和如果已经大于n图中数值为4那么往后遍历就没有意义了直接剪掉。 已选元素总和如果已经大于n图中数值为4那么往后遍历就没有意义了直接剪掉。
@ -181,7 +185,6 @@ if (sum > targetSum) { // 剪枝操作
当然这个剪枝也可以放在 调用递归之前,即放在这里,只不过要记得 要回溯操作给做了。 当然这个剪枝也可以放在 调用递归之前,即放在这里,只不过要记得 要回溯操作给做了。
```CPP ```CPP
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝 for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝
sum += i; // 处理 sum += i; // 处理
path.push_back(i); // 处理 path.push_back(i); // 处理
@ -250,6 +253,7 @@ public:
## Java ## Java
模板方法 模板方法
```java ```java
class Solution { class Solution {
List<List<Integer>> result = new ArrayList<>(); List<List<Integer>> result = new ArrayList<>();
@ -317,6 +321,7 @@ class Solution {
``` ```
其他方法 其他方法
```java ```java
class Solution { class Solution {
List<List<Integer>> res = new ArrayList<>(); List<List<Integer>> res = new ArrayList<>();

View File

@ -152,7 +152,7 @@ public:
我来举一个典型的例子如题: 我来举一个典型的例子如题:
<img src='https://img-blog.csdnimg.cn/20200920221638903.png' width=600> </img></div> <img src='https://code-thinking-1253855093.file.myqcloud.com/pics/20200920221638903-20230310123444151.png' width=600> </img>
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
@ -161,10 +161,10 @@ public:
对于情况二分别递归左孩子和右孩子递归到某一深度一定会有左孩子或者右孩子为满二叉树然后依然可以按照情况1来计算。 对于情况二分别递归左孩子和右孩子递归到某一深度一定会有左孩子或者右孩子为满二叉树然后依然可以按照情况1来计算。
完全二叉树(一)如图: 完全二叉树(一)如图:
![222.完全二叉树的节点个数](https://img-blog.csdnimg.cn/20201124092543662.png) ![222.完全二叉树的节点个数](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124092543662.png)
完全二叉树(二)如图: 完全二叉树(二)如图:
![222.完全二叉树的节点个数1](https://img-blog.csdnimg.cn/20201124092634138.png) ![222.完全二叉树的节点个数1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124092634138.png)
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。 可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
@ -379,6 +379,20 @@ class Solution:
return (2 << leftDepth) - 1 #注意(2<<1) 相当于2^2所以leftDepth初始为0 return (2 << leftDepth) - 1 #注意(2<<1) 相当于2^2所以leftDepth初始为0
return self.countNodes(root.left) + self.countNodes(root.right) + 1 return self.countNodes(root.left) + self.countNodes(root.right) + 1
``` ```
完全二叉树写法2
```python
class Solution: # 利用完全二叉树特性
def countNodes(self, root: TreeNode) -> int:
if not root: return 0
count = 1
left = root.left; right = root.right
while left and right:
count+=1
left = left.left; right = right.right
if not left and not right: # 如果同时到底说明是满二叉树,反之则不是
return 2**count-1
return 1+self.countNodes(root.left)+self.countNodes(root.right)
```
## Go ## Go

View File

@ -111,6 +111,8 @@ public:
} }
}; };
``` ```
* 时间复杂度: push为O(n)其他为O(1)
* 空间复杂度: O(n)
# 优化 # 优化
@ -156,6 +158,9 @@ public:
} }
}; };
``` ```
* 时间复杂度: push为O(n)其他为O(1)
* 空间复杂度: O(n)
# 其他语言版本 # 其他语言版本

View File

@ -11,7 +11,8 @@
翻转一棵二叉树。 翻转一棵二叉树。
![226.翻转二叉树](https://img-blog.csdnimg.cn/20210203192644329.png)
![226.翻转二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203192644329.png)
这道题目背后有一个让程序员心酸的故事,听说 Homebrew的作者Max Howell就是因为没在白板上写出翻转二叉树最后被Google拒绝了。真假不做判断权当一个乐子哈 这道题目背后有一个让程序员心酸的故事,听说 Homebrew的作者Max Howell就是因为没在白板上写出翻转二叉树最后被Google拒绝了。真假不做判断权当一个乐子哈
@ -33,7 +34,8 @@
如果要从整个树来看,翻转还真的挺复杂,整个树以中间分割线进行翻转,如图: 如果要从整个树来看,翻转还真的挺复杂,整个树以中间分割线进行翻转,如图:
![226.翻转二叉树1](https://img-blog.csdnimg.cn/20210203192724351.png)
![226.翻转二叉树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203192724351.png)
可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。 可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。

View File

@ -112,6 +112,10 @@ public:
``` ```
* 时间复杂度: push和empty为O(1), pop和peek为O(n)
* 空间复杂度: O(n)
## 拓展 ## 拓展
可以看出peek()的实现直接复用了pop() 要不然对stOut判空的逻辑又要重写一遍。 可以看出peek()的实现直接复用了pop() 要不然对stOut判空的逻辑又要重写一遍。

View File

@ -15,7 +15,8 @@
例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5] 例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5]
![235. 二叉搜索树的最近公共祖先](https://img-blog.csdnimg.cn/20201018172243602.png)
![235. 二叉搜索树的最近公共祖先](https://code-thinking-1253855093.file.myqcloud.com/pics/20201018172243602.png)
示例 1: 示例 1:

View File

@ -17,7 +17,8 @@
例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4] 例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4]
![236. 二叉树的最近公共祖先](https://img-blog.csdnimg.cn/20201016173414722.png)
![236. 二叉树的最近公共祖先](https://code-thinking-1253855093.file.myqcloud.com/pics/20201016173414722.png)
示例 1: 示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1 输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
@ -130,7 +131,7 @@ left与right的逻辑处理; // 中
如图: 如图:
![236.二叉树的最近公共祖先](https://img-blog.csdnimg.cn/2021020415105872.png) ![236.二叉树的最近公共祖先](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020415105872.png)
就像图中一样直接返回7多美滋滋。 就像图中一样直接返回7多美滋滋。
@ -163,7 +164,7 @@ TreeNode* right = lowestCommonAncestor(root->right, p, q);
如图: 如图:
![236.二叉树的最近公共祖先1](https://img-blog.csdnimg.cn/20210204151125844.png) ![236.二叉树的最近公共祖先1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204151125844.png)
图中节点10的左子树返回null右子树返回目标值7那么此时节点10的处理逻辑就是把右子树的返回值最近公共祖先7返回上去 图中节点10的左子树返回null右子树返回目标值7那么此时节点10的处理逻辑就是把右子树的返回值最近公共祖先7返回上去
@ -184,7 +185,7 @@ else { // (left == NULL && right == NULL)
那么寻找最小公共祖先,完整流程图如下: 那么寻找最小公共祖先,完整流程图如下:
![236.二叉树的最近公共祖先2](https://img-blog.csdnimg.cn/202102041512582.png) ![236.二叉树的最近公共祖先2](https://code-thinking-1253855093.file.myqcloud.com/pics/202102041512582.png)
**从图中,大家可以看到,我们是如何回溯遍历整棵二叉树,将结果返回给头结点的!** **从图中,大家可以看到,我们是如何回溯遍历整棵二叉树,将结果返回给头结点的!**

View File

@ -184,6 +184,9 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(k)
再来看一下时间复杂度,使用单调队列的时间复杂度是 O(n)。 再来看一下时间复杂度,使用单调队列的时间复杂度是 O(n)。

View File

@ -85,6 +85,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 其他语言版本 ## 其他语言版本

View File

@ -16,7 +16,7 @@
说明: 叶子节点是指没有子节点的节点。 说明: 叶子节点是指没有子节点的节点。
示例: 示例:
![257.二叉树的所有路径1](https://img-blog.csdnimg.cn/2021020415161576.png) ![257.二叉树的所有路径1](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020415161576.png)
# 思路 # 思路
@ -28,7 +28,7 @@
前序遍历以及回溯的过程如图: 前序遍历以及回溯的过程如图:
![257.二叉树的所有路径](https://img-blog.csdnimg.cn/20210204151702443.png) ![257.二叉树的所有路径](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204151702443.png)
我们先使用递归的方式,来做前序遍历。**要知道递归和回溯就是一家的,本题也需要回溯。** 我们先使用递归的方式,来做前序遍历。**要知道递归和回溯就是一家的,本题也需要回溯。**

View File

@ -94,7 +94,8 @@ for (int i = 0; i <= n; i++) { // 遍历背包
已输入n为5例dp状态图如下 已输入n为5例dp状态图如下
![279.完全平方数](https://img-blog.csdnimg.cn/20210202112617341.jpg)
![279.完全平方数](https://code-thinking-1253855093.file.myqcloud.com/pics/20210202112617341.jpg)
dp[0] = 0 dp[0] = 0
dp[1] = min(dp[0] + 1) = 1 dp[1] = min(dp[0] + 1) = 1
@ -126,6 +127,10 @@ public:
}; };
``` ```
* 时间复杂度: O(n * √n)
* 空间复杂度: O(n)
同样我在给出先遍历物品在遍历背包的代码一样的可以AC的。 同样我在给出先遍历物品在遍历背包的代码一样的可以AC的。
```CPP ```CPP
@ -144,6 +149,8 @@ public:
} }
}; };
``` ```
* 同上
## 总结 ## 总结

View File

@ -82,7 +82,7 @@ for (int i = 1; i < nums.size(); i++) {
输入:[0,1,0,3,2]dp数组的变化如下 输入:[0,1,0,3,2]dp数组的变化如下
![300.最长上升子序列](https://img-blog.csdnimg.cn/20210110170945618.jpg) ![300.最长上升子序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110170945618.jpg)
如果代码写出来但一直AC不了那么就把dp数组打印出来看看对不对 如果代码写出来但一直AC不了那么就把dp数组打印出来看看对不对
@ -106,6 +106,10 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n^2)
* 空间复杂度: O(n)
## 总结 ## 总结

View File

@ -45,7 +45,7 @@ dp[i][j]第i天状态为j所剩的最多现金为dp[i][j]。
* 状态三:今天卖出股票 * 状态三:今天卖出股票
* 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天! * 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天!
![](https://img-blog.csdnimg.cn/518d5baaf33f4b2698064f8efb42edbf.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/518d5baaf33f4b2698064f8efb42edbf.png)
j的状态为 j的状态为
@ -133,7 +133,8 @@ dp[i][3] = dp[i - 1][2];
以 [1,2,3,0,2] 为例dp数组如下 以 [1,2,3,0,2] 为例dp数组如下
![309.最佳买卖股票时机含冷冻期](https://img-blog.csdnimg.cn/2021032317451040.png)
![309.最佳买卖股票时机含冷冻期](https://code-thinking-1253855093.file.myqcloud.com/pics/2021032317451040.png)
最后结果是取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。 最后结果是取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。

View File

@ -106,7 +106,7 @@ dp[0] = 0;
以输入coins = [1, 2, 5], amount = 5为例 以输入coins = [1, 2, 5], amount = 5为例
![322.零钱兑换](https://img-blog.csdnimg.cn/20210201111833906.jpg) ![322.零钱兑换](https://code-thinking-1253855093.file.myqcloud.com/pics/20210201111833906.jpg)
dp[amount]为最终结果。 dp[amount]为最终结果。
@ -133,6 +133,11 @@ public:
}; };
``` ```
* 时间复杂度: O(n * amount),其中 n 为 coins 的长度
* 空间复杂度: O(amount)
对于遍历方式遍历背包放在外循环,遍历物品放在内循环也是可以的,我就直接给出代码了 对于遍历方式遍历背包放在外循环,遍历物品放在内循环也是可以的,我就直接给出代码了
```CPP ```CPP
@ -154,6 +159,8 @@ public:
} }
}; };
``` ```
* 同上
## 总结 ## 总结

View File

@ -57,7 +57,7 @@
对于死循环,我来举一个有重复机场的例子: 对于死循环,我来举一个有重复机场的例子:
![332.重新安排行程](https://img-blog.csdnimg.cn/20201115180537865.png) ![332.重新安排行程](https://code-thinking-1253855093.file.myqcloud.com/pics/20201115180537865.png)
为什么要举这个例子呢,就是告诉大家,出发机场和到达机场也会重复的,**如果在解题的过程中没有对集合元素处理好,就会死循环。** 为什么要举这个例子呢,就是告诉大家,出发机场和到达机场也会重复的,**如果在解题的过程中没有对集合元素处理好,就会死循环。**
@ -111,7 +111,7 @@ void backtracking(参数) {
本题以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下: 本题以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下:
![332.重新安排行程1](https://img-blog.csdnimg.cn/2020111518065555.png) ![332.重新安排行程1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111518065555-20230310121223600.png)
开始回溯三部曲讲解: 开始回溯三部曲讲解:
@ -137,7 +137,7 @@ bool backtracking(int ticketNum, vector<string>& result) {
因为我们只需要找到一个行程,就是在树形结构中唯一的一条通向叶子节点的路线,如图: 因为我们只需要找到一个行程,就是在树形结构中唯一的一条通向叶子节点的路线,如图:
![332.重新安排行程1](https://img-blog.csdnimg.cn/2020111518065555.png) ![332.重新安排行程1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111518065555-20230310121240991.png)
所以找到了这个叶子节点了直接返回,这个递归函数的返回值问题我们在讲解二叉树的系列的时候,在这篇[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)详细介绍过。 所以找到了这个叶子节点了直接返回,这个递归函数的返回值问题我们在讲解二叉树的系列的时候,在这篇[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://programmercarl.com/0112.路径总和.html)详细介绍过。

View File

@ -13,7 +13,8 @@
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。 计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
![337.打家劫舍III](https://img-blog.csdnimg.cn/20210223173849619.png)
![337.打家劫舍III](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223173849619.png)
## 思路 ## 思路

View File

@ -129,7 +129,7 @@ for (int i = 3; i <= n ; i++) {
举例当n为10 的时候dp数组里的数值如下 举例当n为10 的时候dp数组里的数值如下
![343.整数拆分](https://img-blog.csdnimg.cn/20210104173021581.png) ![343.整数拆分](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104173021581.png)
以上动规五部曲分析完毕C++代码如下: 以上动规五部曲分析完毕C++代码如下:

View File

@ -130,6 +130,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)

View File

@ -79,8 +79,6 @@
```CPP ```CPP
// 时间复杂度O(nlogk)
// 空间复杂度O(n)
class Solution { class Solution {
public: public:
// 小顶堆 // 小顶堆
@ -120,6 +118,10 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(nlogk)
* 空间复杂度: O(n)
# 拓展 # 拓展
大家对这个比较运算在建堆时是如何应用的,为什么左大于右就会建立小顶堆,反而建立大顶堆比较困惑。 大家对这个比较运算在建堆时是如何应用的,为什么左大于右就会建立小顶堆,反而建立大顶堆比较困惑。

View File

@ -16,7 +16,7 @@
题意:给定两个数组,编写一个函数来计算它们的交集。 题意:给定两个数组,编写一个函数来计算它们的交集。
![349. 两个数组的交集](https://img-blog.csdnimg.cn/20200818193523911.png) ![349. 两个数组的交集](https://code-thinking-1253855093.file.myqcloud.com/pics/20200818193523911.png)
**说明:** **说明:**
输出结果中的每个元素一定是唯一的。 输出结果中的每个元素一定是唯一的。
@ -72,6 +72,9 @@ public:
}; };
``` ```
* 时间复杂度: O(mn)
* 空间复杂度: O(n)
## 拓展 ## 拓展
那有同学可能问了遇到哈希问题我直接都用set不就得了用什么数组啊。 那有同学可能问了遇到哈希问题我直接都用set不就得了用什么数组啊。
@ -110,6 +113,8 @@ public:
}; };
``` ```
* 时间复杂度: O(m + n)
* 空间复杂度: O(n)
## 其他语言版本 ## 其他语言版本
@ -143,9 +148,9 @@ class Solution {
return resSet.stream().mapToInt(x -> x).toArray(); return resSet.stream().mapToInt(x -> x).toArray();
//方法2另外申请一个数组存放setRes中的元素,最后返回数组 //方法2另外申请一个数组存放setRes中的元素,最后返回数组
int[] arr = new int[setRes.size()]; int[] arr = new int[resSet.size()];
int j = 0; int j = 0;
for(int i : setRes){ for(int i : resSet){
arr[j++] = i; arr[j++] = i;
} }
@ -169,6 +174,21 @@ class Solution:
val_dict[num] = 0 val_dict[num] = 0
return ans return ans
class Solution: # 使用数组方法
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
count1 = [0]*1001
count2 = [0]*1001
result = []
for i in range(len(nums1)):
count1[nums1[i]]+=1
for j in range(len(nums2)):
count2[nums2[j]]+=1
for k in range(1001):
if count1[k]*count2[k]>0:
result.append(k)
return result
``` ```

View File

@ -4,7 +4,6 @@
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 本周讲解了[贪心理论基础](https://programmercarl.com/贪心算法理论基础.html),以及第一道贪心的题目:[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html),可能会给大家一种贪心算法比较简单的错觉,好了,接下来几天的题目难度要上来了,哈哈。 > 本周讲解了[贪心理论基础](https://programmercarl.com/贪心算法理论基础.html),以及第一道贪心的题目:[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html),可能会给大家一种贪心算法比较简单的错觉,好了,接下来几天的题目难度要上来了,哈哈。
# 376. 摆动序列 # 376. 摆动序列
@ -18,18 +17,25 @@
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。 给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1: 示例 1:
* 输入: [1,7,4,9,2,5]
* 输出: 6 - 输入: [1,7,4,9,2,5]
* 解释: 整个序列均为摆动序列。 - 输出: 6
- 解释: 整个序列均为摆动序列。
示例 2: 示例 2:
* 输入: [1,17,5,10,13,15,10,5,16,8]
* 输出: 7 - 输入: [1,17,5,10,13,15,10,5,16,8]
* 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。 - 输出: 7
- 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3: 示例 3:
* 输入: [1,2,3,4,5,6,7,8,9]
* 输出: 2 - 输入: [1,2,3,4,5,6,7,8,9]
- 输出: 2
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,寻找摆动有细节!| LeetCode376.摆动序列](https://www.bilibili.com/video/BV17M411b7NS),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 1贪心解法 ## 思路 1贪心解法
@ -41,7 +47,7 @@
用示例二来举例,如图所示: 用示例二来举例,如图所示:
![376.摆动序列](https://img-blog.csdnimg.cn/20201124174327597.png) ![376.摆动序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124174327597.png)
**局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值**。 **局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值**。
@ -81,11 +87,9 @@
所以我们记录峰值的条件应该是: `(preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)`,为什么这里允许 prediff == 0 ,就是为了 上面我说的这种情况。 所以我们记录峰值的条件应该是: `(preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)`,为什么这里允许 prediff == 0 ,就是为了 上面我说的这种情况。
### 情况二:数组首尾两端 ### 情况二:数组首尾两端
所以本题统计峰值的时候,数组最左面和最右面如何统计呢?
所以本题统计峰值的时候,数组最左面和最右面如果统计呢?
题目中说了,如果只有两个不同的元素,那摆动序列也是 2。 题目中说了,如果只有两个不同的元素,那摆动序列也是 2。
@ -103,7 +107,7 @@
那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即 preDiff = 0如图 那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即 preDiff = 0如图
![376.摆动序列1](https://img-blog.csdnimg.cn/20201124174357612.png) ![376.摆动序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124174357612.png)
针对以上情形result 初始为 1默认最右面有一个峰值此时 curDiff > 0 && preDiff <= 0那么 result++(计算了左面的峰值),最后得到的 result 就是 2峰值个数为 2 即摆动序列长度为 2 针对以上情形result 初始为 1默认最右面有一个峰值此时 curDiff > 0 && preDiff <= 0那么 result++(计算了左面的峰值),最后得到的 result 就是 2峰值个数为 2 即摆动序列长度为 2
@ -130,8 +134,9 @@ public:
} }
}; };
``` ```
* 时间复杂度O(n)
* 空间复杂度O(1) - 时间复杂度O(n)
- 空间复杂度O(1)
此时大家是不是发现 以上代码提交也不能通过本题? 此时大家是不是发现 以上代码提交也不能通过本题?
@ -188,13 +193,13 @@ public:
很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即 nums[i] > nums[i-1]),要么是作为山谷(即 nums[i] < nums[i - 1] 很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即 nums[i] > nums[i-1]),要么是作为山谷(即 nums[i] < nums[i - 1]
* 设dp状态`dp[i][0]`表示考虑前i个数第i个数作为山峰的摆动子序列的最长长度 - 设 dp 状态`dp[i][0]`,表示考虑前 i 个数,第 i 个数作为山峰的摆动子序列的最长长度
* 设dp状态`dp[i][1]`表示考虑前i个数第i个数作为山谷的摆动子序列的最长长度 - 设 dp 状态`dp[i][1]`,表示考虑前 i 个数,第 i 个数作为山谷的摆动子序列的最长长度
则转移方程为: 则转移方程为:
* `dp[i][0] = max(dp[i][0], dp[j][1] + 1)`,其中`0 < j < i``nums[j] < nums[i]`表示将nums[i]接到前面某个山谷后面作为山峰 - `dp[i][0] = max(dp[i][0], dp[j][1] + 1)`,其中`0 < j < i``nums[j] < nums[i]`表示将 nums[i]接到前面某个山谷后面作为山峰
* `dp[i][1] = max(dp[i][1], dp[j][0] + 1)`,其中`0 < j < i``nums[j] > nums[i]`表示将nums[i]接到前面某个山峰后面,作为山谷。 - `dp[i][1] = max(dp[i][1], dp[j][0] + 1)`,其中`0 < j < i``nums[j] > nums[i]`,表示将 nums[i]接到前面某个山峰后面,作为山谷。
初始状态: 初始状态:
@ -223,28 +228,25 @@ public:
}; };
``` ```
* 时间复杂度O(n^2) - 时间复杂度O(n^2)
* 空间复杂度O(n) - 空间复杂度O(n)
**进阶** **进阶**
可以用两棵线段树来维护区间的最大值 可以用两棵线段树来维护区间的最大值
* 每次更新`dp[i][0]`,则在`tree1`的`nums[i]`位置值更新为`dp[i][0]` - 每次更新`dp[i][0]`,则在`tree1`的`nums[i]`位置值更新为`dp[i][0]`
* 每次更新`dp[i][1]`,则在`tree2`的`nums[i]`位置值更新为`dp[i][1]` - 每次更新`dp[i][1]`,则在`tree2`的`nums[i]`位置值更新为`dp[i][1]`
* 则dp转移方程中就没有必要j从0遍历到i-1可以直接在线段树中查询指定区间的值即可。 - 则 dp 转移方程中就没有必要 j 从 0 遍历到 i-1可以直接在线段树中查询指定区间的值即可。
时间复杂度O(nlog n) 时间复杂度O(nlog n)
空间复杂度O(n) 空间复杂度O(n)
## 其他语言版本 ## 其他语言版本
### Java ### Java
```Java ```Java
class Solution { class Solution {
public int wiggleMaxLength(int[] nums) { public int wiggleMaxLength(int[] nums) {
@ -270,6 +272,7 @@ class Solution {
} }
} }
``` ```
```java ```java
// DP // DP
class Solution { class Solution {
@ -360,6 +363,7 @@ class Solution:
### Go ### Go
**贪心** **贪心**
```go ```go
func wiggleMaxLength(nums []int) int { func wiggleMaxLength(nums []int) int {
n := len(nums) n := len(nums)
@ -383,6 +387,7 @@ func wiggleMaxLength(nums []int) int {
``` ```
**动态规划** **动态规划**
```go ```go
func wiggleMaxLength(nums []int) int { func wiggleMaxLength(nums []int) int {
n := len(nums) n := len(nums)
@ -420,7 +425,9 @@ func max(a, b int) int {
``` ```
### Javascript ### Javascript
**贪心** **贪心**
```Javascript ```Javascript
var wiggleMaxLength = function(nums) { var wiggleMaxLength = function(nums) {
if(nums.length <= 1) return nums.length if(nums.length <= 1) return nums.length
@ -437,7 +444,9 @@ var wiggleMaxLength = function(nums) {
return result return result
}; };
``` ```
**动态规划** **动态规划**
```Javascript ```Javascript
var wiggleMaxLength = function(nums) { var wiggleMaxLength = function(nums) {
if (nums.length === 1) return 1; if (nums.length === 1) return 1;
@ -458,30 +467,55 @@ var wiggleMaxLength = function(nums) {
``` ```
### Rust ### Rust
**贪心** **贪心**
```Rust ```Rust
impl Solution { impl Solution {
pub fn wiggle_max_length(nums: Vec<i32>) -> i32 { pub fn wiggle_max_length(nums: Vec<i32>) -> i32 {
let len = nums.len() as usize; if nums.len() == 1 {
if len <= 1 { return 1;
return len as i32;
} }
let mut preDiff = 0; let mut res = 1;
let mut curDiff = 0; let mut pre_diff = 0;
let mut result = 1; for i in 0..nums.len() - 1 {
for i in 0..len-1 { let cur_diff = nums[i + 1] - nums[i];
curDiff = nums[i+1] - nums[i]; if (pre_diff <= 0 && cur_diff > 0) || (pre_diff >= 0 && cur_diff < 0) {
if (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0) { res += 1;
result += 1; pre_diff = cur_diff;
preDiff = curDiff;
} }
} }
result res
}
}
```
**动态规划**
```rust
impl Solution {
pub fn wiggle_max_length(nums: Vec<i32>) -> i32 {
if nums.len() == 1 {
return 1;
}
let (mut down, mut up) = (1, 1);
for i in 1..nums.len() {
// i - 1 为峰顶
if nums[i] < nums[i - 1] {
down = down.max(up + 1);
}
// i - 1 为峰谷
if nums[i] > nums[i - 1] {
up = up.max(down + 1);
}
}
down.max(up)
} }
} }
``` ```
### C ### C
**贪心** **贪心**
```c ```c
@ -546,8 +580,6 @@ int wiggleMaxLength(int* nums, int numsSize){
} }
``` ```
### TypeScript ### TypeScript
**贪心** **贪心**
@ -561,16 +593,13 @@ function wiggleMaxLength(nums: number[]): number {
let count: number = 1; let count: number = 1;
for (let i = 1; i < length; i++) { for (let i = 1; i < length; i++) {
curDiff = nums[i] - nums[i - 1]; curDiff = nums[i] - nums[i - 1];
if ( if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
(preDiff <= 0 && curDiff > 0) ||
(preDiff >= 0 && curDiff < 0)
) {
preDiff = curDiff; preDiff = curDiff;
count++; count++;
} }
} }
return count; return count;
}; }
``` ```
**动态规划** **动态规划**
@ -579,7 +608,7 @@ function wiggleMaxLength(nums: number[]): number {
function wiggleMaxLength(nums: number[]): number { function wiggleMaxLength(nums: number[]): number {
const length: number = nums.length; const length: number = nums.length;
if (length <= 1) return length; if (length <= 1) return length;
const dp: number[][] = new Array(length).fill(0).map(_ => []); const dp: number[][] = new Array(length).fill(0).map((_) => []);
dp[0][0] = 1; // 第一个数作为波峰 dp[0][0] = 1; // 第一个数作为波峰
dp[0][1] = 1; // 第一个数作为波谷 dp[0][1] = 1; // 第一个数作为波谷
for (let i = 1; i < length; i++) { for (let i = 1; i < length; i++) {
@ -593,7 +622,7 @@ function wiggleMaxLength(nums: number[]): number {
} }
} }
return Math.max(dp[length - 1][0], dp[length - 1][1]); return Math.max(dp[length - 1][0], dp[length - 1][1]);
}; }
``` ```
### Scala ### Scala
@ -626,3 +655,4 @@ object Solution {
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/> <img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a> </a>

View File

@ -105,7 +105,7 @@ dp[i]考虑nums[j])可以由 dp[i - nums[j]]不考虑nums[j] 推导
我们再来用示例中的例子推导一下: 我们再来用示例中的例子推导一下:
![377.组合总和Ⅳ](https://img-blog.csdnimg.cn/20210131174250148.jpg) ![377.组合总和Ⅳ](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000625.png)
如果代码运行处的结果不是想要的结果就把dp[i]都打出来,看看和我们推导的一不一样。 如果代码运行处的结果不是想要的结果就把dp[i]都打出来,看看和我们推导的一不一样。
@ -130,6 +130,11 @@ public:
``` ```
* 时间复杂度: O(target * n),其中 n 为 nums 的长度
* 空间复杂度: O(target)
C++测试用例有两个数相加超过int的数据所以需要在if里加上dp[i] < INT_MAX - dp[i - num] C++测试用例有两个数相加超过int的数据所以需要在if里加上dp[i] < INT_MAX - dp[i - num]
但java就不用考虑这个限制java里的int也是四个字节吧也有可能leetcode后台对不同语言的测试数据不一样。 但java就不用考虑这个限制java里的int也是四个字节吧也有可能leetcode后台对不同语言的测试数据不一样。

View File

@ -39,8 +39,6 @@ canConstruct("aa", "aab") -> true
那么第一个思路其实就是暴力枚举了两层for循环不断去寻找代码如下 那么第一个思路其实就是暴力枚举了两层for循环不断去寻找代码如下
```CPP ```CPP
// 时间复杂度: O(n^2)
// 空间复杂度O(1)
class Solution { class Solution {
public: public:
bool canConstruct(string ransomNote, string magazine) { bool canConstruct(string ransomNote, string magazine) {
@ -62,6 +60,9 @@ public:
}; };
``` ```
* 时间复杂度: O(n^2)
* 空间复杂度: O(1)
这里时间复杂度是比较高的而且里面还有一个字符串删除也就是erase的操作也是费时的当然这段代码也可以过这道题。 这里时间复杂度是比较高的而且里面还有一个字符串删除也就是erase的操作也是费时的当然这段代码也可以过这道题。
@ -78,8 +79,6 @@ public:
代码如下: 代码如下:
```CPP ```CPP
// 时间复杂度: O(n)
// 空间复杂度O(1)
class Solution { class Solution {
public: public:
bool canConstruct(string ransomNote, string magazine) { bool canConstruct(string ransomNote, string magazine) {
@ -105,6 +104,10 @@ public:
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 其他语言版本 ## 其他语言版本

View File

@ -77,7 +77,8 @@ if (s[i - 1] != t[j - 1])此时相当于t要删除元素t如果把当前
因为这样的定义在dp二维矩阵中可以留出初始化的区间如图 因为这样的定义在dp二维矩阵中可以留出初始化的区间如图
![392.判断子序列](https://img-blog.csdnimg.cn/20210303173115966.png)
![392.判断子序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20210303173115966.png)
如果要是定义的dp[i][j]是以下标i为结尾的字符串s和以下标j为结尾的字符串t初始化就比较麻烦了。 如果要是定义的dp[i][j]是以下标i为结尾的字符串s和以下标j为结尾的字符串t初始化就比较麻烦了。
@ -94,13 +95,15 @@ vector<vector<int>> dp(s.size() + 1, vector<int>(t.size() + 1, 0));
如图所示: 如图所示:
![392.判断子序列1](https://img-blog.csdnimg.cn/20210303172354155.jpg)
![392.判断子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210303172354155.jpg)
5. 举例推导dp数组 5. 举例推导dp数组
以示例一为例输入s = "abc", t = "ahbgdc"dp状态转移图如下 以示例一为例输入s = "abc", t = "ahbgdc"dp状态转移图如下
![392.判断子序列2](https://img-blog.csdnimg.cn/2021030317364166.jpg)
![392.判断子序列2](https://code-thinking-1253855093.file.myqcloud.com/pics/2021030317364166.jpg)
dp[i][j]表示以下标i-1为结尾的字符串s和以下标j-1为结尾的字符串t 相同子序列的长度所以如果dp[s.size()][t.size()] 与 字符串s的长度相同说明s与t的最长相同子序列就是s那么s 就是 t 的子序列。 dp[i][j]表示以下标i-1为结尾的字符串s和以下标j-1为结尾的字符串t 相同子序列的长度所以如果dp[s.size()][t.size()] 与 字符串s的长度相同说明s与t的最长相同子序列就是s那么s 就是 t 的子序列。

View File

@ -13,7 +13,8 @@
示例: 示例:
![404.左叶子之和1](https://img-blog.csdnimg.cn/20210204151927654.png)
![404.左叶子之和1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204151927654.png)
## 视频讲解 ## 视频讲解
@ -27,8 +28,7 @@
大家思考一下如下图中二叉树,左叶子之和究竟是多少? 大家思考一下如下图中二叉树,左叶子之和究竟是多少?
![404.左叶子之和](https://img-blog.csdnimg.cn/20210204151949672.png) ![404.左叶子之和](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204151949672.png)
**其实是0因为这棵树根本没有左叶子** **其实是0因为这棵树根本没有左叶子**
但看这个图的左叶子之和是多少? 但看这个图的左叶子之和是多少?

View File

@ -37,6 +37,10 @@
题目数据确保队列可以被重建 题目数据确保队列可以被重建
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,不要两边一起贪,会顾此失彼 | LeetCode406.根据身高重建队列](https://www.bilibili.com/video/BV1EA411675Y),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后再按照另一个维度重新排列。 本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后再按照另一个维度重新排列。
@ -59,7 +63,7 @@
以图中{5,2} 为例: 以图中{5,2} 为例:
![406.根据身高重建队列](https://img-blog.csdnimg.cn/20201216201851982.png) ![406.根据身高重建队列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201216201851982.png)
按照身高排序之后优先按身高高的people的k来插入后序插入节点也不会影响前面已经插入的节点最终按照k的规则完成了队列。 按照身高排序之后优先按身高高的people的k来插入后序插入节点也不会影响前面已经插入的节点最终按照k的规则完成了队列。

View File

@ -148,7 +148,8 @@ dp[j]的数值一定是小于等于j的。
用例1输入[1,5,11,5] 为例,如图: 用例1输入[1,5,11,5] 为例,如图:
![416.分割等和子集2](https://img-blog.csdnimg.cn/20210110104240545.png)
![416.分割等和子集2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110104240545.png)
最后dp[11] == 11说明可以将这个数组分割成两个子集使得两个子集的元素和相等。 最后dp[11] == 11说明可以将这个数组分割成两个子集使得两个子集的元素和相等。
@ -302,7 +303,7 @@ class Solution:
target = sum(nums) target = sum(nums)
if target % 2 == 1: return False if target % 2 == 1: return False
target //= 2 target //= 2
dp = [0] * (len(nums) + 1) dp = [0] * (target + 1)
for i in range(len(nums)): for i in range(len(nums)):
for j in range(target, nums[i] - 1, -1): for j in range(target, nums[i] - 1, -1):
dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])

View File

@ -236,9 +236,222 @@ for (int j = 0; j < m; j++) {
空间复杂度为O(n * m) 这个就不难理解了。开了几个 n * m 的数组。 空间复杂度为O(n * m) 这个就不难理解了。开了几个 n * m 的数组。
## 其他语言版本 ## 其他语言版本
### Java
深度优先遍历:
```Java
class Solution {
// 四个位置
private static final int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
/**
* @param heights 题目给定的二维数组
* @param row 当前位置的行号
* @param col 当前位置的列号
* @param sign 记录是哪一条河,两条河中可以一个为 0一个为 1
* @param visited 记录这个位置可以到哪条河
*/
public void dfs(int[][] heights, int row, int col, int sign, boolean[][][] visited) {
for (int[] current: position) {
int curRow = row + current[0], curCol = col + current[1];
// 越界
if (curRow < 0 || curRow >= heights.length || curCol < 0 || curCol >= heights[0].length)
continue;
// 高度不合适或者已经被访问过了
if (heights[curRow][curCol] < heights[row][col] || visited[curRow][curCol][sign]) continue;
visited[curRow][curCol][sign] = true;
dfs(heights, curRow, curCol, sign, visited);
}
}
public List<List<Integer>> pacificAtlantic(int[][] heights) {
int rowSize = heights.length, colSize = heights[0].length;
List<List<Integer>> ans = new ArrayList<>();
// 记录 [row, col] 位置是否可以到某条河,可以为 true反之为 false
// 假设太平洋的标记为 1大西洋为 0
boolean[][][] visited = new boolean[rowSize][colSize][2];
for (int row = 0; row < rowSize; row++) {
visited[row][colSize - 1][0] = true;
visited[row][0][1] = true;
dfs(heights, row, colSize - 1, 0, visited);
dfs(heights, row, 0, 1, visited);
}
for (int col = 0; col < colSize; col++) {
visited[rowSize - 1][col][0] = true;
visited[0][col][1] = true;
dfs(heights, rowSize - 1, col, 0, visited);
dfs(heights, 0, col, 1, visited);
}
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < colSize; col++) {
// 如果该位置即可以到太平洋又可以到大西洋,就放入答案数组
if (visited[row][col][0] && visited[row][col][1])
ans.add(List.of(row, col));
}
}
return ans;
}
}
```
广度优先遍历:
```Java
class Solution {
// 四个位置
private static final int[][] position = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
/**
* @param heights 题目给定的二维数组
* @param queue 记录可以到达边界的节点
* @param visited 记录这个位置可以到哪条河
*/
public void bfs(int[][] heights, Queue<int[]> queue, boolean[][][] visited) {
while (!queue.isEmpty()) {
int[] curPos = queue.poll();
for (int[] current: position) {
int row = curPos[0] + current[0], col = curPos[1] + current[1], sign = curPos[2];
// 越界
if (row < 0 || row >= heights.length || col < 0 || col >= heights[0].length) continue;
// 高度不合适或者已经被访问过了
if (heights[row][col] < heights[curPos[0]][curPos[1]] || visited[row][col][sign]) continue;
visited[row][col][sign] = true;
queue.add(new int[]{row, col, sign});
}
}
}
public List<List<Integer>> pacificAtlantic(int[][] heights) {
int rowSize = heights.length, colSize = heights[0].length;
List<List<Integer>> ans = new ArrayList<>();
boolean[][][] visited = new boolean[rowSize][colSize][2];
// 队列,保存的数据为 [行号, 列号, 标记]
// 假设太平洋的标记为 1大西洋为 0
Queue<int[]> queue = new ArrayDeque<>();
for (int row = 0; row < rowSize; row++) {
visited[row][colSize - 1][0] = true;
visited[row][0][1] = true;
queue.add(new int[]{row, colSize - 1, 0});
queue.add(new int[]{row, 0, 1});
}
for (int col = 0; col < colSize; col++) {
visited[rowSize - 1][col][0] = true;
visited[0][col][1] = true;
queue.add(new int[]{rowSize - 1, col, 0});
queue.add(new int[]{0, col, 1});
}
bfs(heights, queue, visited);
for (int row = 0; row < rowSize; row++) {
for (int col = 0; col < colSize; col++) {
// 如果该位置即可以到太平洋又可以到大西洋,就放入答案数组
if (visited[row][col][0] && visited[row][col][1])
ans.add(List.of(row, col));
}
}
return ans;
}
}
```
### Python
深度优先遍历
```Python3
class Solution:
def __init__(self):
self.position = [[-1, 0], [0, 1], [1, 0], [0, -1]] # 四个方向
# heights题目给定的二维数组 row当前位置的行号 col当前位置的列号
# sign记录是哪一条河两条河中可以一个为 0一个为 1
# visited记录这个位置可以到哪条河
def dfs(self, heights: List[List[int]], row: int, col: int, sign: int, visited: List[List[List[int]]]):
for current in self.position:
curRow, curCol = row + current[0], col + current[1]
# 索引下标越界
if curRow < 0 or curRow >= len(heights) or curCol < 0 or curCol >= len(heights[0]): continue
# 不满足条件或者已经被访问过
if heights[curRow][curCol] < heights[row][col] or visited[curRow][curCol][sign]: continue
visited[curRow][curCol][sign] = True
self.dfs(heights, curRow, curCol, sign, visited)
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
rowSize, colSize = len(heights), len(heights[0])
# visited 记录 [row, col] 位置是否可以到某条河,可以为 true反之为 false
# 假设太平洋的标记为 1大西洋为 0
# ans 用来保存满足条件的答案
ans, visited = [], [[[False for _ in range(2)] for _ in range(colSize)] for _ in range(rowSize)]
for row in range(rowSize):
visited[row][0][1] = True
visited[row][colSize - 1][0] = True
self.dfs(heights, row, 0, 1, visited)
self.dfs(heights, row, colSize - 1, 0, visited)
for col in range(0, colSize):
visited[0][col][1] = True
visited[rowSize - 1][col][0] = True
self.dfs(heights, 0, col, 1, visited)
self.dfs(heights, rowSize - 1, col, 0, visited)
for row in range(rowSize):
for col in range(colSize):
# 如果该位置即可以到太平洋又可以到大西洋,就放入答案数组
if visited[row][col][0] and visited[row][col][1]:
ans.append([row, col])
return ans
```
广度优先遍历
```Python3
class Solution:
def __init__(self):
self.position = [[-1, 0], [0, 1], [1, 0], [0, -1]]
# heights题目给定的二维数组visited记录这个位置可以到哪条河
def bfs(self, heights: List[List[int]], queue: deque, visited: List[List[List[int]]]):
while queue:
curPos = queue.popleft()
for current in self.position:
row, col, sign = curPos[0] + current[0], curPos[1] + current[1], curPos[2]
# 越界
if row < 0 or row >= len(heights) or col < 0 or col >= len(heights[0]): continue
# 不满足条件或已经访问过
if heights[row][col] < heights[curPos[0]][curPos[1]] or visited[row][col][sign]: continue
visited[row][col][sign] = True
queue.append([row, col, sign])
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
rowSize, colSize = len(heights), len(heights[0])
# visited 记录 [row, col] 位置是否可以到某条河,可以为 true反之为 false
# 假设太平洋的标记为 1大西洋为 0
# ans 用来保存满足条件的答案
ans, visited = [], [[[False for _ in range(2)] for _ in range(colSize)] for _ in range(rowSize)]
# 队列,保存的数据为 [行号, 列号, 标记]
# 假设太平洋的标记为 1大西洋为 0
queue = deque()
for row in range(rowSize):
visited[row][0][1] = True
visited[row][colSize - 1][0] = True
queue.append([row, 0, 1])
queue.append([row, colSize - 1, 0])
for col in range(0, colSize):
visited[0][col][1] = True
visited[rowSize - 1][col][0] = True
queue.append([0, col, 1])
queue.append([rowSize - 1, col, 0])
self.bfs(heights, queue, visited) # 广度优先遍历
for row in range(rowSize):
for col in range(colSize):
# 如果该位置即可以到太平洋又可以到大西洋,就放入答案数组
if visited[row][col][0] and visited[row][col][1]:
ans.append([row, col])
return ans
```
<p align="center"> <p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank"> <a href="https://programmercarl.com/other/kstar.html" target="_blank">

View File

@ -30,6 +30,10 @@
* 输出: 0 * 输出: 0
* 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。 * 解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,依然是判断重叠区间 | LeetCode435.无重叠区间](https://www.bilibili.com/video/BV1A14y1c7E1),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
**相信很多同学看到这道题目都冥冥之中感觉要排序,但是究竟是按照右边界排序,还是按照左边界排序呢?** **相信很多同学看到这道题目都冥冥之中感觉要排序,但是究竟是按照右边界排序,还是按照左边界排序呢?**
@ -395,18 +399,20 @@ object Solution {
```Rust ```Rust
impl Solution { impl Solution {
pub fn erase_overlap_intervals(intervals: Vec<Vec<i32>>) -> i32 { pub fn erase_overlap_intervals(intervals: Vec<Vec<i32>>) -> i32 {
if intervals.len() == 0 { return 0; } if intervals.is_empty() {
let mut intervals = intervals; return 0;
intervals.sort_by(|a, b| a[1].cmp(&b[1])); }
intervals.sort_by_key(|interval| interval[1]);
let mut count = 1; let mut count = 1;
let mut end = intervals[0][1]; let mut end = intervals[0][1];
for i in 1..intervals.len() { for v in intervals.iter().skip(1) {
if end <= intervals[i][0] { if end <= v[0] {
end = intervals[i][1]; end = v[1];
count += 1; count += 1;
} }
} }
intervals.len() as i32 - count
(intervals.len() - count) as i32
} }
} }
``` ```

View File

@ -21,7 +21,8 @@
示例: 示例:
![450.删除二叉搜索树中的节点](https://img-blog.csdnimg.cn/20201020171048265.png)
![450.删除二叉搜索树中的节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20201020171048265.png)
# 算法公开课 # 算法公开课

View File

@ -42,6 +42,10 @@
* points[i].length == 2 * points[i].length == 2
* -2^31 <= xstart < xend <= 2^31 - 1 * -2^31 <= xstart < xend <= 2^31 - 1
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,判断重叠区间问题 | LeetCode452.用最少数量的箭引爆气球](https://www.bilibili.com/video/BV1SA41167xe),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
如何使用最少的弓箭呢? 如何使用最少的弓箭呢?
@ -74,7 +78,7 @@
以题目示例: [[10,16],[2,8],[1,6],[7,12]]为例,如图:(方便起见,已经排序) 以题目示例: [[10,16],[2,8],[1,6],[7,12]]为例,如图:(方便起见,已经排序)
![452.用最少数量的箭引爆气球](https://img-blog.csdnimg.cn/20201123101929791.png) ![452.用最少数量的箭引爆气球](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123101929791.png)
可以看出首先第一组重叠气球一定是需要一个箭气球3的左边界大于了 第一组重叠气球的最小右边界所以再需要一支箭来射气球3了。 可以看出首先第一组重叠气球一定是需要一个箭气球3的左边界大于了 第一组重叠气球的最小右边界所以再需要一支箭来射气球3了。

View File

@ -83,6 +83,9 @@ public:
``` ```
* 时间复杂度: O(n^2)
* 空间复杂度: O(n^2)最坏情况下A和B的值各不相同相加产生的数字个数为 n^2
@ -94,26 +97,25 @@ Java
```Java ```Java
class Solution { class Solution {
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Map<Integer, Integer> map = new HashMap<>();
int temp;
int res = 0; int res = 0;
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
//统计两个数组中的元素之和同时统计出现的次数放入map //统计两个数组中的元素之和同时统计出现的次数放入map
for (int i : nums1) { for (int i : nums1) {
for (int j : nums2) { for (int j : nums2) {
temp = i + j; int tmp = map.getOrDefault(i + j, 0);
if (map.containsKey(temp)) { if (tmp == 0) {
map.put(temp, map.get(temp) + 1); map.put(i + j, 1);
} else { } else {
map.put(temp, 1); map.replace(i + j, tmp + 1);
} }
} }
} }
//统计剩余的两个元素的和在map中找是否存在相加为0的情况同时记录次数 //统计剩余的两个元素的和在map中找是否存在相加为0的情况同时记录次数
for (int i : nums3) { for (int i : nums3) {
for (int j : nums4) { for (int j : nums4) {
temp = i + j; int tmp = map.getOrDefault(0 - i - j, 0);
if (map.containsKey(0 - temp)) { if (tmp != 0) {
res += map.get(0 - temp); res += tmp;
} }
} }
} }

View File

@ -4,7 +4,6 @@
</a> </a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p> <p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 455.分发饼干 # 455.分发饼干
[力扣题目链接](https://leetcode.cn/problems/assign-cookies/) [力扣题目链接](https://leetcode.cn/problems/assign-cookies/)
@ -14,21 +13,26 @@
对每个孩子 i都有一个胃口值  g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。 对每个孩子 i都有一个胃口值  g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例  1: 示例  1:
* 输入: g = [1,2,3], s = [1,1]
* 输出: 1 - 输入: g = [1,2,3], s = [1,1]
- 输出: 1
解释:你有三个孩子和两块小饼干3 个孩子的胃口值分别是1,2,3。虽然你有两块小饼干由于他们的尺寸都是 1你只能让胃口值是 1 的孩子满足。所以你应该输出 1。 解释:你有三个孩子和两块小饼干3 个孩子的胃口值分别是1,2,3。虽然你有两块小饼干由于他们的尺寸都是 1你只能让胃口值是 1 的孩子满足。所以你应该输出 1。
示例  2: 示例  2:
* 输入: g = [1,2], s = [1,2,3]
* 输出: 2
* 解释:你有两个孩子和三块小饼干2个孩子的胃口值分别是1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出2.
- 输入: g = [1,2], s = [1,2,3]
- 输出: 2
- 解释:你有两个孩子和三块小饼干2 个孩子的胃口值分别是 1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2.
提示: 提示:
* 1 <= g.length <= 3 * 10^4
* 0 <= s.length <= 3 * 10^4
* 1 <= g[i], s[j] <= 2^31 - 1
- 1 <= g.length <= 3 \* 10^4
- 0 <= s.length <= 3 \* 10^4
- 1 <= g[i], s[j] <= 2^31 - 1
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,你想先喂哪个小孩?| LeetCode455.分发饼干](https://www.bilibili.com/video/BV1MM411b7cq),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 ## 思路
@ -46,16 +50,12 @@
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230203105634.png) ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230203105634.png)
这个例子可以看出饼干 9 只有喂给胃口为 7 的小孩,这样才是整体最优解,并想不出反例,那么就可以撸代码了。 这个例子可以看出饼干 9 只有喂给胃口为 7 的小孩,这样才是整体最优解,并想不出反例,那么就可以撸代码了。
C++代码整体如下: C++代码整体如下:
```CPP ```CPP
// 版本一 // 版本一
// 时间复杂度O(nlogn)
// 空间复杂度O(1)
class Solution { class Solution {
public: public:
int findContentChildren(vector<int>& g, vector<int>& s) { int findContentChildren(vector<int>& g, vector<int>& s) {
@ -73,12 +73,14 @@ public:
} }
}; };
``` ```
* 时间复杂度O(nlogn)
* 空间复杂度O(1)
从代码中可以看出我用了一个 index 来控制饼干数组的遍历,遍历饼干并没有再起一个 for 循环,而是采用自减的方式,这也是常用的技巧。 从代码中可以看出我用了一个 index 来控制饼干数组的遍历,遍历饼干并没有再起一个 for 循环,而是采用自减的方式,这也是常用的技巧。
有的同学看到要遍历两个数组,就想到用两个 for 循环,那样逻辑其实就复杂了。 有的同学看到要遍历两个数组,就想到用两个 for 循环,那样逻辑其实就复杂了。
### 注意事项 ### 注意事项
注意版本一的代码中,可以看出来,是先遍历的胃口,在遍历的饼干,那么可不可以 先遍历 饼干,在遍历胃口呢? 注意版本一的代码中,可以看出来,是先遍历的胃口,在遍历的饼干,那么可不可以 先遍历 饼干,在遍历胃口呢?
@ -95,7 +97,6 @@ if 里的 index 指向 胃口 10 for里的i指向饼干9因为 饼干9 满
所以 一定要 for 控制 胃口,里面的 if 控制饼干。 所以 一定要 for 控制 胃口,里面的 if 控制饼干。
### 其他思路 ### 其他思路
**也可以换一个思路,小饼干先喂饱小胃口** **也可以换一个思路,小饼干先喂饱小胃口**
@ -118,6 +119,9 @@ public:
} }
}; };
``` ```
* 时间复杂度O(nlogn)
* 空间复杂度O(1)
细心的录友可以发现,这种写法,两个循环的顺序改变了,先遍历的饼干,在遍历的胃口,这是因为遍历顺序变了,我们是从小到大遍历。 细心的录友可以发现,这种写法,两个循环的顺序改变了,先遍历的饼干,在遍历的胃口,这是因为遍历顺序变了,我们是从小到大遍历。
@ -131,8 +135,8 @@ public:
## 其他语言版本 ## 其他语言版本
### Java ### Java
```java ```java
class Solution { class Solution {
// 思路1优先考虑饼干小饼干先喂饱小胃口 // 思路1优先考虑饼干小饼干先喂饱小胃口
@ -151,6 +155,7 @@ class Solution {
} }
} }
``` ```
```java ```java
class Solution { class Solution {
// 思路2优先考虑胃口先喂饱大胃口 // 思路2优先考虑胃口先喂饱大胃口
@ -172,6 +177,7 @@ class Solution {
``` ```
### Python ### Python
```python ```python
class Solution: class Solution:
# 思路1优先考虑小胃口 # 思路1优先考虑小胃口
@ -184,6 +190,7 @@ class Solution:
res += 1 res += 1
return res return res
``` ```
```python ```python
class Solution: class Solution:
# 思路2优先考虑大胃口 # 思路2优先考虑大胃口
@ -199,6 +206,7 @@ class Solution:
``` ```
### Go ### Go
```golang ```golang
//排序后,局部最优 //排序后,局部最优
func findContentChildren(g []int, s []int) int { func findContentChildren(g []int, s []int) int {
@ -218,41 +226,40 @@ func findContentChildren(g []int, s []int) int {
``` ```
### Rust ### Rust
```rust ```rust
pub fn find_content_children(children: Vec<i32>, cookie: Vec<i32>) -> i32 { pub fn find_content_children(mut children: Vec<i32>, mut cookie: Vec<i32>) -> i32 {
let mut children = children;
let mut cookies = cookie;
children.sort(); children.sort();
cookies.sort(); cookies.sort();
let (mut child, mut cookie) = (0usize, 0usize); let (mut child, mut cookie) = (0, 0);
while child < children.len() && cookie < cookies.len() { while child < children.len() && cookie < cookies.len() {
// 优先选择最小饼干喂饱孩子 // 优先选择最小饼干喂饱孩子
if children[child] <= cookies[cookie] { if children[child] <= cookies[cookie] {
child += 1; child += 1;
} }
cookie += 1 cookie += 1;
} }
child as i32 child as i32
} }
``` ```
### Javascript ### Javascript
```js ```js
var findContentChildren = function (g, s) { var findContentChildren = function (g, s) {
g = g.sort((a, b) => a - b) g = g.sort((a, b) => a - b);
s = s.sort((a, b) => a - b) s = s.sort((a, b) => a - b);
let result = 0 let result = 0;
let index = s.length - 1 let index = s.length - 1;
for (let i = g.length - 1; i >= 0; i--) { for (let i = g.length - 1; i >= 0; i--) {
if (index >= 0 && s[index] >= g[i]) { if (index >= 0 && s[index] >= g[i]) {
result++ result++;
index-- index--;
} }
} }
return result return result;
}; };
``` ```
### TypeScript ### TypeScript
@ -275,7 +282,7 @@ function findContentChildren(g: number[], s: number[]): number {
curChild--; curChild--;
} }
return resCount; return resCount;
}; }
``` ```
```typescript ```typescript
@ -294,7 +301,7 @@ function findContentChildren(g: number[], s: number[]): number {
curCookie++; curCookie++;
} }
return curChild; return curChild;
}; }
``` ```
### C ### C

View File

@ -73,6 +73,8 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
不过这种解法还有一个问题,就是 我们最终还是要判断 一个字符串s + s是否出现过 s 的过程大家可能直接用containsfind 之类的库函数。 却忽略了实现这些函数的时间复杂度暴力解法是m * n一般库函数实现为 O(m + n))。 不过这种解法还有一个问题,就是 我们最终还是要判断 一个字符串s + s是否出现过 s 的过程大家可能直接用containsfind 之类的库函数。 却忽略了实现这些函数的时间复杂度暴力解法是m * n一般库函数实现为 O(m + n))。
@ -185,6 +187,8 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
前缀表不减一的C++代码实现: 前缀表不减一的C++代码实现:
@ -219,6 +223,8 @@ public:
} }
}; };
``` ```
* 时间复杂度: O(n)
* 空间复杂度: O(n)
## 其他语言版本 ## 其他语言版本

Some files were not shown because too many files have changed in this diff Show More