diff --git a/problems/0005.最长回文子串.md b/problems/0005.最长回文子串.md index 4d48f184..b1987b87 100644 --- a/problems/0005.最长回文子串.md +++ b/problems/0005.最长回文子串.md @@ -108,7 +108,7 @@ dp[i][j]可以初始化为true么? 当然不行,怎能刚开始就全都匹 dp[i + 1][j - 1] 在 dp[i][j]的左下角,如图: -![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]是不是回文,那结果一定是不对的。 @@ -142,7 +142,7 @@ for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 举例,输入:"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]的时候一定是只填充右上半部分**。 diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index df038806..d1135497 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -13,7 +13,7 @@ 给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。 -![17.电话号码的字母组合](https://img-blog.csdnimg.cn/2020102916424043.png) +![17.电话号码的字母组合](https://code-thinking-1253855093.file.myqcloud.com/pics/2020102916424043.png) 示例: * 输入:"23" @@ -66,7 +66,7 @@ const string letterMap[10] = { 例如:输入:"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"]。 diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index b0641b5f..48c6dd5d 100644 --- a/problems/0019.删除链表的倒数第N个节点.md +++ b/problems/0019.删除链表的倒数第N个节点.md @@ -17,7 +17,8 @@ 示例 1: -![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 输出:[1,2,3,5] diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index 737fab86..a25129c5 100644 --- a/problems/0020.有效的括号.md +++ b/problems/0020.有效的括号.md @@ -80,14 +80,17 @@ cd a/b/c/../../ 先来分析一下 这里有三种不匹配的情况, + 1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。 -![括号匹配1](https://img-blog.csdnimg.cn/2020080915505387.png) +![括号匹配1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020080915505387.png) 2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。 -![括号匹配2](https://img-blog.csdnimg.cn/20200809155107397.png) +![括号匹配2](https://code-thinking-1253855093.file.myqcloud.com/pics/20200809155107397.png) 3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。 -![括号匹配3](https://img-blog.csdnimg.cn/20200809155115779.png) +![括号匹配3](https://code-thinking-1253855093.file.myqcloud.com/pics/20200809155115779.png) + + 我们的代码只要覆盖了这三种不匹配的情况,就不会出问题,可以看出 动手之前分析好题目的重要性。 diff --git a/problems/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md index 4d9ee74f..58340c21 100644 --- a/problems/0035.搜索插入位置.md +++ b/problems/0035.搜索插入位置.md @@ -1,3 +1,4 @@ +

@@ -7,6 +8,7 @@ + # 35.搜索插入位置 [力扣题目链接](https://leetcode.cn/problems/search-insert-position/) @@ -16,18 +18,22 @@ 你可以假设数组中无重复元素。 示例 1: + * 输入: [1,3,5,6], 5 * 输出: 2 -示例 2: +示例 2: + * 输入: [1,3,5,6], 2 * 输出: 1 示例 3: + * 输入: [1,3,5,6], 7 * 输出: 4 示例 4: + * 输入: [1,3,5,6], 0 * 输出: 0 @@ -37,7 +43,7 @@ 这道题目,要在数组中插入目标值,无非是这四种情况。 -![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的位置,并返回其下标。 -![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) 效率如下: -![35_搜索插入位置2](https://img-blog.csdnimg.cn/2020121623272877.png) +![35_搜索插入位置2](https://code-thinking-1253855093.file.myqcloud.com/pics/2020121623272877.png) ### 二分法第二种写法 @@ -199,7 +206,7 @@ public: ## 其他语言版本 -### Java +### Java ```java class Solution { @@ -226,11 +233,12 @@ class Solution { } } ``` + ```java //第二种二分法:左闭右开 public int searchInsert(int[] nums, int target) { int left = 0; - int right = nums.length; + int right = nums.length; while (left < right) { //左闭右开 [left, right) int middle = left + ((right - left) >> 1); if (nums[middle] > target) { @@ -290,7 +298,8 @@ impl Solution { } ``` -### Python +### Python + ```python class Solution: def searchInsert(self, nums: List[int], target: int) -> int: @@ -308,7 +317,8 @@ class Solution: return right + 1 ``` -### JavaScript +### JavaScript + ```js var searchInsert = function (nums, target) { let l = 0, r = nums.length - 1, ans = nums.length; @@ -350,7 +360,7 @@ function searchInsert(nums: number[], target: number): number { }; ``` -### Swift +### Swift ```swift // 暴力法 @@ -383,7 +393,9 @@ func searchInsert(_ nums: [Int], _ target: Int) -> Int { return right + 1 } ``` + ### Scala + ```scala object Solution { def searchInsert(nums: Array[Int], target: Int): Int = { @@ -404,7 +416,7 @@ object Solution { } ``` -### PHP +### PHP ```php // 二分法(1):[左闭右闭] @@ -429,11 +441,13 @@ function searchInsert($nums, $target) return $r + 1; } ``` + ### C + ```c //版本一 [left, right]左闭右闭区间 int searchInsert(int* nums, int numsSize, int target){ - //左闭右开区间 [0 , numsSize-1] + //左闭右开区间 [0 , numsSize-1] int left =0; int mid =0; int right = numsSize - 1; @@ -451,14 +465,15 @@ int searchInsert(int* nums, int numsSize, int target){ } } //数组中未找到target元素 - //target在数组所有元素之后,[left, right]是右闭区间,需要返回 right +1 + //target在数组所有元素之后,[left, right]是右闭区间,需要返回 right +1 return right + 1; } ``` + ```c //版本二 [left, right]左闭右开区间 int searchInsert(int* nums, int numsSize, int target){ - //左闭右开区间 [0 , numsSize) + //左闭右开区间 [0 , numsSize) int left =0; int mid =0; int right = numsSize; diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md index 18a96d58..6edd3c5b 100644 --- a/problems/0037.解数独.md +++ b/problems/0037.解数独.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ 如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg) # 37. 解数独 @@ -14,20 +16,21 @@ 编写一个程序,通过填充空格来解决数独问题。 一个数独的解法需遵循如下规则: -数字 1-9 在每一行只能出现一次。 -数字 1-9 在每一列只能出现一次。 -数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。 -空白格用 '.' 表示。 +数字 1-9 在每一行只能出现一次。 +数字 1-9 在每一列只能出现一次。 +数字 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 和字符 '.' 。 * 你可以假设给定的数独只有唯一解。 * 给定数独永远是 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>& board) * 递归单层搜索逻辑 -![37.解数独](https://img-blog.csdnimg.cn/2020111720451790.png) +![37.解数独](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111720451790-20230310131822254.png) 在树形图中可以看出我们需要的是一个二维的递归(也就是两个for循环嵌套着递归) @@ -171,8 +174,8 @@ bool backtracking(vector>& board) { board[i][j] = '.'; // 回溯,撤销k } } - return false; // 9个数都试完了,都不行,那么就返回false - } + return false; // 9个数都试完了,都不行,那么就返回false + } } } return true; // 遍历完没有返回false,说明找到了合适棋盘位置了 @@ -223,7 +226,8 @@ public: ## 其他语言版本 -### Java +### Java + ```java class Solution { public void solveSudoku(char[][] board) { @@ -291,7 +295,8 @@ class Solution { } ``` -### Python +### Python + ```python class Solution: def solveSudoku(self, board: List[List[str]]) -> None: @@ -306,7 +311,7 @@ class Solution: for j in range(len(board[0])): # 遍历列 # 若空格内已有数字,跳过 if board[i][j] != '.': continue - for k in range(1, 10): + for k in range(1, 10): if self.is_valid(i, j, k, board): board[i][j] = str(k) if self.backtracking(board): return True @@ -334,7 +339,7 @@ class Solution: return True ``` -### Go +### Go ```go func solveSudoku(board [][]byte) { @@ -392,7 +397,8 @@ func isvalid(row, col int, k byte, board [][]byte) bool { -### Javascript +### Javascript + ```Javascript var solveSudoku = function(board) { function isValid(row, col, val, board) { @@ -433,7 +439,7 @@ var solveSudoku = function(board) { if (backTracking()) { return true } - + board[i][j] = `.` } } @@ -444,7 +450,7 @@ var solveSudoku = function(board) { } backTracking(board) return board - + }; ``` @@ -543,7 +549,7 @@ impl Solution { } ``` -### C +### C ```C bool isValid(char** board, int row, int col, int k) { @@ -660,9 +666,10 @@ func solveSudoku(_ board: inout [[Character]]) { ### Scala 详细写法: + ```scala object Solution { - + def solveSudoku(board: Array[Array[Char]]): Unit = { backtracking(board) } @@ -692,7 +699,7 @@ object Solution { return false } } - + // 列 for (j <- 0 until 9) { if (board(x)(j) == value) { @@ -717,9 +724,10 @@ object Solution { ``` 遵循Scala至简原则写法: + ```scala object Solution { - + def solveSudoku(board: Array[Array[Char]]): Unit = { backtracking(board) } diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md index a3c62c53..c4ee5ca6 100644 --- a/problems/0039.组合总和.md +++ b/problems/0039.组合总和.md @@ -1,3 +1,4 @@ +

@@ -5,40 +6,43 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 39. 组合总和 [力扣题目链接](https://leetcode.cn/problems/combination-sum/) -给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 +给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 -candidates 中的数字可以无限制重复被选取。 +candidates 中的数字可以无限制重复被选取。 说明: -* 所有数字(包括 target)都是正整数。 -* 解集不能包含重复的组合。  +* 所有数字(包括 target)都是正整数。 +* 解集不能包含重复的组合。 示例 1: + * 输入:candidates = [2,3,6,7], target = 7, * 所求解集为: -[ + [ [7], [2,2,3] -] + ] + +示例 2: -示例 2: * 输入:candidates = [2,3,5], target = 8, * 所求解集为: -[ -  [2,2,2,2], -  [2,3,3], -  [3,5] -] + [ + [2,2,2,2], + [2,3,3], + [3,5] + ] # 算法公开课 **《代码随想录》算法视频公开课:[Leetcode:39. 组合总和讲解](https://www.bilibili.com/video/BV1KT4y1M7HJ),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 - + # 思路 @@ -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,就返回! 而在[77.组合](https://programmercarl.com/0077.组合.html)和[216.组合总和III](https://programmercarl.com/0216.组合总和III.html) 中都可以知道要递归K层,因为要取k个元素的组合。 @@ -83,7 +87,7 @@ void backtracking(vector& 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。 @@ -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的话就返回。 @@ -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循环剪枝代码如下: @@ -235,7 +239,8 @@ public: # 其他语言版本 -## Java +## Java + ```Java // 剪枝优化 class Solution { @@ -264,8 +269,10 @@ class Solution { } ``` -## Python +## Python + **回溯** + ```python class Solution: def __init__(self): @@ -287,9 +294,9 @@ class Solution: self.paths.append(self.path[:]) # 因为是shallow copy,所以不能直接传入self.path return if sum_ > target: - return - - # 单层递归逻辑 + return + + # 单层递归逻辑 for i in range(start_index, len(candidates)): sum_ += candidates[i] self.path.append(candidates[i]) @@ -297,7 +304,9 @@ class Solution: sum_ -= candidates[i] # 回溯 self.path.pop() # 回溯 ``` + **剪枝回溯** + ```python class Solution: def __init__(self): @@ -321,11 +330,11 @@ class Solution: if sum_ == target: self.paths.append(self.path[:]) # 因为是shallow copy,所以不能直接传入self.path return - # 单层递归逻辑 + # 单层递归逻辑 # 如果本层 sum + condidates[i] > target,就提前结束遍历,剪枝 for i in range(start_index, len(candidates)): - if sum_ + candidates[i] > target: - return + if sum_ + candidates[i] > target: + return sum_ += candidates[i] self.path.append(candidates[i]) self.backtracking(candidates, target, sum_, i) # 因为无限制重复选取,所以不是i-1 @@ -333,7 +342,8 @@ class Solution: self.path.pop() # 回溯 ``` -## Go +## Go + 主要在于递归中传递下一个数字 ```go @@ -423,7 +433,7 @@ function combinationSum(candidates: number[], target: number): number[][] { ```Rust impl Solution { pub fn backtracking(result: &mut Vec>, path: &mut Vec, candidates: &Vec, target: i32, mut sum: i32, start_index: usize) { - if sum == target { + if sum == target { result.push(path.to_vec()); return; } @@ -447,7 +457,7 @@ impl Solution { } ``` -## C +## C ```c int* path; diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md index 415fdb60..83708df7 100644 --- a/problems/0040.组合总和II.md +++ b/problems/0040.组合总和II.md @@ -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数组下面会重点介绍。 @@ -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]相同的情况下: diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index 8ea81234..db66095d 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -1,3 +1,4 @@ +

@@ -5,9 +6,10 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ > 这个图就是大厂面试经典题目,接雨水! 最常青藤的一道题,面试官百出不厌! -# 42. 接雨水 +# 42. 接雨水 [力扣题目链接](https://leetcode.cn/problems/trapping-rain-water/) @@ -15,7 +17,7 @@ 示例 1: -![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210713205038.png) +![](https://code-thinking-1253855093.cos.ap-guangzhou.myqcloud.com/pics/20210713205038.png) * 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] * 输出:6 @@ -27,26 +29,27 @@ * 输出:9 -# 思路 +# 思路 接雨水问题在面试中还是常见题目的,有必要好好讲一讲。 本文深度讲解如下三种方法: + * 双指针法 * 动态规划 * 单调栈 -## 暴力解法 +## 暴力解法 本题暴力解法也是也是使用双指针。 首先要明确,要按照行来计算,还是按照列来计算。 按照行来计算如图: -![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的雨水高度,如图: -![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表示)。 @@ -72,7 +75,7 @@ 此时求出了列4的雨水体积。 -一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。 +一样的方法,只要从头遍历一遍所有的列,然后求出每一列雨水的体积,相加之后就是总雨水的体积了。 首先从头遍历所有的列,并且**要注意第一个柱子和最后一个柱子不接雨水**,代码如下: @@ -132,7 +135,7 @@ public: 因为每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。 -力扣后面修改了后台测试数据,所以以上暴力解法超时了。 +力扣后面修改了后台测试数据,所以以上暴力解法超时了。 ## 双指针优化 @@ -181,9 +184,9 @@ public: }; ``` -## 单调栈解法 +## 单调栈解法 -关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。 +关于单调栈的理论基础,单调栈适合解决什么问题,单调栈的工作过程,大家可以先看这题讲解 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。 单调栈就是保持栈内元素有序。和[栈与队列:单调队列](https://programmercarl.com/0239.滑动窗口最大值.html)一样,需要我们自己维持顺序,没有现成的容器可以用。 @@ -197,7 +200,7 @@ public: 1. 首先单调栈是按照行方向来计算雨水,如图: -![42.接雨水2](https://img-blog.csdnimg.cn/20210223092629946.png) +![42.接雨水2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210223092629946.png) 知道这一点,后面的就可以理解了。 @@ -211,11 +214,11 @@ 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)求一个元素右边第一个更小元素,单调栈就是递减的。 -3. 遇到相同高度的柱子怎么办。 +3. 遇到相同高度的柱子怎么办。 遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。 @@ -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. 栈里要保存什么数值 @@ -233,7 +236,7 @@ public: 长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算, -那么栈里有没有必要存一个pair类型的元素,保存柱子的高度和下标呢。 +那么栈里有没有必要存一个pair类型的元素,保存柱子的高度和下标呢。 其实不用,栈里就存放下标就行,想要知道对应的高度,通过height[stack.top()] 就知道弹出的下标对应的高度了。 @@ -245,17 +248,17 @@ stack st; // 存着下标,计算的时候用下标对应的柱子高度 明确了如上几点,我们再来看处理逻辑。 -### 单调栈处理逻辑 +### 单调栈处理逻辑 -以下操作过程其实和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 也是一样的,建议先做 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。 +以下操作过程其实和 [739. 每日温度](https://programmercarl.com/0739.每日温度.html) 也是一样的,建议先做 [739. 每日温度](https://programmercarl.com/0739.每日温度.html)。 -以下逻辑主要就是三种情况 +以下逻辑主要就是三种情况 -* 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()] -* 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()] -* 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()] +* 情况一:当前遍历的元素(柱子)高度小于栈顶元素的高度 height[i] < height[st.top()] +* 情况二:当前遍历的元素(柱子)高度等于栈顶元素的高度 height[i] == height[st.top()] +* 情况三:当前遍历的元素(柱子)高度大于栈顶元素的高度 height[i] > height[st.top()] -先将下标0的柱子加入到栈中,`st.push(0);`。 栈中存放我们遍历过的元素,所以先将下标0加进来。 +先将下标0的柱子加入到栈中,`st.push(0);`。 栈中存放我们遍历过的元素,所以先将下标0加进来。 然后开始从下标1开始遍历所有的柱子,`for (int i = 1; i < height.size(); i++)`。 @@ -278,9 +281,9 @@ 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)。 @@ -290,7 +293,7 @@ if (height[i] == height[st.top()]) { // 例如 5 5 1 7 这种情况 此时大家应该可以发现其实就是**栈顶和栈顶的下一个元素以及要入栈的元素,三个元素来接水!** -那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];` +那么雨水高度是 min(凹槽左边高度, 凹槽右边高度) - 凹槽底部高度,代码为:`int h = min(height[st.top()], height[i]) - height[mid];` 雨水的宽度是 凹槽右边的下标 - 凹槽左边的下标 - 1(因为只求中间宽度),代码为:`int w = i - st.top() - 1 ;` @@ -373,11 +376,12 @@ public: 精简之后的代码,大家就看不出去三种情况的处理了,貌似好像只处理的情况三,其实是把情况一和情况二融合了。 这样的代码不太利于理解。 -## 其他语言版本 +## 其他语言版本 -### Java: +### Java: 暴力解法: + ```java class Solution { public int trap(int[] height) { @@ -385,7 +389,7 @@ class Solution { for (int i = 0; i < height.length; i++) { // 第一个柱子和最后一个柱子不接雨水 if (i==0 || i== height.length - 1) continue; - + int rHeight = height[i]; // 记录右边柱子的最高高度 int lHeight = height[i]; // 记录左边柱子的最高高度 for (int r = i+1; r < height.length; r++) { @@ -404,6 +408,7 @@ class Solution { ``` 双指针: + ```java class Solution { public int trap(int[] height) { @@ -411,15 +416,15 @@ class Solution { if (length <= 2) return 0; int[] maxLeft = new int[length]; int[] maxRight = new int[length]; - + // 记录每个柱子左边柱子最大高度 maxLeft[0] = height[0]; for (int i = 1; i< length; i++) maxLeft[i] = Math.max(height[i], maxLeft[i-1]); - + // 记录每个柱子右边柱子最大高度 maxRight[length - 1] = height[length - 1]; for(int i = length - 2; i >= 0; i--) maxRight[i] = Math.max(height[i], maxRight[i+1]); - + // 求和 int sum = 0; for (int i = 0; i < length; i++) { @@ -432,13 +437,14 @@ class Solution { ``` 单调栈法 + ```java class Solution { public int trap(int[] height){ int size = height.length; if (size <= 2) return 0; - + // in the stack, we push the index of array // using height[] to access the real height Stack stack = new Stack(); @@ -458,7 +464,7 @@ class Solution { int heightAtIdx = height[index]; while (!stack.isEmpty() && (heightAtIdx > height[stackTop])){ int mid = stack.pop(); - + if (!stack.isEmpty()){ int left = stack.peek(); @@ -472,7 +478,7 @@ class Solution { stack.push(index); } } - + return sum; } } @@ -481,6 +487,7 @@ class Solution { ### Python: 暴力解法: + ```Python class Solution: def trap(self, height: List[int]) -> int: @@ -495,32 +502,35 @@ class Solution: for k in range(i+2,len(height)): if height[k] > rHight: rHight = height[k] - res1 = min(lHight,rHight) - height[i] + res1 = min(lHight,rHight) - height[i] if res1 > 0: res += res1 return res ``` 双指针: + ```python class Solution: def trap(self, height: List[int]) -> int: leftheight, rightheight = [0]*len(height), [0]*len(height) - + leftheight[0]=height[0] for i in range(1,len(height)): leftheight[i]=max(leftheight[i-1],height[i]) rightheight[-1]=height[-1] for i in range(len(height)-2,-1,-1): rightheight[i]=max(rightheight[i+1],height[i]) - + result = 0 for i in range(0,len(height)): summ = min(leftheight[i],rightheight[i])-height[i] result += summ return result ``` + 单调栈 + ```Python class Solution: def trap(self, height: List[int]) -> int: @@ -565,8 +575,8 @@ class Solution: result += h * w stack.append(i) return result - -# 单调栈压缩版 + +# 单调栈压缩版 class Solution: def trap(self, height: List[int]) -> int: stack = [0] @@ -586,7 +596,7 @@ class Solution: ``` -### Go +### Go ```go func trap(height []int) int { @@ -601,7 +611,7 @@ func trap(height []int) int { } left++ } else { - if height[right] > rightMax { + if height[right] > rightMax { rightMax = height[right] // //设置右边最高柱子 } else { res += rightMax - height[right] // //左边必定有柱子挡水,所以,遇到所有值小于等于rightMax的,全部加入水池 @@ -652,6 +662,7 @@ func min(a,b int)int{ ``` 单调栈解法 + ```go func trap(height []int) int { if len(height) <= 2 { @@ -896,12 +907,12 @@ int trap(int* height, int heightSize) { while (left < right) { //两个指针重合就结束 leftMax = fmax(leftMax, height[left]); rightMax = fmax(rightMax, height[right]); - if (leftMax < rightMax) { + if (leftMax < rightMax) { ans += leftMax - height[left]; //这里考虑的是下标为left的“底”能装多少水 ++left;//指针的移动次序是这个方法的关键 //这里左指针右移是因为左“墙”较矮,左边这一片实际情况下的盛水量是受制于这个矮的左“墙”的 //而较高的右边在实际情况下的限制条件可能不是当前的左“墙”,比如限制条件可能是右“墙”,就能装更高的水, - } + } else { ans += rightMax - height[right]; //同理,考虑下标为right的元素 --right; diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index d4f2e6ea..22151fdc 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -46,7 +46,8 @@ 如图: -![45.跳跃游戏II](https://img-blog.csdnimg.cn/20201201232309103.png) + +![45.跳跃游戏II](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232309103.png) **图中覆盖范围的意义在于,只要红色的区域,最多两步一定可以到!(不用管具体怎么跳,反正一定可以跳到)** @@ -96,11 +97,11 @@ public: 因为当移动下标指向nums.size - 2时: * 如果移动下标等于当前覆盖最大距离下标, 需要再走一步(即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) 代码如下: diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index 86bc704d..e08aec94 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -66,7 +66,7 @@ void backtracking (vector& nums, vector& used) * 递归终止条件 -![46.全排列](https://img-blog.csdnimg.cn/20201209174225145.png) +![46.全排列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209174225145.png) 可以看出叶子节点,就是收割结果的地方。 diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index 3eb99948..b4f7a4d8 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 47.全排列 II [力扣题目链接](https://leetcode.cn/problems/permutations-ii/) @@ -12,17 +14,20 @@ 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 示例 1: + * 输入:nums = [1,1,2] * 输出: -[[1,1,2], - [1,2,1], - [2,1,1]] + [[1,1,2], + [1,2,1], + [2,1,1]] 示例 2: + * 输入:nums = [1,2,3] * 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 提示: + * 1 <= nums.length <= 8 * -10 <= nums[i] <= 10 @@ -45,7 +50,7 @@ 我以示例中的 [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])如果使用过,那么就进行去重。 @@ -68,7 +73,7 @@ private: } for (int i = 0; i < nums.size(); i++) { // used[i - 1] == true,说明同一树枝nums[i - 1]使用过 - // used[i - 1] == false,说明同一树层nums[i - 1]使用过 + // used[i - 1] == false,说明同一树层nums[i - 1]使用过 // 如果同一树层nums[i - 1]使用过则直接跳过 if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; @@ -123,23 +128,26 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { 树层上去重(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)的树型结构如下: -![47.全排列II3](https://img-blog.csdnimg.cn/20201124201431571.png) +![47.全排列II3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124201431571.png) 大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。 ## 总结 这道题其实还是用了我们之前讲过的去重思路,但有意思的是,去重的代码中,这么写: + ```cpp if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { continue; } ``` + 和这么写: + ```cpp if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { continue; @@ -154,7 +162,7 @@ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == true) { ## 其他语言版本 -### java +### java ```java class Solution { @@ -196,7 +204,7 @@ class Solution { } ``` -### python +### python ```python class Solution: @@ -224,7 +232,7 @@ class Solution: return res ``` -### Go +### Go ```go var ( @@ -264,7 +272,6 @@ func dfs(nums []int, cur int) { ### Javascript ```javascript - var permuteUnique = function (nums) { nums.sort((a, b) => { return a - b @@ -392,6 +399,7 @@ impl Solution { ``` ### C + ```c //临时数组 int *path; @@ -483,7 +491,7 @@ object Solution { // 当前索引为0,不存在和前一个数字相等可以进入回溯 // 当前索引值和上一个索引不相等,可以回溯 // 前一个索引对应的值没有被选,可以回溯 - // 因为Scala没有continue,只能将逻辑反过来写 + // 因为Scala没有continue,只能将逻辑反过来写 if (i == 0 || (i > 0 && num(i) != num(i - 1)) || used(i-1) == false) { used(i) = true path.append(num(i)) diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index 624452fd..bd1d1c9b 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -47,7 +47,7 @@ n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上, 下面我用一个 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& chessboard) { * 递归终止条件 在如下树形结构中: -![51.N皇后](https://img-blog.csdnimg.cn/20210130182532303.jpg) +![51.N皇后](https://code-thinking-1253855093.file.myqcloud.com/pics/20210130182532303-20230310122134167.jpg) 可以看出,当递归到棋盘最底层(也就是叶子节点)的时候,就可以收集结果并返回了。 diff --git a/problems/0052.N皇后II.md b/problems/0052.N皇后II.md index 1c88fb3c..90b920ba 100644 --- a/problems/0052.N皇后II.md +++ b/problems/0052.N皇后II.md @@ -13,7 +13,9 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 上图为 8 皇后问题的一种解法。 -![51n皇后](https://img-blog.csdnimg.cn/20200821152118456.png) + + +![51n皇后](https://code-thinking-1253855093.file.myqcloud.com/pics/20200821152118456.png) 给定一个整数 n,返回 n 皇后不同的解决方案的数量。 diff --git a/problems/0053.最大子序和(动态规划).md b/problems/0053.最大子序和(动态规划).md index 506c000f..c7d1b2fd 100644 --- a/problems/0053.最大子序和(动态规划).md +++ b/problems/0053.最大子序和(动态规划).md @@ -51,7 +51,7 @@ dp[0]应该是多少呢? 5. 举例推导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]。 diff --git a/problems/0054.螺旋矩阵.md b/problems/0054.螺旋矩阵.md index 858741d0..a38e8237 100644 --- a/problems/0054.螺旋矩阵.md +++ b/problems/0054.螺旋矩阵.md @@ -37,7 +37,8 @@ 由外向内一圈一圈这么画下去,如下所示: -![螺旋矩阵](https://img-blog.csdnimg.cn/2020121623550681.png) + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220922102236.png) 这里每一种颜色,代表一条边,我们遍历的长度,可以看出每一个拐角处的处理规则,拐角处让给新的一条边来继续画。 diff --git a/problems/0055.跳跃游戏.md b/problems/0055.跳跃游戏.md index 7b02075b..a898263d 100644 --- a/problems/0055.跳跃游戏.md +++ b/problems/0055.跳跃游戏.md @@ -46,7 +46,8 @@ 如图: -![55.跳跃游戏](https://img-blog.csdnimg.cn/20201124154758229.png) + +![55.跳跃游戏](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124154758229-20230310135019977.png) i每次移动只能在cover的范围内移动,每移动一个元素,cover得到该元素数值(新的覆盖范围)的补充,让i继续移动下去。 diff --git a/problems/0056.合并区间.md b/problems/0056.合并区间.md index 3c980c2b..d467ab1a 100644 --- a/problems/0056.合并区间.md +++ b/problems/0056.合并区间.md @@ -37,7 +37,7 @@ 这么说有点抽象,看图:(**注意图中区间都是按照左边界排序之后了**) -![56.合并区间](https://img-blog.csdnimg.cn/20201223200632791.png) +![56.合并区间](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223200632791.png) 知道如何判断重复之后,剩下的就是合并了,如何去模拟合并区间呢? diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 40655f0c..bf436369 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -1,14 +1,16 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 62.不同路径 [力扣题目链接](https://leetcode.cn/problems/unique-paths/) -一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 +一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。 @@ -16,30 +18,35 @@ 示例 1: -![](https://img-blog.csdnimg.cn/20210110174033215.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110174033215.png) * 输入:m = 3, n = 7 * 输出:28 示例 2: + * 输入:m = 2, n = 3 * 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 + 1. 向右 -> 向右 -> 向下 2. 向右 -> 向下 -> 向右 3. 向下 -> 向右 -> 向右 示例 3: + * 输入:m = 7, n = 3 * 输出:28 示例 4: + * 输入:m = 3, n = 3 * 输出:6 提示: + * 1 <= m, n <= 100 * 题目数据保证答案小于等于 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++代码如下: @@ -175,7 +182,7 @@ public: 在这个图中,可以看出一共m,n的话,无论怎么走,走到终点都需要 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 步是要向下走的,不用管什么时候向下走。 @@ -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相乘溢出!** 所以不能把算式的分子都算出来,分母都算出来再做除法。 @@ -245,7 +252,8 @@ public: ## 其他语言版本 -### Java +### Java + ```java /** * 1. 确定dp数组下标含义 dp[i][j] 到每一个坐标可能的路径种类 @@ -278,7 +286,8 @@ public: ``` -### Python +### Python + ```python class Solution: # 动态规划 def uniquePaths(self, m: int, n: int) -> int: @@ -289,7 +298,8 @@ class Solution: # 动态规划 return dp[m - 1][n - 1] ``` -### Go +### Go + ```Go func uniquePaths(m int, n int) int { dp := make([][]int, m) @@ -309,19 +319,20 @@ func uniquePaths(m int, n int) int { } ``` -### Javascript +### Javascript + ```Javascript var uniquePaths = function(m, n) { const dp = Array(m).fill().map(item => Array(n)) - + for (let i = 0; i < m; ++i) { dp[i][0] = 1 } - + for (let i = 0; i < n; ++i) { dp[0][i] = 1 } - + for (let i = 1; i < m; ++i) { for (let j = 1; j < n; ++j) { dp[i][j] = dp[i - 1][j] + dp[i][j - 1] @@ -330,7 +341,9 @@ var uniquePaths = function(m, n) { return dp[m - 1][n - 1] }; ``` + >版本二:直接将dp数值值初始化为1 + ```javascript /** * @param {number} m @@ -414,9 +427,9 @@ int **initDP(int m, int n) { } //从0,0到i,0只有一种走法,所以dp[i][0]都是1,同理dp[0][j]也是1 - for(i = 0; i < m; ++i) + for(i = 0; i < m; ++i) dp[i][0] = 1; - for(j = 0; j < n; ++j) + for(j = 0; j < n; ++j) dp[0][j] = 1; return dp; } @@ -440,6 +453,7 @@ int uniquePaths(int m, int n){ ``` 滚动数组解法: + ```c int uniquePaths(int m, int n){ int i, j; @@ -455,7 +469,7 @@ int uniquePaths(int m, int n){ dp[i] += dp[i - 1]; } } - return dp[n - 1]; + return dp[n - 1]; } ``` diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index c4ce3f95..85130ab4 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -1,9 +1,11 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 63. 不同路径 II [力扣题目链接](https://leetcode.cn/problems/unique-paths-ii/) @@ -14,32 +16,33 @@ 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径? -![](https://img-blog.csdnimg.cn/20210111204901338.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210111204901338.png) 网格中的障碍物和空位置分别用 1 和 0 来表示。 示例 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]] * 输出:2 -解释: + 解释: * 3x3 网格的正中间有一个障碍物。 * 从左上角到右下角一共有 2 条不同的路径: - 1. 向右 -> 向右 -> 向下 -> 向下 - 2. 向下 -> 向下 -> 向右 -> 向右 + 1. 向右 -> 向右 -> 向下 -> 向下 + 2. 向下 -> 向下 -> 向右 -> 向右 示例 2: -![](https://img-blog.csdnimg.cn/20210111205857918.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210111205857918.png) * 输入:obstacleGrid = [[0,1],[0,0]] * 输出:1 提示: -* m == obstacleGrid.length -* n == obstacleGrid[i].length + +* m == obstacleGrid.length +* n == obstacleGrid[i].length * 1 <= m, n <= 100 * 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)的初始化情况同理。 @@ -126,13 +129,13 @@ for (int i = 1; i < m; i++) { 拿示例1来举例如题: -![63.不同路径II1](https://img-blog.csdnimg.cn/20210104114548983.png) +![63.不同路径II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114548983.png) 对应的dp table 如图: -![63.不同路径II2](https://img-blog.csdnimg.cn/20210104114610256.png) +![63.不同路径II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114610256.png) -如果这个图看不同,建议在理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下​!​ +如果这个图看不同,建议在理解一下递归公式,然后照着文章中说的遍历顺序,自己推导一下! 动规五部分分析完毕,对应C++代码如下: @@ -163,6 +166,7 @@ public: 同样我们给出空间优化版本: + ```CPP class Solution { public: @@ -208,7 +212,7 @@ public: ## 其他语言版本 -### Java +### Java ```java class Solution { @@ -246,11 +250,11 @@ class Solution { int m = obstacleGrid.length; int n = obstacleGrid[0].length; int[] dp = new int[n]; - + for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) { dp[j] = 1; } - + for (int i = 1; i < m; i++) { for (int j = 0; j < n; j++) { if (obstacleGrid[i][j] == 1) { @@ -316,7 +320,7 @@ class Solution: if obstacleGrid[0][j] == 1: break curr[j] = 1 - + for i in range(1, m): # 从第二行开始 for j in range(n): # 从第一列开始,因为第一列可能有障碍物 # 有障碍物处无法通行,状态就设成0 @@ -328,7 +332,7 @@ class Solution: curr[j] = curr[j] + curr[j - 1] # 隐含的状态更新 # dp[i][0] = dp[i - 1][0] - + return curr[n - 1] ``` @@ -369,26 +373,27 @@ func uniquePathsWithObstacles(obstacleGrid [][]int) int { ``` ### Javascript + ```Javascript var uniquePathsWithObstacles = function(obstacleGrid) { const m = obstacleGrid.length const n = obstacleGrid[0].length const dp = Array(m).fill().map(item => Array(n).fill(0)) - + for (let i = 0; i < m && obstacleGrid[i][0] === 0; ++i) { dp[i][0] = 1 } - + for (let i = 0; i < n && obstacleGrid[0][i] === 0; ++i) { dp[0][i] = 1 } - + for (let i = 1; i < m; ++i) { for (let j = 1; j < n; ++j) { dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i - 1][j] + dp[i][j - 1] } } - + return dp[m - 1][n - 1] }; @@ -545,9 +550,10 @@ int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obst ``` 空间优化版本: + ```c int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obstacleGridColSize){ - int m = obstacleGridSize; + int m = obstacleGridSize; int n = obstacleGridColSize[0]; int *dp = (int*)malloc(sizeof(int) * n); int i, j; diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index 14aeef01..ed9be618 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -102,7 +102,8 @@ dp[i]: 爬到第i层楼梯,有dp[i]种方法 举例当n为5的时候,dp table(dp数组)应该是这样的 -![70.爬楼梯](https://img-blog.csdnimg.cn/20210105202546299.png) + +![70.爬楼梯](https://code-thinking-1253855093.file.myqcloud.com/pics/20210105202546299.png) 如果代码出问题了,就把dp table 打印出来,看看究竟是不是和自己推导的一样。 diff --git a/problems/0072.编辑距离.md b/problems/0072.编辑距离.md index 8a877d20..0b49e3c6 100644 --- a/problems/0072.编辑距离.md +++ b/problems/0072.编辑距离.md @@ -169,7 +169,7 @@ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j; 可以看出dp[i][j]是依赖左方,上方和左上方元素的,如图: -![72.编辑距离](https://img-blog.csdnimg.cn/20210114162113131.jpg) +![72.编辑距离](https://code-thinking-1253855093.file.myqcloud.com/pics/20210114162113131.jpg) 所以在dp矩阵中一定是从左到右从上到下去遍历。 @@ -193,7 +193,7 @@ for (int i = 1; i <= word1.size(); i++) { 以示例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++代码如下: diff --git a/problems/0077.组合.md b/problems/0077.组合.md index 77ea5e80..9c6d481d 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -1,3 +1,4 @@ +

@@ -7,31 +8,32 @@ + # 第77题. 组合 [力扣题目链接](https://leetcode.cn/problems/combinations/ ) -给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 +给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 -示例: -输入: n = 4, k = 2 -输出: -[ - [2,4], - [3,4], - [2,3], - [1,2], - [1,3], - [1,4], -] +示例: +输入: n = 4, k = 2 +输出: +[ + [2,4], + [3,4], + [2,3], + [1,2], + [1,3], + [1,4], +] -# 算法公开课 +# 算法公开课 **《代码随想录》算法视频公开课:[带你学透回溯算法-组合问题(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv),[组合问题的剪枝操作](https://www.bilibili.com/video/BV1wi4y157er),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 -# 思路 +# 思路 本题是回溯法的经典题目。 @@ -39,6 +41,7 @@ 直接的解法当然是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。 代码如下: + ```CPP int n = 4; for (int i = 1; i <= n; i++) { @@ -66,7 +69,7 @@ for (int i = 1; i <= n; i++) { **此时就会发现虽然想暴力搜索,但是用for循环嵌套连暴力都写不出来!** -咋整? +咋整? 回溯搜索法来了,虽然回溯法也是暴力,但至少能写出来,不像for循环嵌套k层让人绝望。 @@ -86,7 +89,7 @@ for (int i = 1; i <= n; i++) { 那么我把组合问题抽象为如下树形结构: -![77.组合](https://img-blog.csdnimg.cn/20201123195223940.png) +![77.组合](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195223940.png) 可以看出这棵树,一开始集合是 1,2,3,4, 从左向右取数,取过的数,不再重复取。 @@ -94,7 +97,7 @@ for (int i = 1; i <= n; i++) { **每次从集合中选取元素,可选择的范围随着选择的进行而收缩,调整可选择的范围**。 -**图中可以发现n相当于树的宽度,k相当于树的深度**。 +**图中可以发现n相当于树的宽度,k相当于树的深度**。 那么如何在这个树上遍历,然后收集到我们要的结果集呢? @@ -107,7 +110,7 @@ for (int i = 1; i <= n; i++) { ## 回溯法三部曲 -* 递归函数的返回值以及参数 +* 递归函数的返回值以及参数 在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。 @@ -124,25 +127,25 @@ vector path; // 用来存放符合条件结果 然后还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。 -为什么要有这个startIndex呢? +为什么要有这个startIndex呢? **建议在[77.组合视频讲解](https://www.bilibili.com/video/BV1ti4y1L7cv)中,07:36的时候开始听,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来记录下一层递归,搜索的起始位置。 那么整体代码如下: ```cpp vector> result; // 存放符合条件结果的集合 vector path; // 用来存放符合条件单一结果 -void backtracking(int n, int k, int startIndex) +void backtracking(int n, int k, int 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保存起来,并终止本层递归。 @@ -163,21 +166,21 @@ if (path.size() == k) { } ``` -* 单层搜索的过程 +* 单层搜索的过程 回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。 -![77.组合1](https://img-blog.csdnimg.cn/20201123195242899.png) +![77.组合1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195242899.png) 如此我们才遍历完图中的这棵树。 -for循环每次从startIndex开始遍历,然后用path保存取到的节点i。 +for循环每次从startIndex开始遍历,然后用path保存取到的节点i。 代码如下: ```CPP for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历 - path.push_back(i); // 处理节点 + path.push_back(i); // 处理节点 backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始 path.pop_back(); // 回溯,撤销处理的节点 } @@ -201,7 +204,7 @@ private: return; } for (int i = startIndex; i <= n; i++) { - path.push_back(i); // 处理节点 + path.push_back(i); // 处理节点 backtracking(n, k, i + 1); // 递归 path.pop_back(); // 回溯,撤销处理的节点 } @@ -216,9 +219,10 @@ public: }; ``` -还记得我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的回溯法模板么? +还记得我们在[关于回溯算法,你该了解这些!](https://programmercarl.com/回溯算法理论基础.html)中给出的回溯法模板么? 如下: + ``` void backtracking(参数) { if (终止条件) { @@ -234,15 +238,15 @@ void backtracking(参数) { } ``` -**对比一下本题的代码,是不是发现有点像!** 所以有了这个模板,就有解题的大体方向,不至于毫无头绪。 +**对比一下本题的代码,是不是发现有点像!** 所以有了这个模板,就有解题的大体方向,不至于毫无头绪。 -## 总结 +## 总结 组合问题是回溯法解决的经典问题,我们开始的时候给大家列举一个很形象的例子,就是n为100,k为50的话,直接想法就需要50层for循环。 从而引出了回溯法就是解决这种k层for循环嵌套的问题。 -然后进一步把回溯法的搜索过程抽象为树形结构,可以直观的看出搜索的过程。 +然后进一步把回溯法的搜索过程抽象为树形结构,可以直观的看出搜索的过程。 接着用回溯法三部曲,逐步分析了函数参数、终止条件和单层搜索的过程。 @@ -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循环从第二个数开始遍历的话,都没有意义,都是无效遍历。 @@ -275,6 +279,7 @@ for (int i = startIndex; i <= n; i++) { **如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了**。 注意代码中i,就是for循环里选择的起始位置。 + ``` for (int i = startIndex; i <= n; i++) { ``` @@ -342,6 +347,7 @@ public: ### Java: + ```java class Solution { List> result = new ArrayList<>(); @@ -370,7 +376,7 @@ class Solution { } ``` -### Python +### Python ```python class Solution(object): @@ -417,6 +423,7 @@ class Solution: ``` 剪枝: + ```python class Solution: def combine(self, n: int, k: int) -> List[List[int]]: @@ -434,7 +441,8 @@ class Solution: return res ``` -### Go +### Go + ```Go var ( path []int @@ -452,7 +460,7 @@ func dfs(n int, k int, start int) { tmp := make([]int, k) copy(tmp, path) res = append(res, tmp) - return + return } for i := start; i <= n; i++ { // 从start开始,不往回走,避免出现重复组合 if n - i + 1 < k - len(path) { // 剪枝 @@ -465,9 +473,10 @@ func dfs(n int, k int, start int) { } ``` -### javascript +### javascript 剪枝: + ```javascript let result = [] let path = [] @@ -536,6 +545,7 @@ impl Solution { ``` 剪枝 + ```Rust impl Solution { fn backtracking(result: &mut Vec>, path: &mut Vec, n: i32, k: i32, start_index: i32) { @@ -561,6 +571,7 @@ impl Solution { ``` ### C + ```c int* path; int pathTop; @@ -615,6 +626,7 @@ int** combine(int n, int k, int* returnSize, int** returnColumnSizes){ ``` 剪枝: + ```c int* path; int pathTop; @@ -701,13 +713,14 @@ func combine(_ n: Int, _ k: Int) -> [[Int]] { ### Scala 暴力: + ```scala object Solution { import scala.collection.mutable // 导包 def combine(n: Int, k: Int): List[List[Int]] = { var result = mutable.ListBuffer[List[Int]]() // 存放结果集 var path = mutable.ListBuffer[Int]() //存放符合条件的结果 - + def backtracking(n: Int, k: Int, startIndex: Int): Unit = { if (path.size == k) { // 如果path的size == k就达到题目要求,添加到结果集,并返回 @@ -720,7 +733,7 @@ object Solution { path = path.take(path.size - 1) // 回溯完再删除掉刚刚添加的数字 } } - + backtracking(n, k, 1) // 执行回溯 result.toList // 最终返回result的List形式,return关键字可以省略 } @@ -743,7 +756,7 @@ object Solution { return } // 剪枝优化 - for (i <- startIndex to (n - (k - path.size) + 1)) { + for (i <- startIndex to (n - (k - path.size) + 1)) { path.append(i) // 先把数字添加进去 backtracking(n, k, i + 1) // 进行下一步回溯 path = path.take(path.size - 1) // 回溯完再删除掉刚刚添加的数字 diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md index 066e1b33..9736549c 100644 --- a/problems/0077.组合优化.md +++ b/problems/0077.组合优化.md @@ -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循环从第二个数开始遍历的话,都没有意义,都是无效遍历。 diff --git a/problems/0078.子集.md b/problems/0078.子集.md index b0c51d44..f26d0821 100644 --- a/problems/0078.子集.md +++ b/problems/0078.子集.md @@ -48,7 +48,7 @@ 以示例中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& 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) 剩余集合为空的时候,就是叶子节点。 diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md index a02161aa..1a9f8fda 100644 --- a/problems/0090.子集II.md +++ b/problems/0090.子集II.md @@ -42,7 +42,7 @@ 用示例中的[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,因为同一树枝上元素的集合才是唯一子集! diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index 1f99a7a5..9d4d5918 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -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) 然后就是递归和回溯的过程: diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index 99a4b8dd..9f41906d 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -1,9 +1,11 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 96.不同的二叉搜索树 [力扣题目链接](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有两棵树,这个是很直观的。 -![96.不同的二叉搜索树1](https://img-blog.csdnimg.cn/20210107093129889.png) +![96.不同的二叉搜索树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210107093129889.png) 来看看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数组状态如图: -![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的时候,画图已经比较麻烦了。 @@ -168,7 +170,8 @@ public: ## 其他语言版本 -### Java +### Java + ```Java class Solution { public int numTrees(int n) { @@ -190,6 +193,7 @@ class Solution { ``` ### Python + ```python class Solution: def numTrees(self, n: int) -> int: @@ -202,6 +206,7 @@ class Solution: ``` ### Go + ```Go func numTrees(n int)int{ dp := make([]int, n+1) @@ -216,6 +221,7 @@ func numTrees(n int)int{ ``` ### Javascript + ```Javascript const numTrees =(n) => { let dp = new Array(n+1).fill(0); @@ -241,7 +247,7 @@ function numTrees(n: number): number { dp[0]: -1; 无意义; dp[1]: 1; ... - dp[i]: 2 * dp[i - 1] + + dp[i]: 2 * dp[i - 1] + (dp[1] * dp[i - 2] + dp[2] * dp[i - 3] + ... + dp[i - 2] * dp[1]); 从1加到i-2 */ const dp: number[] = []; @@ -282,7 +288,7 @@ impl Solution { int *initDP(int n) { int *dp = (int *)malloc(sizeof(int) * (n + 1)); int i; - for(i = 0; i <= n; ++i) + for(i = 0; i <= n; ++i) dp[i] = 0; return dp; } diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md index 93c2272c..95afe680 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -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: -![二叉搜索树](https://img-blog.csdnimg.cn/20200812191501419.png) +![二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000824.png) 节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了! diff --git a/problems/0101.对称二叉树.md b/problems/0101.对称二叉树.md index 1c2812fa..b75e9ff2 100644 --- a/problems/0101.对称二叉树.md +++ b/problems/0101.对称二叉树.md @@ -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) 那么遍历的顺序应该是什么样的呢? diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 5c6ce488..26f0ae2d 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -1,9 +1,11 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 二叉树层序遍历登场! 《代码随想录》算法视频公开课:[讲透二叉树的层序遍历 | 广度优先搜索 | LeetCode:102.二叉树的层序遍历](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 # 递归法 class Solution { @@ -108,7 +111,7 @@ public: }; ``` -java: +java: ```Java // 102.二叉树的层序遍历 @@ -168,7 +171,6 @@ python3代码: ```python - class Solution: """二叉树层序遍历迭代解法""" @@ -176,10 +178,10 @@ class Solution: results = [] if not root: return results - + from collections import deque que = deque([root]) - + while que: size = len(que) result = [] @@ -194,6 +196,7 @@ class Solution: return results ``` + ```python # 递归法 class Solution: @@ -209,7 +212,7 @@ class Solution: return res ``` -go: +go: ```go /** @@ -252,9 +255,9 @@ func levelOrder(root *TreeNode) [][]int { } queue := list.New() queue.PushBack(root) - + var tmpArr []int - + for queue.Len() > 0 { length := queue.Len() //保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) for i := 0; i < length; i++ { @@ -270,7 +273,7 @@ func levelOrder(root *TreeNode) [][]int { res = append(res, tmpArr) //放入结果集 tmpArr = []int{} //清空层的数据 } - + return res } @@ -279,7 +282,7 @@ func levelOrder(root *TreeNode) [][]int { */ func levelOrder(root *TreeNode) (res [][]int) { if root == nil { - return + return } curLevel := []*TreeNode{root} // 存放当前层节点 @@ -318,7 +321,7 @@ var levelOrder = function(root) { while(queue.length !== 0) { // 记录当前层级节点数 let length = queue.length; - //存放每一层的节点 + //存放每一层的节点 let curLevel = []; for(let i = 0;i < length; i++) { let node = queue.shift(); @@ -387,7 +390,9 @@ func levelOrder(_ root: TreeNode?) -> [[Int]] { return result } ``` + Scala: + ```scala // 102.二叉树的层序遍历 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 @@ -498,10 +504,10 @@ class Solution: results = [] if not root: return results - + from collections import deque que = deque([root]) - + while que: result = [] for _ in range(len(que)): @@ -572,7 +578,7 @@ class Solution { public List> levelOrderBottom(TreeNode root) { // 利用链表可以进行 O(1) 头部插入, 这样最后答案不需要再反转 LinkedList> ans = new LinkedList<>(); - + Queue q = new LinkedList<>(); if (root != null) q.offer(root); @@ -584,9 +590,9 @@ class Solution { for (int i = 0; i < size; i ++) { TreeNode node = q.poll(); - + temp.add(node.val); - + if (node.left != null) q.offer(node.left); if (node.right != null) q.offer(node.right); @@ -616,7 +622,7 @@ func levelOrderBottom(root *TreeNode) [][]int { return res } queue.PushBack(root) - + for queue.Len() > 0 { length := queue.Len() tmp := []int{} @@ -632,12 +638,12 @@ func levelOrderBottom(root *TreeNode) [][]int { } res=append(res, tmp) } - + //反转结果集 for i:=0; i [[Int]] { Scala: + ```scala // 107.二叉树的层次遍历II 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 @@ -817,7 +825,7 @@ class Solution: def rightSideView(self, root: TreeNode) -> List[int]: if not root: return [] - + # deque来自collections模块,不在力扣平台时,需要手动写入 # 'from collections import deque' 导入 # deque相比list的好处是,list的pop(0)是O(n)复杂度,deque的popleft()是O(1)复杂度 @@ -837,15 +845,15 @@ class Solution: quene.append(node.left) if node.right: quene.append(node.right) - + return out_list - + # 执行用时:36 ms, 在所有 Python3 提交中击败了89.47%的用户 # 内存消耗:14.6 MB, 在所有 Python3 提交中击败了96.65%的用户 ``` -Java: +Java: ```java // 199.二叉树的右视图 @@ -889,10 +897,9 @@ public class N0199 { } ``` -go: +go: ```GO - /** 199. 二叉树的右视图 */ @@ -932,7 +939,7 @@ var rightSideView = function(root) { //二叉树右视图 只需要把每一层最后一个节点存储到res数组 let res = [], queue = []; queue.push(root); - + while(queue.length && root!==null) { // 记录当前层级节点个数 let length = queue.length; @@ -946,7 +953,7 @@ var rightSideView = function(root) { node.right && queue.push(node.right); } } - + return res; }; ``` @@ -997,6 +1004,7 @@ func rightSideView(_ root: TreeNode?) -> [Int] { ``` Scala: + ```scala // 199.二叉树的右视图 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) 思路: @@ -1103,10 +1111,10 @@ class Solution: results = [] if not root: return results - + from collections import deque que = deque([root]) - + while que: size = len(que) sum_ = 0 @@ -1124,8 +1132,7 @@ class Solution: java: -```java - +```java // 637. 二叉树的层平均值 public class N0637 { @@ -1210,7 +1217,7 @@ var averageOfLevels = function(root) { //层级平均值 let res = [], queue = []; queue.push(root); - + while(queue.length && root!==null) { //每一层节点个数 let length = queue.length; @@ -1225,7 +1232,7 @@ var averageOfLevels = function(root) { //每一层的平均值存入数组res res.push(sum/length); } - + return res; }; ``` @@ -1280,7 +1287,9 @@ func averageOfLevels(_ root: TreeNode?) -> [Double] { return result } ``` + Scala: + ```scala // 637.二叉树的层平均值 object Solution { @@ -1346,7 +1355,7 @@ impl Solution { 例如,给定一个 3叉树 : -![429. N叉树的层序遍历](https://img-blog.csdnimg.cn/20210203151439168.png) +![429. N叉树的层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203151439168.png) 返回其层序遍历: @@ -1399,10 +1408,10 @@ class Solution: results = [] if not root: return results - + from collections import deque que = deque([root]) - + while que: result = [] for _ in range(len(que)): @@ -1426,16 +1435,16 @@ class Solution: def traversal(root,depth): if len(result)==depth:result.append([]) result[depth].append(root.val) - if root.children: + if root.children: for i in range(len(root.children)):traversal(root.children[i],depth+1) - + traversal(root,0) - return result + return result ``` + java: -```java - +```java // 429. N 叉树的层序遍历 public class N0429 { /** @@ -1520,7 +1529,7 @@ func levelOrder(root *Node) [][]int { } res = append(res, tmp) } - + return res } ``` @@ -1532,7 +1541,7 @@ var levelOrder = function(root) { //每一层可能有2个以上,所以不再使用node.left node.right let res = [], queue = []; queue.push(root); - + while(queue.length && root!==null) { //记录每一层节点个数还是和二叉树一致 let length = queue.length; @@ -1541,7 +1550,7 @@ var levelOrder = function(root) { while(length--) { let node = queue.shift(); curLevel.push(node.val); - + //这里不再是 ndoe.left node.right 而是循坏node.children for(let item of node.children){ item && queue.push(item); @@ -1549,7 +1558,7 @@ var levelOrder = function(root) { } res.push(curLevel); } - + return res; }; ``` @@ -1602,6 +1611,7 @@ func levelOrder(_ root: Node?) -> [[Int]] { ``` Scala: + ```scala // 429.N叉树的层序遍历 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 @@ -1734,6 +1745,7 @@ class Solution: out_list.append(max(in_list)) return out_list ``` + java代码: ```java @@ -1754,7 +1766,7 @@ class Solution { if(node.right != null) queue.offer(node.right); } result.add(max); - } + } return result; } } @@ -1811,8 +1823,8 @@ var largestValues = function(root) { //使用层序遍历 let res = [], queue = []; queue.push(root); - - while(root !== null && queue.length) { + + while(root !== null && queue.length) { //设置max初始值就是队列的第一个元素 let max = queue[0].val; let length = queue.length; @@ -1825,7 +1837,7 @@ var largestValues = function(root) { //把每一层的最大值放到res数组 res.push(max); } - + return res; }; ``` @@ -1884,6 +1896,7 @@ func largestValues(_ root: TreeNode?) -> [Int] { ``` Scala: + ```scala // 515.在每个树行中找最大值 object Solution { @@ -1959,9 +1972,9 @@ struct Node { 填充它的每个 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) 思路: @@ -2009,24 +2022,24 @@ class Solution { public Node connect(Node root) { Queue tmpQueue = new LinkedList(); if (root != null) tmpQueue.add(root); - + while (tmpQueue.size() != 0){ int size = tmpQueue.size(); - + Node cur = tmpQueue.poll(); if (cur.left != null) tmpQueue.add(cur.left); if (cur.right != null) tmpQueue.add(cur.right); - + for (int index = 1; index < size; index++){ Node next = tmpQueue.poll(); if (next.left != null) tmpQueue.add(next.left); if (next.right != null) tmpQueue.add(next.right); - + cur.next = next; cur = next; } } - + return root; } } @@ -2067,6 +2080,7 @@ class Solution: first = first.left # 从本层扩展到下一层 return root ``` + go: ```GO @@ -2108,8 +2122,8 @@ func connect(root *Node) *Node { ``` JavaScript: -```javascript +```javascript /** * // Definition for a Node. * function Node(val, left, right, next) { @@ -2142,6 +2156,7 @@ var connect = function(root) { }; ``` + TypeScript: ```typescript @@ -2200,6 +2215,7 @@ func connect(_ root: Node?) -> Node? { ``` Scala: + ```scala // 116.填充每个节点的下一个右侧节点指针 object Solution { @@ -2228,6 +2244,7 @@ object Solution { } } ``` + # 117.填充每个节点的下一个右侧节点指针II [力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) @@ -2284,7 +2301,7 @@ class Solution { int size = queue.size(); Node node = null; Node nodePre = null; - + for (int i = 0; i < size; i++) { if (i == 0) { nodePre = queue.poll(); // 取出本层头一个节点 @@ -2307,6 +2324,7 @@ class Solution { } } ``` + python代码: ```python @@ -2329,6 +2347,7 @@ class Solution: return root ``` + go: ```GO @@ -2369,6 +2388,7 @@ func connect(root *Node) *Node { ``` JavaScript: + ```javascript /** * // Definition for a Node. @@ -2401,6 +2421,7 @@ var connect = function(root) { return root; }; ``` + TypeScript: ```typescript @@ -2459,6 +2480,7 @@ func connect(_ root: Node?) -> Node? { ``` Scala: + ```scala // 117.填充每个节点的下一个右侧节点指针II object Solution { @@ -2487,6 +2509,7 @@ object Solution { } } ``` + # 104.二叉树的最大深度 [力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) @@ -2501,7 +2524,7 @@ object Solution { 给定二叉树 [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 。 @@ -2511,7 +2534,7 @@ object Solution { 在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示: -![层序遍历](https://img-blog.csdnimg.cn/20200810193056585.png) +![层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20200810193056585-20230310134854803.png) 所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 @@ -2540,7 +2563,8 @@ public: }; ``` -Java: +Java: + ```Java class Solution { public int maxDepth(TreeNode root) { @@ -2566,12 +2590,13 @@ class Solution { ``` Python: + ```python 3 class Solution: def maxDepth(self, root: TreeNode) -> int: if root == None: return 0 - + queue_ = [root] depth = 0 while queue_: @@ -2583,7 +2608,7 @@ class Solution: if cur.left: queue_.append(cur.left) if cur.right: queue_.append(cur.right) depth += 1 - + return depth ``` @@ -2623,6 +2648,7 @@ func maxDepth(root *TreeNode) int { ``` JavaScript: + ```javascript /** * Definition for a binary tree node. @@ -2700,6 +2726,7 @@ func maxDepth(_ root: TreeNode?) -> Int { ``` Scala: + ```scala // 104.二叉树的最大深度 object Solution { @@ -2789,7 +2816,8 @@ public: }; ``` -Java: +Java: + ```java class Solution { public int minDepth(TreeNode root){ @@ -2838,7 +2866,7 @@ class Solution: queue_ = [(root,1)] while queue_: cur, depth = queue_.pop(0) - + if cur.left == None and cur.right == None: return depth #先左子节点,由于左子节点没有孩子,则就是这一层了 @@ -2884,12 +2912,13 @@ func minDepth(root *TreeNode) int { } ans++//记录层数 } - + return ans+1 } ``` JavaScript: + ```javascript /** * Definition for a binary tree node. @@ -2972,6 +3001,7 @@ func minDepth(_ root: TreeNode?) -> Int { ``` Scala: + ```scala // 111.二叉树的最小深度 object Solution { diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md index e54221db..b95e39c8 100644 --- a/problems/0104.二叉树的最大深度.md +++ b/problems/0104.二叉树的最大深度.md @@ -18,7 +18,8 @@ 示例: 给定二叉树 [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 。 @@ -169,7 +170,8 @@ public: 在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示: -![层序遍历](https://img-blog.csdnimg.cn/20200810193056585.png) + +![层序遍历](https://code-thinking-1253855093.file.myqcloud.com/pics/20200810193056585.png) 所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。 @@ -213,7 +215,7 @@ public: 例如,给定一个 3叉树 : -![559.n叉树的最大深度](https://img-blog.csdnimg.cn/2021020315313214.png) +![559.n叉树的最大深度](https://code-thinking-1253855093.file.myqcloud.com/pics/2021020315313214.png) 我们应返回其最大深度,3。 diff --git a/problems/0106.从中序与后序遍历序列构造二叉树.md b/problems/0106.从中序与后序遍历序列构造二叉树.md index 9c563619..adb374f9 100644 --- a/problems/0106.从中序与后序遍历序列构造二叉树.md +++ b/problems/0106.从中序与后序遍历序列构造二叉树.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ 看完本文,可以一起解决如下两道题目 * 106.从中序与后序遍历序列构造二叉树 @@ -21,11 +23,11 @@ 例如,给出 -* 中序遍历 inorder = [9,3,15,20,7] +* 中序遍历 inorder = [9,3,15,20,7] * 后序遍历 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了,每次用下标索引来分割) ### C++优化版本 + ```CPP class Solution { private: @@ -397,7 +400,7 @@ public: }; ``` -## Python +## Python # 105.从前序与中序遍历序列构造二叉树 @@ -411,11 +414,11 @@ public: 例如,给出 -前序遍历 preorder = [3,9,20,15,7] +前序遍历 preorder = [3,9,20,15,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]。 @@ -601,7 +604,7 @@ class Solution { return findNode(inorder, 0, inorder.length, postorder,0, postorder.length); // 前闭后开 } - + public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) { // 参数里的范围都是前闭后开 if (inBegin >= inEnd || postBegin >= postEnd) { // 不满足左闭右开,说明没有元素,返回空树 @@ -642,7 +645,7 @@ class Solution { int rootIndex = map.get(preorder[preBegin]); // 找到前序遍历的第一个元素在中序遍历中的位置 TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点 int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定前序数列的个数 - root.left = findNode(preorder, preBegin + 1, preBegin + lenOfLeft + 1, + root.left = findNode(preorder, preBegin + 1, preBegin + lenOfLeft + 1, inorder, inBegin, rootIndex); root.right = findNode(preorder, preBegin + lenOfLeft + 1, preEnd, inorder, rootIndex + 1, inEnd); @@ -652,18 +655,19 @@ class Solution { } ``` -## Python +## Python + ```python class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]: # 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件 if not postorder: - return + return # 第二步: 后序遍历的最后一个就是当前的中间节点 root_val = postorder[-1] root = TreeNode(root_val) - + # 第三步: 找切割点. root_index = inorder.index(root_val) @@ -672,7 +676,7 @@ class Solution: right_inorder = inorder[root_index + 1:] # 第五步: 切割postorder数组. 得到postorder数组的左,右半边. - # ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的. + # ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的. left_postorder = postorder[:len(left_inorder)] right_postorder = postorder[len(left_inorder): len(postorder) - 1] @@ -682,7 +686,7 @@ class Solution: root.right = self.buildTree(right_inorder, right_postorder) # 第七步: 返回答案 - return root + return root ``` 105.从前序与中序遍历序列构造二叉树 @@ -691,22 +695,22 @@ class Solution: class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: # 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件 - if not preorder: + if not preorder: return None - # 第二步: 前序遍历的第一个就是当前的中间节点. + # 第二步: 前序遍历的第一个就是当前的中间节点. root_val = preorder[0] root = TreeNode(root_val) - # 第三步: 找切割点. + # 第三步: 找切割点. separator_idx = inorder.index(root_val) - # 第四步: 切割inorder数组. 得到inorder数组的左,右半边. + # 第四步: 切割inorder数组. 得到inorder数组的左,右半边. inorder_left = inorder[:separator_idx] inorder_right = inorder[separator_idx + 1:] # 第五步: 切割preorder数组. 得到preorder数组的左,右半边. - # ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的. + # ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的. preorder_left = preorder[1:1 + len(inorder_left)] preorder_right = preorder[1 + len(inorder_left):] @@ -723,22 +727,22 @@ class Solution: class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: # 第一步: 特殊情况讨论: 树为空. (递归终止条件) - if not postorder: + if not postorder: return None - # 第二步: 后序遍历的最后一个就是当前的中间节点. + # 第二步: 后序遍历的最后一个就是当前的中间节点. root_val = postorder[-1] root = TreeNode(root_val) - # 第三步: 找切割点. + # 第三步: 找切割点. separator_idx = inorder.index(root_val) - # 第四步: 切割inorder数组. 得到inorder数组的左,右半边. + # 第四步: 切割inorder数组. 得到inorder数组的左,右半边. inorder_left = inorder[:separator_idx] inorder_right = inorder[separator_idx + 1:] # 第五步: 切割postorder数组. 得到postorder数组的左,右半边. - # ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的. + # ⭐️ 重点1: 中序数组大小一定跟后序数组大小是相同的. postorder_left = postorder[:len(inorder_left)] postorder_right = postorder[len(inorder_left): len(postorder) - 1] @@ -746,7 +750,7 @@ class Solution: root.left = self.buildTree(inorder_left, postorder_left) root.right = self.buildTree(inorder_right, postorder_right) - return root + return root ``` ## Go @@ -786,7 +790,7 @@ func rebuild(inorder []int, postorder []int, rootIdx int, l, r int) *TreeNode { rootIn := hash[rootV] // 找到根节点在对应的中序数组中的位置 root := &TreeNode{Val : rootV} // 构造根节点 // 重建左节点和右节点 - root.Left = rebuild(inorder, postorder, rootIdx-(r-rootIn)-1, l, rootIn-1) + root.Left = rebuild(inorder, postorder, rootIdx-(r-rootIn)-1, l, rootIn-1) root.Right = rebuild(inorder, postorder, rootIdx-1, rootIn+1, r) return root } @@ -830,7 +834,7 @@ func build(pre []int, in []int, root int, l, r int) *TreeNode { -## JavaScript +## JavaScript ```javascript var buildTree = function(inorder, postorder) { @@ -1031,7 +1035,7 @@ struct TreeNode* buildTree(int* preorder, int preorderSize, int* inorder, int in // 4.根据中序遍历数组左右数组的各子大小切割前序遍历数组。也分为左右数组 int* leftPreorder = preorder+1; - int* rightPreorder = preorder + 1 + leftNum; + int* rightPreorder = preorder + 1 + leftNum; // 5.递归进入左右数组,将返回的结果作为根结点的左右孩子 root->left = buildTree(leftPreorder, leftNum, leftInorder, leftNum); @@ -1056,26 +1060,26 @@ class Solution { inorderBegin: 0, inorderEnd: inorder.count) } - + func helper(preorder: [Int], preorderBegin: Int, preorderEnd: Int, inorder: [Int], inorderBegin: Int, inorderEnd: Int) -> TreeNode? { if preorderBegin == preorderEnd { return nil } - + // 前序遍历数组的第一个元素作为分割点 let rootValue = preorder[preorderBegin] let root = TreeNode(rootValue) - - + + if preorderEnd - preorderBegin == 1 { return root } - + var index = 0 // 从中序遍历数组中找到根节点的下标 if let ind = inorder.firstIndex(of: rootValue) { index = ind } - + // 递归 root.left = helper(preorder: preorder, preorderBegin: preorderBegin + 1, @@ -1102,28 +1106,28 @@ class Solution_0106 { if postorderEnd - postorderBegin < 1 { return nil } - + // 后序遍历数组的最后一个元素作为分割点 let rootValue = postorder[postorderEnd - 1] let root = TreeNode(rootValue) - + if postorderEnd - postorderBegin == 1 { return root } - + // 从中序遍历数组中找到根节点的下标 var delimiterIndex = 0 if let index = inorder.firstIndex(of: rootValue) { delimiterIndex = index } - + root.left = buildTree(inorder: inorder, inorderBegin: inorderBegin, inorderEnd: delimiterIndex, postorder: postorder, postorderBegin: postorderBegin, postorderEnd: postorderBegin + (delimiterIndex - inorderBegin)) - + root.right = buildTree(inorder: inorder, inorderBegin: delimiterIndex + 1, inorderEnd: inorderEnd, diff --git a/problems/0108.将有序数组转换为二叉搜索树.md b/problems/0108.将有序数组转换为二叉搜索树.md index f0455a83..89778421 100644 --- a/problems/0108.将有序数组转换为二叉搜索树.md +++ b/problems/0108.将有序数组转换为二叉搜索树.md @@ -17,7 +17,8 @@ 示例: -![108.将有序数组转换为二叉搜索树](https://img-blog.csdnimg.cn/20201022164420763.png) + +![108.将有序数组转换为二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20201022164420763.png) # 算法公开课 diff --git a/problems/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md index 492e25d9..804c95eb 100644 --- a/problems/0110.平衡二叉树.md +++ b/problems/0110.平衡二叉树.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ > 求高度还是求深度,你搞懂了不? # 110.平衡二叉树 @@ -13,13 +15,13 @@ 给定一个二叉树,判断它是否是高度平衡的二叉树。 -本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 +本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。 示例 1: 给定二叉树 [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 。 @@ -27,7 +29,7 @@ 给定二叉树 [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 。 @@ -45,7 +47,7 @@ 但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为准(毕竟要在这上面刷题)。 @@ -125,7 +127,7 @@ public: 1. 明确递归函数的参数和返回值 -参数:当前传入节点。 +参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。 那么如何标记左右子树是否差值大于1呢? @@ -496,9 +498,10 @@ class Solution { } ``` -### Python +### Python 递归法: + ```python # Definition for a binary tree node. # class TreeNode: @@ -512,7 +515,7 @@ class Solution: return True else: return False - + def get_height(self, root: TreeNode) -> int: # Base Case if not root: @@ -531,6 +534,7 @@ class Solution: ``` 迭代法: + ```python class Solution: def isBalanced(self, root: Optional[TreeNode]) -> bool: @@ -557,9 +561,10 @@ class Solution: ### Go + ```Go func isBalanced(root *TreeNode) bool { - h := getHeight(root) + h := getHeight(root) if h == -1 { return false } @@ -588,7 +593,9 @@ func max(a, b int) int { ``` ### JavaScript -递归法: + +递归法: + ```javascript var isBalanced = function(root) { //还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1 @@ -614,6 +621,7 @@ var isBalanced = function(root) { ``` 迭代法: + ```javascript // 获取当前节点的高度 var getHeight = function (curNode) { @@ -644,7 +652,7 @@ var isBalanced = function (root) { let queue = [root]; while (queue.length) { let node = queue[queue.length - 1]; // 取出栈顶 - queue.pop(); + queue.pop(); if (Math.abs(getHeight(node.left) - getHeight(node.right)) > 1) { return false; } @@ -676,6 +684,7 @@ function isBalanced(root: TreeNode | null): boolean { ### C 递归法: + ```c int getDepth(struct TreeNode* node) { //如果结点不存在,返回0 @@ -706,6 +715,7 @@ bool isBalanced(struct TreeNode* root) { ``` 迭代法: + ```c //计算结点深度 int getDepth(struct TreeNode* node) { @@ -717,7 +727,7 @@ int getDepth(struct TreeNode* node) { stack[stackTop++] = node; int result = 0; int depth = 0; - + //当栈中有元素时,进行迭代遍历 while(stackTop) { //取出栈顶元素 @@ -741,7 +751,7 @@ int getDepth(struct TreeNode* node) { tempNode = stack[--stackTop]; depth--; } - } + } return result; } @@ -750,11 +760,11 @@ bool isBalanced(struct TreeNode* root){ //开辟栈空间 struct TreeNode** stack = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 10000); int stackTop = 0; - + //若根节点不存在,返回True if(!root) return 1; - + //将根节点入栈 stack[stackTop++] = root; //当栈中有元素时,进行遍历 @@ -764,7 +774,7 @@ bool isBalanced(struct TreeNode* root){ //计算左右子树的深度 int diff = getDepth(node->right) - getDepth(node->left); //若深度的绝对值大于1,返回False - if(diff > 1 || diff < -1) + if(diff > 1 || diff < -1) return 0; //如果栈顶结点有左右结点,将左右结点入栈 if(node->left) @@ -780,6 +790,7 @@ bool isBalanced(struct TreeNode* root){ ### Swift: >递归 + ```swift func isBalanced(_ root: TreeNode?) -> Bool { // -1 已经不是平衡二叉树 diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index cbe7b7ec..de36c6f2 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -21,7 +21,8 @@ 给定二叉树 [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. @@ -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) 如果这么求的话,没有左孩子的分支会算为最短深度。 diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index b1f01336..e412d38e 100644 --- a/problems/0112.路径总和.md +++ b/problems/0112.路径总和.md @@ -1,18 +1,20 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 112. 路径总和 [力扣题目链接](https://leetcode.cn/problems/path-sum/) 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 -说明: 叶子节点是指没有子节点的节点。 +说明: 叶子节点是指没有子节点的节点。 -示例:  +示例: 给定如下二叉树,以及目标和 sum = 22, ![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类型表示。 @@ -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) 为了尽可能的把细节体现出来,我写出如下代码(**这份代码并不简洁,但是逻辑非常清晰**) @@ -303,6 +305,7 @@ public: ## java ### 0112.路径总和 + ```java class solution { public boolean haspathsum(treenode root, int targetsum) { @@ -333,9 +336,9 @@ class solution { // lc112 简洁方法 class solution { public boolean haspathsum(treenode root, int targetsum) { - + if (root == null) return false; // 为空退出 - + // 叶子节点判断是否符合 if (root.left == null && root.right == null) return root.val == targetsum; @@ -344,7 +347,9 @@ class solution { } } ``` + 迭代 + ```java class solution { public boolean haspathsum(treenode root, int targetsum) { @@ -363,7 +368,7 @@ class solution { // 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true if(node.left == null && node.right == null && sum == targetsum) { return true; - } + } // 右节点,压进去一个节点的时候,将该节点的路径数值也记录下来 if(node.right != null){ stack1.push(node.right); @@ -377,7 +382,7 @@ class solution { } } return false; - } + } } ``` @@ -447,6 +452,7 @@ class Solution { ### 0112.路径总和 **递归** + ```python class solution: def haspathsum(self, root: treenode, targetsum: int) -> bool: @@ -472,55 +478,57 @@ class solution: ``` **迭代 - 层序遍历** + ```python class solution: def haspathsum(self, root: treenode, targetsum: int) -> bool: - if not root: + if not root: return false stack = [] # [(当前节点,路径数值), ...] stack.append((root, root.val)) - while stack: + while stack: cur_node, path_sum = stack.pop() - if not cur_node.left and not cur_node.right and path_sum == targetsum: + if not cur_node.left and not cur_node.right and path_sum == targetsum: return true - if cur_node.right: - stack.append((cur_node.right, path_sum + cur_node.right.val)) + if cur_node.right: + stack.append((cur_node.right, path_sum + cur_node.right.val)) - if cur_node.left: + if cur_node.left: stack.append((cur_node.left, path_sum + cur_node.left.val)) return false ``` -### 0113.路径总和-ii +### 0113.路径总和-ii **递归** + ```python class solution: def pathsum(self, root: treenode, targetsum: int) -> list[list[int]]: - def traversal(cur_node, remain): + def traversal(cur_node, remain): if not cur_node.left and not cur_node.right: - if remain == 0: + if remain == 0: result.append(path[:]) return - if cur_node.left: + if cur_node.left: path.append(cur_node.left.val) traversal(cur_node.left, remain-cur_node.left.val) path.pop() - if cur_node.right: + if cur_node.right: path.append(cur_node.right.val) traversal(cur_node.right, remain-cur_node.right.val) path.pop() result, path = [], [] - if not root: + if not root: return [] path.append(root.val) traversal(root, targetsum - root.val) @@ -528,6 +536,7 @@ class solution: ``` **迭代法,用第二个队列保存目前的总和与路径** + ```python class Solution: def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]: @@ -569,7 +578,7 @@ func hasPathSum(root *TreeNode, targetSum int) bool { if root == nil { return false } - + targetSum -= root.Val // 将targetSum在遍历每层的时候都减去本层节点的值 if root.Left == nil && root.Right == nil && targetSum == 0 { // 如果剩余的targetSum为0, 则正好就是符合的结果 return true @@ -602,10 +611,10 @@ func traverse(node *TreeNode, result *[][]int, currPath *[]int, targetSum int) { targetSum -= node.Val // 将targetSum在遍历每层的时候都减去本层节点的值 *currPath = append(*currPath, node.Val) // 把当前节点放到路径记录里 - + if node.Left == nil && node.Right == nil && targetSum == 0 { // 如果剩余的targetSum为0, 则正好就是符合的结果 // 不能直接将currPath放到result里面, 因为currPath是共享的, 每次遍历子树时都会被修改 - pathCopy := make([]int, len(*currPath)) + pathCopy := make([]int, len(*currPath)) for i, element := range *currPath { pathCopy[i] = element } @@ -623,6 +632,7 @@ func traverse(node *TreeNode, result *[][]int, currPath *[]int, targetSum int) { ### 0112.路径总和 **递归** + ```javascript /** * @param {treenode} root @@ -639,7 +649,7 @@ let haspathsum = function (root, targetsum) { // 左(空节点不遍历).遇到叶子节点返回true,则直接返回true if (node.left && traversal(node.left, cnt - node.left.val)) return true; - // 右(空节点不遍历) + // 右(空节点不遍历) if (node.right && traversal(node.right, cnt - node.right.val)) return true; return false; }; @@ -652,7 +662,9 @@ let haspathsum = function (root, targetsum) { // return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val); }; ``` + **迭代** + ```javascript let hasPathSum = function(root, targetSum) { if(root === null) return false; @@ -681,9 +693,10 @@ let hasPathSum = function(root, targetSum) { }; ``` -### 0113.路径总和-ii +### 0113.路径总和-ii **递归** + ```javascript let pathsum = function (root, targetsum) { // 递归法 @@ -715,7 +728,9 @@ let pathsum = function (root, targetsum) { return res; }; ``` + **递归 精简版** + ```javascript var pathsum = function(root, targetsum) { //递归方法 @@ -739,7 +754,9 @@ var pathsum = function(root, targetsum) { return resPath; }; ``` + **迭代** + ```javascript let pathSum = function(root, targetSum) { if(root === null) return []; @@ -905,7 +922,7 @@ func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool { guard let root = root else { return false } - + return traversal(root, targetSum - root.val) } @@ -913,52 +930,54 @@ func traversal(_ cur: TreeNode?, _ count: Int) -> Bool { if cur?.left == nil && cur?.right == nil && count == 0 { return true } - + if cur?.left == nil && cur?.right == nil { return false } - + if let leftNode = cur?.left { if traversal(leftNode, count - leftNode.val) { return true } } - + if let rightNode = cur?.right { if traversal(rightNode, count - rightNode.val) { return true } } - + return false } ``` + **迭代** + ```swift func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool { guard let root = root else { return false } - + var stack = Array<(TreeNode, Int)>() stack.append((root, root.val)) - + while !stack.isEmpty { let node = stack.removeLast() - + if node.0.left == nil && node.0.right == nil && targetSum == node.1 { return true } - + if let rightNode = node.0.right { stack.append((rightNode, node.1 + rightNode.val)) } - + if let leftNode = node.0.left { stack.append((leftNode, node.1 + leftNode.val)) } } - + return false } ``` @@ -989,12 +1008,12 @@ func traversal(_ cur: TreeNode?, count: Int) { result.append(path) return } - + // 遇到叶子节点而没有找到合适的边,直接返回 if cur?.left == nil && cur?.right == nil{ return } - + if let leftNode = cur?.left { path.append(leftNode.val) count -= leftNode.val @@ -1002,7 +1021,7 @@ func traversal(_ cur: TreeNode?, count: Int) { count += leftNode.val// 回溯 path.removeLast()// 回溯 } - + if let rightNode = cur?.right { path.append(rightNode.val) count -= rightNode.val @@ -1015,8 +1034,10 @@ func traversal(_ cur: TreeNode?, count: Int) { ``` ## C + > 0112.路径总和 -递归法: +> 递归法: + ```c bool hasPathSum(struct TreeNode* root, int targetSum){ // 递归结束条件:若当前节点不存在,返回false @@ -1025,13 +1046,14 @@ bool hasPathSum(struct TreeNode* root, int targetSum){ // 若当前节点为叶子节点,且targetSum-root的值为0。(当前路径上的节点值的和满足条件)返回true if(!root->right && !root->left && targetSum == root->val) return true; - + // 查看左子树和右子树的所有节点是否满足条件 return hasPathSum(root->right, targetSum - root->val) || hasPathSum(root->left, targetSum - root->val); } ``` 迭代法: + ```c // 存储一个节点以及当前的和 struct Pair { @@ -1056,7 +1078,7 @@ bool hasPathSum(struct TreeNode* root, int targetSum){ // 若栈顶元素为叶子节点,且和为targetSum时,返回true if(!topPair.node->left && !topPair.node->right && topPair.sum == targetSum) return true; - + // 若当前栈顶节点有左右孩子,计算和并入栈 if(topPair.node->left) { struct Pair newPair = {topPair.node->left, topPair.sum + topPair.node->left->val}; @@ -1070,7 +1092,9 @@ bool hasPathSum(struct TreeNode* root, int targetSum){ return false; } ``` + > 0113.路径总和 II + ```c int** ret; int* path; @@ -1139,6 +1163,7 @@ int** pathSum(struct TreeNode* root, int targetSum, int* returnSize, int** retur ### 0112.路径总和 **递归:** + ```scala object Solution { def hasPathSum(root: TreeNode, targetSum: Int): Boolean = { @@ -1163,6 +1188,7 @@ object Solution { ``` **迭代:** + ```scala object Solution { import scala.collection.mutable @@ -1187,6 +1213,7 @@ object Solution { ### 0113.路径总和 II **递归:** + ```scala object Solution { import scala.collection.mutable.ListBuffer diff --git a/problems/0121.买卖股票的最佳时机.md b/problems/0121.买卖股票的最佳时机.md index 8736c9f3..5b398f3f 100644 --- a/problems/0121.买卖股票的最佳时机.md +++ b/problems/0121.买卖股票的最佳时机.md @@ -127,7 +127,8 @@ dp[0][1]表示第0天不持有股票,不持有股票那么现金就是0,所 以示例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]就是最终结果。 diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md index d094da48..29242be3 100644 --- a/problems/0122.买卖股票的最佳时机II.md +++ b/problems/0122.买卖股票的最佳时机II.md @@ -62,7 +62,8 @@ 如图: -![122.买卖股票的最佳时机II](https://img-blog.csdnimg.cn/2020112917480858.png) + +![122.买卖股票的最佳时机II](https://code-thinking-1253855093.file.myqcloud.com/pics/2020112917480858-20230310134659477.png) 一些同学陷入:第一天怎么就没有利润呢,第一天到底算不算的困惑中。 diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md index 3cd7ab26..33157238 100644 --- a/problems/0123.买卖股票的最佳时机III.md +++ b/problems/0123.买卖股票的最佳时机III.md @@ -116,7 +116,8 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]); 以输入[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) 大家可以看到红色框为最后两次卖出的状态。 diff --git a/problems/0132.分割回文串II.md b/problems/0132.分割回文串II.md index 24cf43ae..eb0e39fb 100644 --- a/problems/0132.分割回文串II.md +++ b/problems/0132.分割回文串II.md @@ -163,7 +163,7 @@ for (int i = s.size() - 1; i >= 0; i--) { 以输入:"aabc" 为例: -![132.分割回文串II](https://img-blog.csdnimg.cn/20210124182218844.jpg) +![132.分割回文串II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210124182218844.jpg) 以上分析完毕,代码如下: diff --git a/problems/0135.分发糖果.md b/problems/0135.分发糖果.md index 1a2fe92a..181f24e9 100644 --- a/problems/0135.分发糖果.md +++ b/problems/0135.分发糖果.md @@ -53,7 +53,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 +79,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) 所以该过程代码如下: diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md index 2148971d..edb2c65a 100644 --- a/problems/0139.单词拆分.md +++ b/problems/0139.单词拆分.md @@ -181,7 +181,8 @@ dp[0]表示如果字符串为空的话,说明出现在字典里。 以输入: 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()]就是最终结果。 diff --git a/problems/0142.环形链表II.md b/problems/0142.环形链表II.md index 7f944345..8a48c7d5 100644 --- a/problems/0142.环形链表II.md +++ b/problems/0142.环形链表II.md @@ -1,3 +1,4 @@ +

@@ -6,6 +7,7 @@ + > 找到有没有环已经很不容易了,还要让我找到环的入口? @@ -14,13 +16,13 @@ [力扣题目链接](https://leetcode.cn/problems/linked-list-cycle-ii/) 题意: -给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 +给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 为了表示给定链表中的环,使用整数 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就相遇了 @@ -149,20 +151,20 @@ public: 即文章[链表:环找到了,那入口呢?](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也在环入口,那么把这个环展开成直线,就是如下图的样子: -![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一定是在环的任意一个位置,如图: -![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 个节点。 @@ -187,6 +189,7 @@ public: Java: + ```java public class Solution { public ListNode detectCycle(ListNode head) { @@ -235,6 +238,7 @@ class Solution: ``` Go: + ```go func detectCycle(head *ListNode) *ListNode { slow, fast := head, head @@ -267,7 +271,7 @@ var detectCycle = function(head) { let slow =head.next, fast = head.next.next; while(fast && fast.next && fast!== slow) { slow = slow.next; - fast = fast.next.next; + fast = fast.next.next; } if(!fast || !fast.next ) return null; slow = head; @@ -374,6 +378,7 @@ ListNode *detectCycle(ListNode *head) { ``` Scala: + ```scala object Solution { def detectCycle(head: ListNode): ListNode = { diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md index 695fac35..4fdd7bf4 100644 --- a/problems/0188.买卖股票的最佳时机IV.md +++ b/problems/0188.买卖股票的最佳时机IV.md @@ -129,7 +129,7 @@ for (int j = 1; j < 2 * k; j += 2) { 以输入[1,2,3,4,5],k=2为例。 -![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]即红色部分就是最后求解。 diff --git a/problems/0198.打家劫舍.md b/problems/0198.打家劫舍.md index 6002cd3a..fdb2dabf 100644 --- a/problems/0198.打家劫舍.md +++ b/problems/0198.打家劫舍.md @@ -85,7 +85,7 @@ for (int i = 2; i < nums.size(); i++) { 以示例二,输入[2,7,9,3,1]为例。 -![198.打家劫舍](https://img-blog.csdnimg.cn/20210221170954115.jpg) +![198.打家劫舍](https://code-thinking-1253855093.file.myqcloud.com/pics/20210221170954115.jpg) 红框dp[nums.size() - 1]为结果。 diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md index 88387667..99bd3580 100644 --- a/problems/0203.移除链表元素.md +++ b/problems/0203.移除链表元素.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ > 链表操作中,可以使用原链表来直接进行删除操作,也可以设置一个虚拟头结点再进行删除操作,接下来看一看哪种方式更方便。 # 203.移除链表元素 @@ -13,17 +15,17 @@ 题意:删除链表中等于给定值 val 的所有节点。 -示例 1: -输入:head = [1,2,6,3,4,5,6], val = 6 -输出:[1,2,3,4,5] +示例 1: +输入:head = [1,2,6,3,4,5,6], val = 6 +输出:[1,2,3,4,5] -示例 2: -输入:head = [], val = 1 -输出:[] +示例 2: +输入:head = [], val = 1 +输出:[] -示例 3: -输入:head = [7,7,7,7], val = 7 -输出:[] +示例 3: +输入:head = [7,7,7,7], val = 7 +输出:[] # 思路 @@ -32,11 +34,11 @@ 这里以链表 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) 如果使用C,C++编程语言的话,不要忘了还要从内存中删除这两个移除的节点, 清理节点内存之后如图: -![203_链表删除元素2](https://img-blog.csdnimg.cn/20210316095418280.png) +![203_链表删除元素2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095418280.png) **当然如果使用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。 -![203_链表删除元素6](https://img-blog.csdnimg.cn/20210316095619221.png) +![203_链表删除元素6](https://code-thinking-1253855093.file.myqcloud.com/pics/20210316095619221.png) 这里来给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。 @@ -146,8 +148,10 @@ public: ## 其他语言版本 + C: 用原来的链表操作: + ```c struct ListNode* removeElements(struct ListNode* head, int val){ struct ListNode* temp; @@ -168,7 +172,7 @@ struct ListNode* removeElements(struct ListNode* head, int val){ // 将cur->next设置为cur->next->next并删除cur->next cur->next = temp->next; free(temp); - } + } // 若cur->next不等于val,则将cur后移一位 else cur = cur->next; @@ -178,7 +182,9 @@ struct ListNode* removeElements(struct ListNode* head, int val){ return head; } ``` + 设置一个虚拟头结点: + ```c /** * Definition for singly-linked list. @@ -212,6 +218,7 @@ struct ListNode* removeElements(struct ListNode* head, int val){ ``` Java: + ```java /** * 添加虚节点方式 @@ -292,6 +299,7 @@ public ListNode removeElements(ListNode head, int val) { ``` Python: + ```python # Definition for singly-linked list. # class ListNode: @@ -335,7 +343,7 @@ func removeElements(head *ListNode, val int) *ListNode { } ``` -javaScript: +javaScript: ```js /** @@ -442,6 +450,7 @@ func removeElements(_ head: ListNode?, _ val: Int) -> ListNode? { ``` PHP: + ```php /** * Definition for singly-linked list. @@ -469,6 +478,7 @@ func removeElements(head *ListNode, val int) *ListNode { ``` RUST: + ```rust // Definition for singly-linked list. // #[derive(PartialEq, Eq, Clone, Debug)] @@ -476,7 +486,7 @@ RUST: // pub val: i32, // pub next: Option> // } -// +// // impl ListNode { // #[inline] // fn new(val: i32) -> Self { @@ -504,7 +514,9 @@ impl Solution { } } ``` + Scala: + ```scala /** * Definition for singly-linked list. @@ -535,7 +547,9 @@ object Solution { } } ``` + Kotlin: + ```kotlin /** * Example: @@ -569,6 +583,7 @@ class Solution { } } ``` +

diff --git a/problems/0206.翻转链表.md b/problems/0206.翻转链表.md index b87c7cba..8bf61c3f 100644 --- a/problems/0206.翻转链表.md +++ b/problems/0206.翻转链表.md @@ -25,7 +25,8 @@ 其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示: -![206_反转链表](https://img-blog.csdnimg.cn/20210218090901207.png) + +![206_反转链表](https://code-thinking-1253855093.file.myqcloud.com/pics/20210218090901207.png) 之前链表的头节点是元素1, 反转之后头结点就是元素5 ,这里并没有添加或者删除节点,仅仅是改变next指针的方向。 diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index a25fc2f5..5a8f91af 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -103,7 +103,7 @@ public: 解题的关键在于 窗口的起始位置如何移动,如图所示: -![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)。** diff --git a/problems/0213.打家劫舍II.md b/problems/0213.打家劫舍II.md index e595d2fd..0627eedb 100644 --- a/problems/0213.打家劫舍II.md +++ b/problems/0213.打家劫舍II.md @@ -39,15 +39,15 @@ * 情况一:考虑不包含首尾元素 -![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]就是最大的。 diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index 5ca7bb90..f631c3cd 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -1,3 +1,4 @@ +

@@ -7,17 +8,19 @@ + > 别看本篇选的是组合总和III,而不是组合总和,本题和上一篇77.组合相比难度刚刚好! # 216.组合总和III [力扣题目链接](https://leetcode.cn/problems/combination-sum-iii/) -找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 +找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 说明: + * 所有数字都是正整数。 -* 解集不能包含重复的组合。  +* 解集不能包含重复的组合。 示例 1: 输入: k = 3, n = 7 @@ -46,7 +49,7 @@ 选取过程如图: -![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) +![216.组合总和III](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123195717975.png) 图中,可以看出,只有最后取到集合(1,3)和为4 符合条件。 @@ -80,6 +83,7 @@ vector> result; vector path; void backtracking(int targetSum, int k, int sum, int startIndex) ``` + 其实这里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 如图: -![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里元素的总和。 @@ -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)了,那么往后遍历就没有意义了,直接剪掉。 @@ -181,7 +185,6 @@ if (sum > targetSum) { // 剪枝操作 当然这个剪枝也可以放在 调用递归之前,即放在这里,只不过要记得 要回溯操作给做了。 ```CPP - for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { // 剪枝 sum += i; // 处理 path.push_back(i); // 处理 @@ -250,6 +253,7 @@ public: ## Java 模板方法 + ```java class Solution { List> result = new ArrayList<>(); @@ -317,6 +321,7 @@ class Solution { ``` 其他方法 + ```java class Solution { List> res = new ArrayList<>(); @@ -429,12 +434,12 @@ var combinationSum3 = function(k, n) { const dfs = (path,index) => { // 剪枝操作 if (sum > n){ - return + return } if (path.length == k) { if(sum == n){ res.push([...path]); - return + return } } for (let i = index; i <= 9 - (k-path.length) + 1;i++) { diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index 10ac8264..c346b6ff 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -152,7 +152,7 @@ public: 我来举一个典型的例子如题: - + 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。 @@ -161,10 +161,10 @@ public: 对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。 完全二叉树(一)如图: -![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) 可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。 diff --git a/problems/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md index 1ea9a3e6..16a5be57 100644 --- a/problems/0226.翻转二叉树.md +++ b/problems/0226.翻转二叉树.md @@ -11,7 +11,8 @@ 翻转一棵二叉树。 -![226.翻转二叉树](https://img-blog.csdnimg.cn/20210203192644329.png) + +![226.翻转二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210203192644329.png) 这道题目背后有一个让程序员心酸的故事,听说 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) 可以发现想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。 diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md index 1c21f2be..8353303a 100644 --- a/problems/0235.二叉搜索树的最近公共祖先.md +++ b/problems/0235.二叉搜索树的最近公共祖先.md @@ -15,7 +15,8 @@ 例如,给定如下二叉搜索树:  root = [6,2,8,0,4,7,9,null,null,3,5] -![235. 二叉搜索树的最近公共祖先](https://img-blog.csdnimg.cn/20201018172243602.png) + +![235. 二叉搜索树的最近公共祖先](https://code-thinking-1253855093.file.myqcloud.com/pics/20201018172243602.png) 示例 1: diff --git a/problems/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md index 7b163ee5..33201def 100644 --- a/problems/0236.二叉树的最近公共祖先.md +++ b/problems/0236.二叉树的最近公共祖先.md @@ -17,7 +17,8 @@ 例如,给定如下二叉树:  root = [3,5,1,6,2,0,8,null,null,7,4] -![236. 二叉树的最近公共祖先](https://img-blog.csdnimg.cn/20201016173414722.png) + +![236. 二叉树的最近公共祖先](https://code-thinking-1253855093.file.myqcloud.com/pics/20201016173414722.png) 示例 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,多美滋滋。 @@ -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)返回上去! @@ -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) **从图中,大家可以看到,我们是如何回溯遍历整棵二叉树,将结果返回给头结点的!** diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index 68434479..8c542cea 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -16,7 +16,7 @@ 说明: 叶子节点是指没有子节点的节点。 示例: -![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) 我们先使用递归的方式,来做前序遍历。**要知道递归和回溯就是一家的,本题也需要回溯。** diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index 0654e494..c329156b 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -94,7 +94,8 @@ for (int i = 0; i <= n; i++) { // 遍历背包 已输入n为5例,dp状态图如下: -![279.完全平方数](https://img-blog.csdnimg.cn/20210202112617341.jpg) + +![279.完全平方数](https://code-thinking-1253855093.file.myqcloud.com/pics/20210202112617341.jpg) dp[0] = 0 dp[1] = min(dp[0] + 1) = 1 diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index f4fe1c31..478837cc 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -82,7 +82,7 @@ for (int i = 1; i < nums.size(); i++) { 输入:[0,1,0,3,2],dp数组的变化如下: -![300.最长上升子序列](https://img-blog.csdnimg.cn/20210110170945618.jpg) +![300.最长上升子序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110170945618.jpg) 如果代码写出来,但一直AC不了,那么就把dp数组打印出来,看看对不对! diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md index b4825894..d62e91c7 100644 --- a/problems/0309.最佳买卖股票时机含冷冻期.md +++ b/problems/0309.最佳买卖股票时机含冷冻期.md @@ -45,7 +45,7 @@ dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。 * 状态三:今天卖出股票 * 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天! -![](https://img-blog.csdnimg.cn/518d5baaf33f4b2698064f8efb42edbf.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/518d5baaf33f4b2698064f8efb42edbf.png) j的状态为: @@ -133,7 +133,8 @@ dp[i][3] = dp[i - 1][2]; 以 [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) 最后结果是取 状态二,状态三,和状态四的最大值,不少同学会把状态四忘了,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。 diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index c718ef47..3be72565 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -106,7 +106,7 @@ dp[0] = 0; 以输入:coins = [1, 2, 5], amount = 5为例 -![322.零钱兑换](https://img-blog.csdnimg.cn/20210201111833906.jpg) +![322.零钱兑换](https://code-thinking-1253855093.file.myqcloud.com/pics/20210201111833906.jpg) dp[amount]为最终结果。 diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 17ae9641..9bd5df7a 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -57,7 +57,7 @@ 对于死循环,我来举一个有重复机场的例子: -![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"]为例,抽象为树形结构如下: -![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& 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)详细介绍过。 diff --git a/problems/0337.打家劫舍III.md b/problems/0337.打家劫舍III.md index 97cf234f..f75c4c88 100644 --- a/problems/0337.打家劫舍III.md +++ b/problems/0337.打家劫舍III.md @@ -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) ## 思路 diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index 4170f7e6..c2fbbdde 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -129,7 +129,7 @@ for (int i = 3; i <= n ; i++) { 举例当n为10 的时候,dp数组里的数值,如下: -![343.整数拆分](https://img-blog.csdnimg.cn/20210104173021581.png) +![343.整数拆分](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104173021581.png) 以上动规五部曲分析完毕,C++代码如下: diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md index ed6a5d97..0d25cba5 100644 --- a/problems/0349.两个数组的交集.md +++ b/problems/0349.两个数组的交集.md @@ -16,7 +16,7 @@ 题意:给定两个数组,编写一个函数来计算它们的交集。 -![349. 两个数组的交集](https://img-blog.csdnimg.cn/20200818193523911.png) +![349. 两个数组的交集](https://code-thinking-1253855093.file.myqcloud.com/pics/20200818193523911.png) **说明:** 输出结果中的每个元素一定是唯一的。 diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index efb9c6b6..5eb61ed0 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -41,7 +41,7 @@ 用示例二来举例,如图所示: -![376.摆动序列](https://img-blog.csdnimg.cn/20201124174327597.png) +![376.摆动序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124174327597.png) **局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值**。 @@ -103,7 +103,7 @@ 那么为了规则统一,针对序列[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) diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index 0b7a2fdd..f2287188 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -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]都打出来,看看和我们推导的一不一样。 diff --git a/problems/0392.判断子序列.md b/problems/0392.判断子序列.md index 03c1baae..2fa647d2 100644 --- a/problems/0392.判断子序列.md +++ b/problems/0392.判断子序列.md @@ -77,7 +77,8 @@ if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前 因为这样的定义在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,初始化就比较麻烦了。 @@ -94,13 +95,15 @@ vector> dp(s.size() + 1, vector(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数组 以示例一为例,输入: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 的子序列。 diff --git a/problems/0404.左叶子之和.md b/problems/0404.左叶子之和.md index 1ff74bc9..cf5441c4 100644 --- a/problems/0404.左叶子之和.md +++ b/problems/0404.左叶子之和.md @@ -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,因为这棵树根本没有左叶子!** 但看这个图的左叶子之和是多少? diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index 73a7affc..48c498e1 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -59,7 +59,7 @@ 以图中{5,2} 为例: -![406.根据身高重建队列](https://img-blog.csdnimg.cn/20201216201851982.png) +![406.根据身高重建队列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201216201851982.png) 按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。 diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 45dd289a..08d005f5 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -148,7 +148,8 @@ dp[j]的数值一定是小于等于j的。 用例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,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。 diff --git a/problems/0450.删除二叉搜索树中的节点.md b/problems/0450.删除二叉搜索树中的节点.md index 0655f8c5..2602b528 100644 --- a/problems/0450.删除二叉搜索树中的节点.md +++ b/problems/0450.删除二叉搜索树中的节点.md @@ -21,7 +21,8 @@ 示例: -![450.删除二叉搜索树中的节点](https://img-blog.csdnimg.cn/20201020171048265.png) + +![450.删除二叉搜索树中的节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20201020171048265.png) # 算法公开课 diff --git a/problems/0452.用最少数量的箭引爆气球.md b/problems/0452.用最少数量的箭引爆气球.md index 86bfa490..6b018f97 100644 --- a/problems/0452.用最少数量的箭引爆气球.md +++ b/problems/0452.用最少数量的箭引爆气球.md @@ -74,7 +74,7 @@ 以题目示例: [[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了。 diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index b40380db..8d79d701 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -52,7 +52,8 @@ 其实本题并不是多重背包,再来看一下这个图,捋清几种背包的关系 -![416.分割等和子集1](https://img-blog.csdnimg.cn/20210117171307407.png) + +![416.分割等和子集1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210117171307407-20230310132423205.png) 多重背包是每个物品,数量不同的情况。 @@ -128,7 +129,7 @@ for (string str : strs) { // 遍历物品 最后dp数组的状态如下所示: -![474.一和零](https://img-blog.csdnimg.cn/20210120111201512.jpg) +![474.一和零](https://code-thinking-1253855093.file.myqcloud.com/pics/20210120111201512.jpg) 以上动规五部曲分析完毕,C++代码如下: diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 6aa81539..436dbf01 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -46,7 +46,10 @@ 为了有鲜明的对比,我用[4, 7, 6, 7]这个数组来举例,抽象为树形结构如图: -![491. 递增子序列1](https://img-blog.csdnimg.cn/20201124200229824.png) + +![491. 递增子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124200229824.png) + + ### 回溯三部曲 @@ -78,7 +81,7 @@ if (path.size() > 1) { * 单层搜索逻辑 -![491. 递增子序列1](https://img-blog.csdnimg.cn/20201124200229824.png) +![491. 递增子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124200229824-20230310131640070.png) 在图中可以看出,**同一父节点下的同层上使用过的元素就不能再使用了** 那么单层搜索代码如下: diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index 9adf8b05..3dcac737 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -219,7 +219,7 @@ bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4 dp数组状态变化如下: -![494.目标和](https://img-blog.csdnimg.cn/20210125120743274.jpg) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210125120743274.jpg) C++代码如下: diff --git a/problems/0501.二叉搜索树中的众数.md b/problems/0501.二叉搜索树中的众数.md index 4214e232..b7ef606f 100644 --- a/problems/0501.二叉搜索树中的众数.md +++ b/problems/0501.二叉搜索树中的众数.md @@ -25,7 +25,7 @@ 给定 BST [1,null,2,2], -![501. 二叉搜索树中的众数](https://img-blog.csdnimg.cn/20201014221532206.png) +![501. 二叉搜索树中的众数](https://code-thinking-1253855093.file.myqcloud.com/pics/20201014221532206.png) 返回[2]. @@ -146,7 +146,7 @@ public: 如图: -![501.二叉搜索树中的众数1](https://img-blog.csdnimg.cn/20210204152758889.png) +![501.二叉搜索树中的众数1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204152758889.png) 中序遍历代码如下: diff --git a/problems/0513.找树左下角的值.md b/problems/0513.找树左下角的值.md index d0894882..5ff25484 100644 --- a/problems/0513.找树左下角的值.md +++ b/problems/0513.找树左下角的值.md @@ -11,13 +11,14 @@ 给定一个二叉树,在树的最后一行找到最左边的值。 + 示例 1: -![513.找树左下角的值](https://img-blog.csdnimg.cn/20210204152956836.png) +![513.找树左下角的值](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204152956836.png) 示例 2: -![513.找树左下角的值1](https://img-blog.csdnimg.cn/20210204153017586.png) +![513.找树左下角的值1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204153017586.png) ## 视频讲解 diff --git a/problems/0516.最长回文子序列.md b/problems/0516.最长回文子序列.md index c8f10c24..d28a33cc 100644 --- a/problems/0516.最长回文子序列.md +++ b/problems/0516.最长回文子序列.md @@ -1,10 +1,12 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 516.最长回文子序列 + +# 516.最长回文子序列 [力扣题目链接](https://leetcode.cn/problems/longest-palindromic-subsequence/) @@ -52,7 +54,7 @@ 如果s[i]与s[j]相同,那么dp[i][j] = dp[i + 1][j - 1] + 2; 如图: -![516.最长回文子序列](https://img-blog.csdnimg.cn/20210127151350563.jpg) +![516.最长回文子序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20210127151350563.jpg) (如果这里看不懂,回忆一下dp[i][j]的定义) @@ -64,7 +66,7 @@ 那么dp[i][j]一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]); -![516.最长回文子序列1](https://img-blog.csdnimg.cn/20210127151420476.jpg) +![516.最长回文子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210127151420476.jpg) 代码如下: @@ -91,7 +93,7 @@ for (int i = 0; i < s.size(); i++) dp[i][i] = 1; 4. 确定遍历顺序 -从递归公式中,可以看出,dp[i][j] 依赖于 dp[i + 1][j - 1] ,dp[i + 1][j] 和 dp[i][j - 1],如图: +从递归公式中,可以看出,dp[i][j] 依赖于 dp[i + 1][j - 1] ,dp[i + 1][j] 和 dp[i][j - 1],如图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230102172155.png) @@ -117,7 +119,7 @@ for (int i = s.size() - 1; i >= 0; i--) { 输入s:"cbbd" 为例,dp数组状态如图: -![516.最长回文子序列3](https://img-blog.csdnimg.cn/20210127151521432.jpg) +![516.最长回文子序列3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210127151521432.jpg) 红色框即:dp[0][s.size() - 1]; 为最终结果。 @@ -147,6 +149,7 @@ public: Java: + ```java public class Solution { public int longestPalindromeSubseq(String s) { @@ -169,6 +172,7 @@ public class Solution { Python: + ```python class Solution: def longestPalindromeSubseq(self, s: str) -> int: @@ -185,6 +189,7 @@ class Solution: ``` Go: + ```Go func longestPalindromeSubseq(s string) int { size := len(s) @@ -213,11 +218,12 @@ func longestPalindromeSubseq(s string) int { ``` Javascript: + ```javascript const longestPalindromeSubseq = (s) => { const strLen = s.length; let dp = Array.from(Array(strLen), () => Array(strLen).fill(0)); - + for(let i = 0; i < strLen; i++) { dp[i][i] = 1; } @@ -241,7 +247,7 @@ TypeScript: ```typescript function longestPalindromeSubseq(s: string): number { /** - dp[i][j]:[i,j]区间内,最长回文子序列的长度 + dp[i][j]:[i,j]区间内,最长回文子序列的长度 */ const length: number = s.length; const dp: number[][] = new Array(length).fill(0) diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md index 162bbe68..3fd03e16 100644 --- a/problems/0518.零钱兑换II.md +++ b/problems/0518.零钱兑换II.md @@ -158,7 +158,7 @@ for (int j = 0; j <= amount; j++) { // 遍历背包容量 输入: amount = 5, coins = [1, 2, 5] ,dp状态图如下: -![518.零钱兑换II](https://img-blog.csdnimg.cn/20210120181331461.jpg) +![518.零钱兑换II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210120181331461.jpg) 最后红色框dp[amount]为最终结果。 diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md index d59ec09c..203add39 100644 --- a/problems/0530.二叉搜索树的最小绝对差.md +++ b/problems/0530.二叉搜索树的最小绝对差.md @@ -15,7 +15,7 @@ 示例: -![530二叉搜索树的最小绝对差](https://img-blog.csdnimg.cn/20201014223400123.png) +![530二叉搜索树的最小绝对差](https://code-thinking-1253855093.file.myqcloud.com/pics/20201014223400123.png) 提示:树中至少有 2 个节点。 @@ -72,7 +72,7 @@ public: 如图: -![530.二叉搜索树的最小绝对差](https://img-blog.csdnimg.cn/20210204153247458.png) +![530.二叉搜索树的最小绝对差](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204153247458.png) 一些同学不知道在递归中如何记录前一个节点的指针,其实实现起来是很简单的,大家只要看过一次,写过一次,就掌握了。 diff --git a/problems/0538.把二叉搜索树转换为累加树.md b/problems/0538.把二叉搜索树转换为累加树.md index 8d4b97f1..29b21840 100644 --- a/problems/0538.把二叉搜索树转换为累加树.md +++ b/problems/0538.把二叉搜索树转换为累加树.md @@ -19,7 +19,8 @@ 示例 1: -![538.把二叉搜索树转换为累加树](https://img-blog.csdnimg.cn/20201023160751832.png) + +![538.把二叉搜索树转换为累加树](https://code-thinking-1253855093.file.myqcloud.com/pics/20201023160751832.png) * 输入:[4,1,6,0,2,5,7,null,null,null,3,null,null,null,8] * 输出:[30,36,21,36,35,26,15,null,null,null,33,null,null,null,8] @@ -67,7 +68,8 @@ 遍历顺序如图所示: -![538.把二叉搜索树转换为累加树](https://img-blog.csdnimg.cn/20210204153440666.png) + +![538.把二叉搜索树转换为累加树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204153440666.png) 本题依然需要一个pre指针记录当前遍历节点cur的前一个节点,这样才方便做累加。 diff --git a/problems/0583.两个字符串的删除操作.md b/problems/0583.两个字符串的删除操作.md index 68c6de16..eb046a9d 100644 --- a/problems/0583.两个字符串的删除操作.md +++ b/problems/0583.两个字符串的删除操作.md @@ -78,7 +78,7 @@ for (int j = 0; j <= word2.size(); j++) dp[0][j] = j; 以word1:"sea",word2:"eat"为例,推导dp数组状态图如下: -![583.两个字符串的删除操作1](https://img-blog.csdnimg.cn/20210714101750205.png) +![583.两个字符串的删除操作1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210714101750205.png) 以上分析完毕,代码如下: diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md index 058deb85..f98948f0 100644 --- a/problems/0617.合并二叉树.md +++ b/problems/0617.合并二叉树.md @@ -15,7 +15,7 @@ 示例 1: -![617.合并二叉树](https://img-blog.csdnimg.cn/20210204153634809.png) +![617.合并二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000854.png) 注意: 合并必须从两个树的根节点开始。 diff --git a/problems/0647.回文子串.md b/problems/0647.回文子串.md index df55d24d..90e6da9f 100644 --- a/problems/0647.回文子串.md +++ b/problems/0647.回文子串.md @@ -102,7 +102,7 @@ dp[i][j]可以初始化为true么? 当然不行,怎能刚开始就全都匹 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-20230310132134822.jpg) 如果这矩阵是从上到下,从左到右遍历,那么会用到没有计算过的dp[i + 1][j - 1],也就是根据不确定是不是回文的区间[i+1,j-1],来判断了[i,j]是不是回文,那结果一定是不对的。 @@ -132,7 +132,7 @@ for (int i = s.size() - 1; i >= 0; i--) { // 注意遍历顺序 举例,输入:"aaa",dp[i][j]状态如下: -![647.回文子串1](https://img-blog.csdnimg.cn/20210121171059951.jpg) +![647.回文子串1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210121171059951-20230310132153163.jpg) 图中有6个true,所以就是有6个回文子串。 diff --git a/problems/0654.最大二叉树.md b/problems/0654.最大二叉树.md index 0f473228..64b38b48 100644 --- a/problems/0654.最大二叉树.md +++ b/problems/0654.最大二叉树.md @@ -19,7 +19,7 @@ 示例 : -![654.最大二叉树](https://img-blog.csdnimg.cn/20210204154534796.png) +![654.最大二叉树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204154534796.png) 提示: diff --git a/problems/0669.修剪二叉搜索树.md b/problems/0669.修剪二叉搜索树.md index 1fd6fce0..18d8a0cc 100644 --- a/problems/0669.修剪二叉搜索树.md +++ b/problems/0669.修剪二叉搜索树.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ > 如果不对递归有深刻的理解,本题有点难 > 单纯移除一个节点那还不够,要修剪! @@ -12,13 +14,13 @@ [力扣题目链接](https://leetcode.cn/problems/trim-a-binary-search-tree/) -给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 +给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 -![669.修剪二叉搜索树](https://img-blog.csdnimg.cn/20201014173115788.png) +![669.修剪二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20201014173115788.png) -![669.修剪二叉搜索树1](https://img-blog.csdnimg.cn/20201014173219142.png) +![669.修剪二叉搜索树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201014173219142.png) -# 算法公开课 +# 算法公开课 **《代码随想录》算法视频公开课:[你修剪的方式不对,我来给你纠正一下!| LeetCode:669. 修剪二叉搜索树](https://www.bilibili.com/video/BV17P41177ud?share_source=copy_web),相信结合视频在看本篇题解,更有助于大家对本题的理解**。 @@ -50,7 +52,7 @@ public: 我们在重新关注一下第二个示例,如图: -![669.修剪二叉搜索树](https://img-blog.csdnimg.cn/20210204155302751.png) +![669.修剪二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204155302751.png) **所以以上的代码是不可行的!** @@ -60,7 +62,7 @@ public: 在上图中我们发现节点0并不符合区间要求,那么将节点0的右孩子 节点2 直接赋给 节点3的左孩子就可以了(就是把节点0从二叉树中移除),如图: -![669.修剪二叉搜索树1](https://img-blog.csdnimg.cn/20210204155327203.png) +![669.修剪二叉搜索树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204155327203.png) 理解了最关键部分了我们再递归三部曲: @@ -127,9 +129,10 @@ return root; 在回顾一下上面的代码,针对下图中二叉树的情况: -![669.修剪二叉搜索树1](https://img-blog.csdnimg.cn/20210204155327203.png) +![669.修剪二叉搜索树1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204155327203-20230310120126738.png) 如下代码相当于把节点0的右孩子(节点2)返回给上一层, + ``` if (root->val < low) { TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点 @@ -243,7 +246,7 @@ public: # 其他语言版本 -## Java +## Java ```Java class Solution { @@ -266,8 +269,10 @@ class Solution { ``` -## Python +## Python + **递归** + ```python # Definition for a binary tree node. # class TreeNode: @@ -287,7 +292,7 @@ class Solution: if root.val < low: # 若当前root节点小于左界:只考虑其右子树,用于替代更新后的其本身,抛弃其左子树整体 return self.trimBST(root.right, low, high) - + if high < root.val: # 若当前root节点大于右界:只考虑其左子树,用于替代更新后的其本身,抛弃其右子树整体 return self.trimBST(root.left, low, high) @@ -300,6 +305,7 @@ class Solution: ``` **迭代** + ```python class Solution: def trimBST(self, root: Optional[TreeNode], low: int, high: int) -> Optional[TreeNode]: @@ -325,10 +331,9 @@ class Solution: return root ``` -## Go +## Go ```go - // 递归 func trimBST(root *TreeNode, low int, high int) *TreeNode { if root == nil { @@ -384,6 +389,7 @@ func trimBST(root *TreeNode, low int, high int) *TreeNode { ## JavaScript版本 迭代: + ```js var trimBST = function(root, low, high) { if(root === null) { @@ -416,8 +422,9 @@ var trimBST = function(root, low, high) { ``` 递归: + ```js -var trimBST = function (root,low,high) { +var trimBST = function (root,low,high) { if(root === null) { return null; } @@ -486,6 +493,7 @@ function trimBST(root: TreeNode | null, low: number, high: number): TreeNode | n ## Scala 递归法: + ```scala object Solution { def trimBST(root: TreeNode, low: Int, high: Int): TreeNode = { @@ -502,6 +510,7 @@ object Solution { ## rust // 递归 + ```rust impl Solution { pub fn trim_bst( diff --git a/problems/0673.最长递增子序列的个数.md b/problems/0673.最长递增子序列的个数.md index 6418be8a..da80a4e0 100644 --- a/problems/0673.最长递增子序列的个数.md +++ b/problems/0673.最长递增子序列的个数.md @@ -180,7 +180,7 @@ for (int i = 0; i < nums.size(); i++) { 输入:[1,3,5,4,7] -![673.最长递增子序列的个数](https://img-blog.csdnimg.cn/20210112104555234.jpg) +![673.最长递增子序列的个数](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000656.png) **如果代码写出来了,怎么改都通过不了,那么把dp和count打印出来看看对不对!** diff --git a/problems/0674.最长连续递增序列.md b/problems/0674.最长连续递增序列.md index 3cb68060..79a8311d 100644 --- a/problems/0674.最长连续递增序列.md +++ b/problems/0674.最长连续递增序列.md @@ -82,7 +82,8 @@ for (int i = 1; i < nums.size(); i++) { 已输入nums = [1,3,5,4,7]为例,dp数组状态如下: -![674.最长连续递增序列](https://img-blog.csdnimg.cn/20210204103529742.jpg) + +![674.最长连续递增序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204103529742.jpg) **注意这里要取dp[i]里的最大值,所以dp[2]才是结果!** diff --git a/problems/0700.二叉搜索树中的搜索.md b/problems/0700.二叉搜索树中的搜索.md index 675a0e4a..40724f48 100644 --- a/problems/0700.二叉搜索树中的搜索.md +++ b/problems/0700.二叉搜索树中的搜索.md @@ -13,7 +13,8 @@ 例如, -![700.二叉搜索树中的搜索](https://img-blog.csdnimg.cn/20210204155522476.png) + +![700.二叉搜索树中的搜索](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204155522476.png) 在上述示例中,如果要找的值是 5,但因为没有节点值为 5,我们应该返回 NULL。 @@ -125,7 +126,7 @@ public: 中间节点如果大于3就向左走,如果小于3就向右走,如图: -![二叉搜索树](https://img-blog.csdnimg.cn/20200812190213280.png) +![二叉搜索树](https://code-thinking-1253855093.file.myqcloud.com/pics/20200812190213280.png) 所以迭代法代码如下: diff --git a/problems/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index 4bdb8d60..4e834201 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -13,7 +13,8 @@ 注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回任意有效的结果。 -![701.二叉搜索树中的插入操作](https://img-blog.csdnimg.cn/20201019173259554.png) + +![701.二叉搜索树中的插入操作](https://code-thinking-1253855093.file.myqcloud.com/pics/20201019173259554.png) 提示: diff --git a/problems/0704.二分查找.md b/problems/0704.二分查找.md index a02bf7a2..97b123bc 100644 --- a/problems/0704.二分查找.md +++ b/problems/0704.二分查找.md @@ -59,7 +59,7 @@ 例如在数组:1,2,3,4,7,9,10中查找元素2,如图所示: -![704.二分查找](https://img-blog.csdnimg.cn/20210311153055723.jpg) +![704.二分查找](https://code-thinking-1253855093.file.myqcloud.com/pics/20210311153055723.jpg) 代码如下:(详细注释) @@ -98,7 +98,8 @@ public: 在数组:1,2,3,4,7,9,10中查找元素2,如图所示:(**注意和方法一的区别**) -![704.二分查找1](https://img-blog.csdnimg.cn/20210311153123632.jpg) + +![704.二分查找1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210311153123632.jpg) 代码如下:(详细注释) diff --git a/problems/0707.设计链表.md b/problems/0707.设计链表.md index 43ed2622..95560fb1 100644 --- a/problems/0707.设计链表.md +++ b/problems/0707.设计链表.md @@ -22,7 +22,7 @@ * deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。 -![707示例](https://img-blog.csdnimg.cn/20200814200558953.png) +![707示例](https://code-thinking-1253855093.file.myqcloud.com/pics/20200814200558953.png) # 思路 @@ -33,10 +33,10 @@ 如果对链表的虚拟头结点不清楚,可以看这篇文章:[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html) 删除链表节点: -![链表-删除节点](https://img-blog.csdnimg.cn/20200806195114541.png) +![链表-删除节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806195114541.png) 添加链表节点: -![链表-添加节点](https://img-blog.csdnimg.cn/20200806195134331.png) +![链表-添加节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806195134331.png) 这道题目设计链表的五个接口: * 获取链表第index个节点的数值 diff --git a/problems/0718.最长重复子数组.md b/problems/0718.最长重复子数组.md index 0658f9c8..08be6732 100644 --- a/problems/0718.最长重复子数组.md +++ b/problems/0718.最长重复子数组.md @@ -93,7 +93,8 @@ for (int i = 1; i <= nums1.size(); i++) { 拿示例1中,A: [1,2,3,2,1],B: [3,2,1,4,7]为例,画一个dp数组的状态变化,如下: -![718.最长重复子数组](https://img-blog.csdnimg.cn/2021011215282060.jpg) + +![718.最长重复子数组](https://code-thinking-1253855093.file.myqcloud.com/pics/2021011215282060.jpg) 以上五部曲分析完毕,C++代码如下: @@ -124,7 +125,8 @@ public: 在如下图中: -![718.最长重复子数组](https://img-blog.csdnimg.cn/2021011215282060.jpg) + +![718.最长重复子数组](https://code-thinking-1253855093.file.myqcloud.com/pics/2021011215282060-20230310134554486.jpg) 我们可以看出dp[i][j]都是由dp[i - 1][j - 1]推出。那么压缩为一维数组,也就是dp[j]都是由dp[j - 1]推出。 diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index 5aa37005..263a28f4 100644 --- a/problems/0739.每日温度.md +++ b/problems/0739.每日温度.md @@ -1,3 +1,4 @@ +

@@ -5,15 +6,16 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 739. 每日温度 [力扣题目链接](https://leetcode.cn/problems/daily-temperatures/) -请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 +请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 -例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 +例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 -提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。 +提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。 ## 思路 @@ -32,7 +34,7 @@ **单调栈的本质是空间换时间**,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。 -**更直白来说,就是用一个栈来记录我们遍历过的元素**,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。 +**更直白来说,就是用一个栈来记录我们遍历过的元素**,因为我们遍历数组的时候,我们不知道之前都遍历了哪些元素,以至于遍历一个元素找不到是不是之前遍历过一个更小的,所以我们需要用一个容器(这里用单调栈)来记录我们遍历过的元素。 在使用单调栈的时候首先要明确如下几点: @@ -59,79 +61,79 @@ **把这三种情况分析清楚了,也就理解透彻了**。 -接下来我们用temperatures = [73, 74, 75, 71, 71, 72, 76, 73]为例来逐步分析,输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 +接下来我们用temperatures = [73, 74, 75, 71, 71, 72, 76, 73]为例来逐步分析,输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 -------- +------- 首先先将第一个遍历元素加入单调栈 -![739.每日温度1](https://img-blog.csdnimg.cn/20210219124434172.jpg) +![739.每日温度1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124434172.jpg) ---------- +--------- 加入T[1] = 74,因为T[1] > T[0](当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况)。 我们要保持一个递增单调栈(从栈头到栈底),所以将T[0]弹出,T[1]加入,此时result数组可以记录了,result[0] = 1,即T[0]右面第一个比T[0]大的元素是T[1]。 -![739.每日温度2](https://img-blog.csdnimg.cn/20210219124504299.jpg) +![739.每日温度2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124504299.jpg) ------------ +----------- 加入T[2],同理,T[1]弹出 -![739.每日温度3](https://img-blog.csdnimg.cn/20210219124527361.jpg) +![739.每日温度3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124527361.jpg) -------- +------- 加入T[3],T[3] < T[2] (当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况),加T[3]加入单调栈。 -![739.每日温度4](https://img-blog.csdnimg.cn/20210219124610761.jpg) +![739.每日温度4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124610761.jpg) ---------- +--------- 加入T[4],T[4] == T[3] (当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况),此时依然要加入栈,不用计算距离,因为我们要求的是右面第一个大于本元素的位置,而不是大于等于! -![739.每日温度5](https://img-blog.csdnimg.cn/20210219124633444.jpg) +![739.每日温度5](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124633444.jpg) ---------- +--------- 加入T[5],T[5] > T[4] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[4]弹出,同时计算距离,更新result -![739.每日温度6](https://img-blog.csdnimg.cn/20210219124700567.jpg) +![739.每日温度6](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124700567.jpg) ----------- +---------- T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[3]继续弹出,同时计算距离,更新result -![739.每日温度7](https://img-blog.csdnimg.cn/20210219124726613.jpg) +![739.每日温度7](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124726613.jpg) -------- +------- 直到发现T[5]小于T[st.top()],终止弹出,将T[5]加入单调栈 -![739.每日温度8](https://img-blog.csdnimg.cn/20210219124807715.jpg) +![739.每日温度8](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124807715.jpg) -------- +------- 加入T[6],同理,需要将栈里的T[5],T[2]弹出 -![739.每日温度9](https://img-blog.csdnimg.cn/2021021912483374.jpg) +![739.每日温度9](https://code-thinking-1253855093.file.myqcloud.com/pics/2021021912483374.jpg) -------- +------- 同理,继续弹出 -![739.每日温度10](https://img-blog.csdnimg.cn/2021021912490098.jpg) +![739.每日温度10](https://code-thinking-1253855093.file.myqcloud.com/pics/2021021912490098.jpg) ------- +------ 此时栈里只剩下了T[6] -![739.每日温度11](https://img-blog.csdnimg.cn/20210219124930156.jpg) +![739.每日温度11](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124930156.jpg) ------------ 加入T[7], T[7] < T[6] 直接入栈,这就是最后的情况,result数组也更新完了。 -![739.每日温度12](https://img-blog.csdnimg.cn/20210219124957216.jpg) +![739.每日温度12](https://code-thinking-1253855093.file.myqcloud.com/pics/20210219124957216.jpg) 此时有同学可能就疑惑了,那result[6] , result[7]怎么没更新啊,元素也一直在栈里。 @@ -144,7 +146,7 @@ T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[ * 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况 * 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况 -通过以上过程,大家可以自己再模拟一遍,就会发现:只有单调栈递增(从栈口到栈底顺序),就是求右边第一个比自己大的,单调栈递减的话,就是求右边第一个比自己小的。 +通过以上过程,大家可以自己再模拟一遍,就会发现:只有单调栈递增(从栈口到栈底顺序),就是求右边第一个比自己大的,单调栈递减的话,就是求右边第一个比自己小的。 C++代码如下: @@ -198,6 +200,7 @@ public: } }; ``` + * 时间复杂度:O(n) * 空间复杂度:O(n) @@ -210,26 +213,26 @@ public: Java: -```java +```java class Solution { - // 版本 1 + // 版本 1 public int[] dailyTemperatures(int[] temperatures) { - + int lens=temperatures.length; int []res=new int[lens]; - + /** 如果当前遍历的元素 大于栈顶元素,表示 栈顶元素的 右边的最大的元素就是 当前遍历的元素, - 所以弹出 栈顶元素,并记录 - 如果栈不空的话,还要考虑新的栈顶与当前元素的大小关系 + 所以弹出 栈顶元素,并记录 + 如果栈不空的话,还要考虑新的栈顶与当前元素的大小关系 否则的话,可以直接入栈。 注意,单调栈里 加入的元素是 下标。 */ Deque stack=new LinkedList<>(); stack.push(0); for(int i=1;i stack=new LinkedList<>(); for(int i=0;itemperatures[stack.peek()]){ res[stack.peek()]=i-stack.peek(); stack.pop(); @@ -263,10 +266,12 @@ class Solution { return res; } } - + } ``` + Python: + ```python class Solution: def dailyTemperatures(self, temperatures: List[int]) -> List[int]: @@ -282,9 +287,10 @@ class Solution: answer[stack[-1]]=i-stack[-1] stack.pop() stack.append(i) - + return answer ``` + Go: > 暴力法 @@ -341,6 +347,7 @@ func dailyTemperatures(temperatures []int) []int { ``` > 单调栈法(精简版本) + ```go // 单调递减栈 func dailyTemperatures(num []int) []int { @@ -362,6 +369,7 @@ func dailyTemperatures(num []int) []int { ``` JavaScript: + ```javascript // 版本一 var dailyTemperatures = function(temperatures) { diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index 6667e740..077cb54a 100644 --- a/problems/0763.划分字母区间.md +++ b/problems/0763.划分字母区间.md @@ -41,7 +41,8 @@ 如图: -![763.划分字母区间](https://img-blog.csdnimg.cn/20201222191924417.png) + +![763.划分字母区间](https://code-thinking-1253855093.file.myqcloud.com/pics/20201222191924417.png) 明白原理之后,代码并不复杂,如下: diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index 92212ebb..14563a51 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 968.监控二叉树 [力扣题目链接](https://leetcode.cn/problems/binary-tree-cameras/) @@ -17,7 +19,7 @@ 示例 1: -![](https://img-blog.csdnimg.cn/20201229175736596.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20201229175736596.png) * 输入:[0,0,null,0,0] * 输出:1 @@ -25,7 +27,7 @@ 示例 2: -![](https://img-blog.csdnimg.cn/2020122917584449.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/2020122917584449.png) * 输入:[0,0,null,0,null,0,null,null,0] * 输出:2 @@ -139,7 +141,7 @@ if (cur == NULL) return 2; 如图: -![968.监控二叉树2](https://img-blog.csdnimg.cn/20201229203710729.png) +![968.监控二叉树2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201229203710729.png) 代码如下: @@ -163,6 +165,7 @@ if (left == 2 && right == 2) return 0; 此时摄像头的数量要加一,并且return 1,代表中间节点放摄像头。 代码如下: + ```CPP if (left == 0 || right == 0) { result++; @@ -186,7 +189,7 @@ if (left == 1 || right == 1) return 2; **从这个代码中,可以看出,如果left == 1, right == 0 怎么办?其实这种条件在情况2中已经判断过了**,如图: -![968.监控二叉树1](https://img-blog.csdnimg.cn/2020122920362355.png) +![968.监控二叉树1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020122920362355.png) 这种情况也是大多数同学容易迷惑的情况。 @@ -194,7 +197,7 @@ if (left == 1 || right == 1) return 2; 以上都处理完了,递归结束之后,可能头结点 还有一个无覆盖的情况,如图: -![968.监控二叉树3](https://img-blog.csdnimg.cn/20201229203742446.png) +![968.监控二叉树3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201229203742446.png) 所以递归结束之后,还要判断根节点,如果没有覆盖,result++,代码如下: @@ -311,7 +314,8 @@ public: ## 其他语言版本 -### Java +### Java + ```java class Solution { int res=0; @@ -324,32 +328,32 @@ class Solution { } /** 节点的状态值: - 0 表示无覆盖 + 0 表示无覆盖 1 表示 有摄像头 - 2 表示有覆盖 + 2 表示有覆盖 后序遍历,根据左右节点的情况,来判读 自己的状态 */ public int minCame(TreeNode root){ if(root==null){ - // 空节点默认为 有覆盖状态,避免在叶子节点上放摄像头 + // 空节点默认为 有覆盖状态,避免在叶子节点上放摄像头 return 2; } int left=minCame(root.left); int right=minCame(root.right); - + // 如果左右节点都覆盖了的话, 那么本节点的状态就应该是无覆盖,没有摄像头 if(left==2&&right==2){ - //(2,2) + //(2,2) return 0; }else if(left==0||right==0){ // 左右节点都是无覆盖状态,那 根节点此时应该放一个摄像头 - // (0,0) (0,1) (0,2) (1,0) (2,0) + // (0,0) (0,1) (0,2) (1,0) (2,0) // 状态值为 1 摄像头数 ++; res++; return 1; }else{ // 左右节点的 状态为 (1,1) (1,2) (2,1) 也就是左右节点至少存在 1个摄像头, - // 那么本节点就是处于被覆盖状态 + // 那么本节点就是处于被覆盖状态 return 2; } } @@ -357,7 +361,8 @@ class Solution { ``` -### Python +### Python + ```python class Solution: def minCameraCover(self, root: TreeNode) -> int: @@ -367,19 +372,19 @@ class Solution: # 0: 该节点未覆盖 # 1: 该节点有摄像头 # 2: 该节点有覆盖 - + result = 0 # 从下往上遍历:后序(左右中) def traversal(curr: TreeNode) -> int: nonlocal result - + if not curr: return 2 left = traversal(curr.left) right = traversal(curr.right) # Case 1: # 左右节点都有覆盖 - if left == 2 and right == 2: + if left == 2 and right == 2: return 0 # Case 2: @@ -388,7 +393,7 @@ class Solution: # left == 0 && right == 1 左节点有无覆盖,右节点摄像头 # left == 0 && right == 2 左节点无覆盖,右节点覆盖 # left == 2 && right == 0 左节点覆盖,右节点无覆盖 - elif left == 0 or right == 0: + elif left == 0 or right == 0: result += 1 return 1 @@ -398,16 +403,17 @@ class Solution: # left == 1 && right == 1 左右节点都有摄像头 elif left == 1 or right == 1: return 2 - + # 其他情况前段代码均已覆盖 if traversal(root) == 0: result += 1 - + return result ``` -### Go +### Go + ```go const inf = math.MaxInt64 / 2 @@ -437,7 +443,8 @@ func min(a, b int) int { ``` -### Javascript +### Javascript + ```Javascript var minCameraCover = function(root) { let result = 0 @@ -470,7 +477,7 @@ var minCameraCover = function(root) { } return result - + }; ``` @@ -501,7 +508,7 @@ function minCameraCover(root: TreeNode | null): number { }; ``` -### C +### C ```c /* @@ -576,7 +583,9 @@ object Solution { } } ``` + ### Rust + ```Rust /// 版本一 impl Solution { diff --git a/problems/1035.不相交的线.md b/problems/1035.不相交的线.md index 6742c8d9..d1e1e30a 100644 --- a/problems/1035.不相交的线.md +++ b/problems/1035.不相交的线.md @@ -14,7 +14,8 @@ 以这种方法绘制线条,并返回我们可以绘制的最大连线数。 -![1035.不相交的线](https://img-blog.csdnimg.cn/2021032116363533.png) + +![1035.不相交的线](https://code-thinking-1253855093.file.myqcloud.com/pics/2021032116363533.png) ## 思路 diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md index 8e40dcd5..210ce737 100644 --- a/problems/1049.最后一块石头的重量II.md +++ b/problems/1049.最后一块石头的重量II.md @@ -115,7 +115,7 @@ for (int i = 0; i < stones.size(); i++) { // 遍历物品 举例,输入:[2,4,1,1],此时target = (2 + 4 + 1 + 1)/2 = 4 ,dp数组状态图如下: -![1049.最后一块石头的重量II](https://img-blog.csdnimg.cn/20210121115805904.jpg) +![1049.最后一块石头的重量II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210121115805904.jpg) 最后dp[target]里是容量为target的背包所能背的最大重量。 diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index f2e6e7e2..8e5f7eb2 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -91,7 +91,7 @@ vector> dp(text1.size() + 1, vector(text2.size() + 1, 0)); 从递推公式,可以看出,有三个方向可以推出dp[i][j],如图: -![1143.最长公共子序列](https://img-blog.csdnimg.cn/20210204115139616.jpg) +![1143.最长公共子序列](https://code-thinking-1253855093.file.myqcloud.com/pics/20210204115139616.jpg) 那么为了在递推的过程中,这三个方向都是经过计算的数值,所以要从前向后,从上到下来遍历这个矩阵。 @@ -99,7 +99,8 @@ vector> dp(text1.size() + 1, vector(text2.size() + 1, 0)); 以输入:text1 = "abcde", text2 = "ace" 为例,dp状态如图: -![1143.最长公共子序列1](https://img-blog.csdnimg.cn/20210210150215918.jpg) + +![1143.最长公共子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210210150215918.jpg) 最后红框dp[text1.size()][text2.size()]为最终结果 diff --git a/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md b/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md index c2854ca8..a488c0ba 100644 --- a/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md +++ b/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md @@ -1,18 +1,21 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 程序提交之后为什么会超时?O(n)的算法会超时,n究竟是多大? + 一些同学可能对计算机运行的速度还没有概念,就是感觉计算机运行速度应该会很快,那么在leetcode上做算法题目的时候为什么会超时呢? 计算机究竟1s可以执行多少次操作呢? 接下来探讨一下这个问题。 # 超时是怎么回事 -![程序超时](https://img-blog.csdnimg.cn/20200729112716117.png) +![程序超时](https://code-thinking-1253855093.file.myqcloud.com/pics/20200729112716117.png) 大家在leetcode上练习算法的时候应该都遇到过一种错误是“超时”。 @@ -52,6 +55,7 @@ 尽管有很多因素影响,但是还是可以对自己程序的运行时间有一个大体的评估的。 引用算法4里面的一段话: + * 火箭科学家需要大致知道一枚试射火箭的着陆点是在大海里还是在城市中; * 医学研究者需要知道一次药物测试是会杀死还是会治愈实验对象; @@ -103,6 +107,7 @@ void function3(long long n) { ``` 来看一下这三个函数随着n的规模变化,耗时会产生多大的变化,先测function1 ,就把 function2 和 function3 注释掉 + ```CPP int main() { long long n; // 数据规模 @@ -126,11 +131,11 @@ int main() { 来看一下运行的效果,如下图: -![程序超时2](https://img-blog.csdnimg.cn/20200729200018460.png) +![程序超时2](https://code-thinking-1253855093.file.myqcloud.com/pics/20200729200018460.png) O(n)的算法,1s内大概计算机可以运行 5 * (10^8)次计算,可以推测一下O(n^2) 的算法应该1s可以处理的数量级的规模是 5 * (10^8)开根号,实验数据如下。 -![程序超时3](https://img-blog.csdnimg.cn/2020072919590970.png) +![程序超时3](https://code-thinking-1253855093.file.myqcloud.com/pics/2020072919590970.png) O(n^2)的算法,1s内大概计算机可以运行 22500次计算,验证了刚刚的推测。 @@ -138,7 +143,7 @@ O(n^2)的算法,1s内大概计算机可以运行 22500次计算,验证了刚 理论上应该是比 O(n)少一个数量级,因为logn的复杂度 其实是很快,看一下实验数据。 -![程序超时4](https://img-blog.csdnimg.cn/20200729195729407.png) +![程序超时4](https://code-thinking-1253855093.file.myqcloud.com/pics/20200729195729407.png) O(nlogn)的算法,1s内大概计算机可以运行 2 * (10^7)次计算,符合预期。 @@ -146,7 +151,7 @@ O(nlogn)的算法,1s内大概计算机可以运行 2 * (10^7)次计算,符 **整体测试数据整理如下:** -![程序超时1](https://img-blog.csdnimg.cn/20201208231559175.png) +![程序超时1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201208231559175.png) 至于O(log n)和O(n^3) 等等这些时间复杂度在1s内可以处理的多大的数据规模,大家可以自己写一写代码去测一下了。 diff --git a/problems/二叉树理论基础.md b/problems/二叉树理论基础.md index 9541baaf..3afedc82 100644 --- a/problems/二叉树理论基础.md +++ b/problems/二叉树理论基础.md @@ -1,18 +1,20 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 二叉树理论基础篇 《代码随想录》算法视频公开课:[关于二叉树,你该了解这些!](https://www.bilibili.com/video/BV1Hy4y1t7ij),相信结合视频在看本篇题解,更有助于大家对本题的理解。 -题目分类大纲如下: +题目分类大纲如下: -二叉树大纲 +二叉树大纲 说到二叉树,大家对于二叉树其实都很熟悉了,本文呢我也不想教科书式的把二叉树的基础内容再啰嗦一遍,所以以下我讲的都是一些比较重点的内容。 @@ -28,7 +30,7 @@ 如图所示: - + 这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。 @@ -37,13 +39,13 @@ 什么是完全二叉树? -完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1)  个节点。 +完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。 **大家要自己看完全二叉树的定义,很多同学对完全二叉树其实不是真正的懂了。** 我来举一个典型的例子如题: - + 相信不少同学最后一个二叉树是不是完全二叉树都中招了。 @@ -60,16 +62,16 @@ 下面这两棵树都是搜索树 - + ### 平衡二叉搜索树 -平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 +平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 如图: - + 最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。 @@ -88,13 +90,13 @@ 链式存储如图: - + 链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢? 其实就是用数组来存储二叉树,顺序存储的方式如图: - + 用数组来存储二叉树如何遍历的呢? @@ -113,6 +115,7 @@ 我这里把二叉树的几种遍历方式列出来,大家就可以一一串起来了。 二叉树主要有两种遍历方式: + 1. 深度优先遍历:先往深走,遇到叶子节点再往回走。 2. 广度优先遍历:一层一层的去遍历。 @@ -121,11 +124,11 @@ 那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式: * 深度优先遍历 - * 前序遍历(递归法,迭代法) - * 中序遍历(递归法,迭代法) - * 后序遍历(递归法,迭代法) + * 前序遍历(递归法,迭代法) + * 中序遍历(递归法,迭代法) + * 后序遍历(递归法,迭代法) * 广度优先遍历 - * 层次遍历(迭代法) + * 层次遍历(迭代法) 在深度优先遍历中:有三个顺序,前中后序遍历, 有同学总分不清这三个顺序,经常搞混,我这里教大家一个技巧。 @@ -140,7 +143,7 @@ 大家可以对着如下图,看看自己理解的前后中序有没有问题。 - + 最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。 @@ -206,8 +209,9 @@ public class TreeNode { Python: + ```python -class TreeNode: +class TreeNode: def __init__(self, value): self.value = value self.left = None @@ -215,6 +219,7 @@ class TreeNode: ``` Go: + ```go type TreeNode struct { Val int @@ -224,6 +229,7 @@ type TreeNode struct { ``` JavaScript: + ```javascript function TreeNode(val, left, right) { this.val = (val===undefined ? 0 : val) @@ -263,7 +269,9 @@ class TreeNode { } } ``` + Scala: + ```scala class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null) { var value: Int = _value diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index 0814866f..35cf4077 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -116,7 +116,7 @@ public: 再来看后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图: -![前序到后序](https://img-blog.csdnimg.cn/20200808200338924.png) +![前序到后序](https://code-thinking-1253855093.file.myqcloud.com/pics/20200808200338924.png) **所以后序遍历只需要前序遍历的代码稍作修改就可以了,代码如下:** diff --git a/problems/关于时间复杂度,你不知道的都在这里!.md b/problems/关于时间复杂度,你不知道的都在这里!.md index 246294ed..c479dddc 100644 --- a/problems/关于时间复杂度,你不知道的都在这里!.md +++ b/problems/关于时间复杂度,你不知道的都在这里!.md @@ -31,7 +31,7 @@ 同样的同理再看一下快速排序,都知道快速排序是O(nlog n),但是当数据已经有序情况下,快速排序的时间复杂度是O(n^2) 的,**所以严格从大O的定义来讲,快速排序的时间复杂度应该是O(n^2)**。 **但是我们依然说快速排序是O(nlog n)的时间复杂度,这个就是业内的一个默认规定,这里说的O代表的就是一般情况,而不是严格的上界**。如图所示: -![时间复杂度4,一般情况下的时间复杂度](https://img-blog.csdnimg.cn/20200728185745611.png) +![时间复杂度4,一般情况下的时间复杂度](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728185745611.png) 我们主要关心的还是一般情况下的数据形式。 @@ -42,7 +42,7 @@ 如下图中可以看出不同算法的时间复杂度在不同数据输入规模下的差异。 -![时间复杂度,不同数据规模的差异](https://img-blog.csdnimg.cn/20200728191447384.png) +![时间复杂度,不同数据规模的差异](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728191447384.png) 在决定使用哪些算法的时候,不是时间复杂越低的越好(因为简化后的时间复杂度忽略了常数项等等),要考虑数据规模,如果数据规模很小甚至可以用O(n^2)的算法比O(n)的更合适(在有常数项的时候)。 @@ -108,7 +108,7 @@ O(2 × n^2 + 10 × n + 1000) < O(3 × n^2),所以说最后省略掉常数项 为什么可以这么做呢?如下图所示: -![时间复杂度1.png](https://img-blog.csdnimg.cn/20200728191447349.png) +![时间复杂度1.png](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728191447349.png) 假如有两个算法的时间复杂度,分别是log以2为底n的对数和log以10为底n的对数,那么这里如果还记得高中数学的话,应该不能理解`以2为底n的对数 = 以2为底10的对数 * 以10为底n的对数`。 diff --git a/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md b/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md index 20a48e19..8b0934c5 100644 --- a/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md +++ b/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md @@ -8,7 +8,7 @@ ## 超时是怎么回事 -![程序超时](https://img-blog.csdnimg.cn/20200729112716117.png) +![程序超时](https://code-thinking-1253855093.file.myqcloud.com/pics/20200729112716117-20230310124308704.png) 大家在leetcode上练习算法的时候应该都遇到过一种错误是“超时”。 @@ -48,6 +48,7 @@ 尽管有很多因素影响,但是还是可以对自己程序的运行时间有一个大体的评估的。 引用算法4里面的一段话: + * 火箭科学家需要大致知道一枚试射火箭的着陆点是在大海里还是在城市中; * 医学研究者需要知道一次药物测试是会杀死还是会治愈实验对象; @@ -99,6 +100,7 @@ void function3(long long n) { ``` 来看一下这三个函数随着n的规模变化,耗时会产生多大的变化,先测function1 ,就把 function2 和 function3 注释掉 + ```CPP int main() { long long n; // 数据规模 @@ -122,11 +124,11 @@ int main() { 来看一下运行的效果,如下图: -![程序超时2](https://img-blog.csdnimg.cn/20200729200018460.png) +![程序超时2](https://code-thinking-1253855093.file.myqcloud.com/pics/20200729200018460-20230310124315093.png) O(n)的算法,1s内大概计算机可以运行 5 * (10^8)次计算,可以推测一下$O(n^2)$ 的算法应该1s可以处理的数量级的规模是 5 * (10^8)开根号,实验数据如下。 -![程序超时3](https://img-blog.csdnimg.cn/2020072919590970.png) +![程序超时3](https://code-thinking-1253855093.file.myqcloud.com/pics/2020072919590970-20230310124318532.png) O(n^2)的算法,1s内大概计算机可以运行 22500次计算,验证了刚刚的推测。 @@ -134,7 +136,7 @@ O(n^2)的算法,1s内大概计算机可以运行 22500次计算,验证了刚 理论上应该是比 $O(n)$少一个数量级,因为$\log n$的复杂度 其实是很快,看一下实验数据。 -![程序超时4](https://img-blog.csdnimg.cn/20200729195729407.png) +![程序超时4](https://code-thinking-1253855093.file.myqcloud.com/pics/20200729195729407-20230310124322232.png) $O(n\log n)$的算法,1s内大概计算机可以运行 2 * (10^7)次计算,符合预期。 @@ -142,7 +144,7 @@ $O(n\log n)$的算法,1s内大概计算机可以运行 2 * (10^7)次计算, **整体测试数据整理如下:** -![程序超时1](https://img-blog.csdnimg.cn/20201208231559175.png) +![程序超时1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201208231559175-20230310124325152.png) 至于 $O(\log n)$ 和 $O(n^3)$ 等等这些时间复杂度在1s内可以处理的多大的数据规模,大家可以自己写一写代码去测一下了。 @@ -204,6 +206,7 @@ int main() { Java版本 + ```Java import java.util.Scanner; @@ -274,4 +277,5 @@ public class TimeComplexity { ----------------------- +
diff --git a/problems/前序/代码风格.md b/problems/前序/代码风格.md index 196f4027..b5083460 100644 --- a/problems/前序/代码风格.md +++ b/problems/前序/代码风格.md @@ -57,7 +57,7 @@ 我做了一下总结如图: -![编程风格](https://img-blog.csdnimg.cn/20201119173039835.png) +![编程风格](https://code-thinking-1253855093.file.myqcloud.com/pics/20201119173039835.png) ### 水平留白(代码空格) diff --git a/problems/前序/关于时间复杂度,你不知道的都在这里!.md b/problems/前序/关于时间复杂度,你不知道的都在这里!.md index 72cfab54..9d00a0f2 100644 --- a/problems/前序/关于时间复杂度,你不知道的都在这里!.md +++ b/problems/前序/关于时间复杂度,你不知道的都在这里!.md @@ -38,7 +38,7 @@ 同样的同理再看一下快速排序,都知道快速排序是O(nlogn),但是当数据已经有序情况下,快速排序的时间复杂度是O(n^2) 的,**所以严格从大O的定义来讲,快速排序的时间复杂度应该是O(n^2)**。 **但是我们依然说快速排序是O(nlogn)的时间复杂度,这个就是业内的一个默认规定,这里说的O代表的就是一般情况,而不是严格的上界**。如图所示: -![时间复杂度4,一般情况下的时间复杂度](https://img-blog.csdnimg.cn/20200728185745611.png) +![时间复杂度4,一般情况下的时间复杂度](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728185745611-20230310123844306.png) 我们主要关心的还是一般情况下的数据形式。 @@ -49,7 +49,7 @@ 如下图中可以看出不同算法的时间复杂度在不同数据输入规模下的差异。 -![时间复杂度,不同数据规模的差异](https://img-blog.csdnimg.cn/20200728191447384.png) +![时间复杂度,不同数据规模的差异](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728191447384-20230310124015324.png) 在决定使用哪些算法的时候,不是时间复杂越低的越好(因为简化后的时间复杂度忽略了常数项等等),要考虑数据规模,如果数据规模很小甚至可以用O(n^2)的算法比O(n)的更合适(在有常数项的时候)。 @@ -115,7 +115,7 @@ O(2 × n^2 + 10 × n + 1000) < O(3 × n^2),所以说最后省略掉常数项 为什么可以这么做呢?如下图所示: -![时间复杂度1.png](https://img-blog.csdnimg.cn/20200728191447349.png) +![时间复杂度1.png](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728191447349-20230310124032001.png) 假如有两个算法的时间复杂度,分别是log以2为底n的对数和log以10为底n的对数,那么这里如果还记得高中数学的话,应该不难理解`以2为底n的对数 = 以2为底10的对数 * 以10为底n的对数`。 diff --git a/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md b/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md index f4aa4b6e..f70564e4 100644 --- a/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md +++ b/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md @@ -1,5 +1,4 @@ - # 刷了这么多题,你了解自己代码的内存消耗么? 理解代码的内存消耗,最关键是要知道自己所用编程语言的内存管理。 @@ -20,7 +19,7 @@ 如果我们写C++的程序,就要知道栈和堆的概念,程序运行时所需的内存空间分为 固定部分,和可变部分,如下: -![C++内存空间](https://img-blog.csdnimg.cn/20210309165950660.png) +![C++内存空间](https://code-thinking-1253855093.file.myqcloud.com/pics/20210309165950660.png) 固定部分的内存消耗 是不会随着代码运行产生变化的, 可变部分则是会产生变化的 @@ -42,7 +41,7 @@ 想要算出自己程序会占用多少内存就一定要了解自己定义的数据类型的大小,如下: -![C++数据类型的大小](https://img-blog.csdnimg.cn/20200804193045440.png) +![C++数据类型的大小](https://code-thinking-1253855093.file.myqcloud.com/pics/20200804193045440.png) 注意图中有两个不一样的地方,为什么64位的指针就占用了8个字节,而32位的指针占用4个字节呢? @@ -85,9 +84,11 @@ int main() { cout << sizeof(st) << endl; } ``` + 看一下和自己想的结果一样么, 我们来逐一分析一下。 其输出的结果依次为: + ``` 4 1 @@ -108,7 +109,7 @@ CPU读取内存不是一次读取单个字节,而是一块一块的来读取 第一种就是内存对齐的情况,如图: -![内存对齐](https://img-blog.csdnimg.cn/20200804193307347.png) +![内存对齐](https://code-thinking-1253855093.file.myqcloud.com/pics/20200804193307347.png) 一字节的char占用了四个字节,空了三个字节的内存地址,int数据从地址4开始。 @@ -116,7 +117,7 @@ CPU读取内存不是一次读取单个字节,而是一块一块的来读取 第二种是没有内存对齐的情况如图: -![非内存对齐](https://img-blog.csdnimg.cn/20200804193353926.png) +![非内存对齐](https://code-thinking-1253855093.file.myqcloud.com/pics/20200804193353926.png) char型的数据和int型的数据挨在一起,该int数据从地址1开始,那么CPU想要读这个数据的话来看看需要几步操作: @@ -143,4 +144,5 @@ char型的数据和int型的数据挨在一起,该int数据从地址1开始, ----------------------- +
diff --git a/problems/前序/程序员简历.md b/problems/前序/程序员简历.md index e64f547a..3e25b895 100644 --- a/problems/前序/程序员简历.md +++ b/problems/前序/程序员简历.md @@ -103,7 +103,7 @@ Carl校招社招都拿过大厂的offer,同时也看过很多应聘者的简 最后福利,把我的简历模板贡献出来!如下图所示。 -![简历模板](https://img-blog.csdnimg.cn/20200803175538158.png) +![简历模板](https://code-thinking-1253855093.file.myqcloud.com/pics/20200803175538158.png) 这里是简历模板中Markdown的代码:[https://github.com/youngyangyang04/Markdown-Resume-Template](https://github.com/youngyangyang04/Markdown-Resume-Template) ,可以fork到自己Github仓库上,按照这个模板来修改自己的简历。 diff --git a/problems/前序/递归算法的时间与空间复杂度分析.md b/problems/前序/递归算法的时间与空间复杂度分析.md index 142358da..aacc4568 100644 --- a/problems/前序/递归算法的时间与空间复杂度分析.md +++ b/problems/前序/递归算法的时间与空间复杂度分析.md @@ -28,7 +28,8 @@ int fibonacci(int i) { 可以看出上面的代码每次递归都是O(1)的操作。再来看递归了多少次,这里将i为5作为输入的递归过程 抽象成一棵递归树,如图: -![递归空间复杂度分析](https://img-blog.csdnimg.cn/20210305093200104.png) + +![递归空间复杂度分析](https://code-thinking-1253855093.file.myqcloud.com/pics/20210305093200104.png) 从图中,可以看出f(5)是由f(4)和f(3)相加而来,那么f(4)是由f(3)和f(2)相加而来 以此类推。 @@ -194,7 +195,8 @@ int main() 在看递归的深度是多少呢?如图所示: -![递归空间复杂度分析](https://img-blog.csdnimg.cn/20210305094749554.png) + +![递归空间复杂度分析](https://code-thinking-1253855093.file.myqcloud.com/pics/20210305094749554.png) 递归第n个斐波那契数的话,递归调用栈的深度就是n。 @@ -211,7 +213,8 @@ int fibonacci(int i) { 最后对各种求斐波那契数列方法的性能做一下分析,如题: -![递归的空间复杂度分析](https://img-blog.csdnimg.cn/20210305095227356.png) + +![递归的空间复杂度分析](https://code-thinking-1253855093.file.myqcloud.com/pics/20210305095227356.png) 可以看出,求斐波那契数的时候,使用递归算法并不一定是在性能上是最优的,但递归确实简化的代码层面的复杂度。 diff --git a/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md b/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md index b2db92f5..9f17e6fa 100644 --- a/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md +++ b/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md @@ -69,7 +69,7 @@ int function3(int x, int n) { 我们来分析一下,首先看递归了多少次呢,可以把递归抽象出一棵满二叉树。刚刚同学写的这个算法,可以用一棵满二叉树来表示(为了方便表示,选择n为偶数16),如图: -![递归算法的时间复杂度](https://img-blog.csdnimg.cn/20201209193909426.png) +![递归算法的时间复杂度](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209193909426.png) 当前这棵二叉树就是求x的n次方,n为16的情况,n为16的时候,进行了多少次乘法运算呢? @@ -79,7 +79,7 @@ int function3(int x, int n) { 这么如果是求x的n次方,这个递归树有多少个节点呢,如下图所示:(m为深度,从0开始) -![递归求时间复杂度](https://img-blog.csdnimg.cn/20200728195531892.png) +![递归求时间复杂度](https://code-thinking-1253855093.file.myqcloud.com/pics/20200728195531892.png) **时间复杂度忽略掉常数项`-1`之后,这个递归算法的时间复杂度依然是O(n)**。对,你没看错,依然是O(n)的时间复杂度! diff --git a/problems/周总结/20201003二叉树周末总结.md b/problems/周总结/20201003二叉树周末总结.md index 5f59b040..57416775 100644 --- a/problems/周总结/20201003二叉树周末总结.md +++ b/problems/周总结/20201003二叉树周末总结.md @@ -254,5 +254,5 @@ traversal(cur->left, tmp, result); * Github:[leetcode-master](https://github.com/youngyangyang04/leetcode-master) * 知乎:[代码随想录](https://www.zhihu.com/people/sun-xiu-yang-64) -![](https://img-blog.csdnimg.cn/2021013018121150.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/2021013018121150.png)
diff --git a/problems/周总结/20201107回溯周末总结.md b/problems/周总结/20201107回溯周末总结.md index 5310f9ec..76bd331b 100644 --- a/problems/周总结/20201107回溯周末总结.md +++ b/problems/周总结/20201107回溯周末总结.md @@ -1,5 +1,4 @@ - # 本周小结!(回溯算法系列二) > 例行每周小结 @@ -32,7 +31,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 在[回溯算法:求组合总和(二)](https://programmercarl.com/0039.组合总和.html)第一个树形结构没有画出startIndex的作用,**这里这里纠正一下,准确的树形结构如图所示:** -![39.组合总和](https://img-blog.csdnimg.cn/20201123202227835.png) +![39.组合总和](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123202227835.png) ## 周二 @@ -46,7 +45,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因**。 -![40.组合总和II1](https://img-blog.csdnimg.cn/20201123202817973.png) +![40.组合总和II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123202817973.png) 我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下: @@ -80,7 +79,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; **本题的树形结构中,和代码的逻辑有一个小出入,已经判断不是回文的子串就不会进入递归了,纠正如下:** -![131.分割回文串](https://img-blog.csdnimg.cn/20201123203228309.png) +![131.分割回文串](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123203228309.png) ## 周四 @@ -91,7 +90,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 树形图如下: -![93.复原IP地址](https://img-blog.csdnimg.cn/20201123203735933.png) +![93.复原IP地址](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123203735933-20230310133532452.png) 在本文的树形结构图中,我已经把详细的分析思路都画了出来,相信大家看了之后一定会思路清晰不少! @@ -113,7 +112,7 @@ if (s.size() > 12) return result; // 剪枝 如图: -![78.子集](https://img-blog.csdnimg.cn/202011232041348.png) +![78.子集](https://code-thinking-1253855093.file.myqcloud.com/pics/202011232041348.png) 认清这个本质之后,今天的题目就是一道模板题了。 diff --git a/problems/周总结/20201112回溯周末总结.md b/problems/周总结/20201112回溯周末总结.md index af08097b..ec36d121 100644 --- a/problems/周总结/20201112回溯周末总结.md +++ b/problems/周总结/20201112回溯周末总结.md @@ -1,5 +1,4 @@ - # 本周小结!(回溯算法系列三) ## 周一 @@ -12,14 +11,14 @@ 树形结构如下: -![90.子集II](https://img-blog.csdnimg.cn/2020111217110449.png) +![90.子集II](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111217110449-20230310133150714.png) ## 周二 在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨! 树形结构如下: -![491. 递增子序列1](https://img-blog.csdnimg.cn/20201112170832333.png) +![491. 递增子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112170832333-20230310133155209.png) [回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)留言区大家有很多疑问,主要还是和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混合在了一起。 @@ -34,7 +33,7 @@ 可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。 如图: -![46.全排列](https://img-blog.csdnimg.cn/20201112170304979.png) +![46.全排列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112170304979-20230310133201250.png) **大家此时可以感受出排列问题的不同:** @@ -47,7 +46,7 @@ 树形结构如下: -![47.全排列II1](https://img-blog.csdnimg.cn/20201112171930470.png) +![47.全排列II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112171930470-20230310133206398.png) **这道题目神奇的地方就是used[i - 1] == false也可以,used[i - 1] == true也可以!** @@ -55,11 +54,11 @@ 树层上去重(used[i - 1] == false),的树形结构如下: -![47.全排列II2.png](https://img-blog.csdnimg.cn/20201112172230434.png) +![47.全排列II2.png](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112172230434-20230310133211392.png) 树枝上去重(used[i - 1] == true)的树型结构如下: -![47.全排列II3](https://img-blog.csdnimg.cn/20201112172327967.png) +![47.全排列II3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112172327967-20230310133216389.png) **可以清晰的看到使用(used[i - 1] == false),即树层去重,效率更高!** @@ -72,14 +71,17 @@ **所以这块就说一说我个人理解,对内容持开放态度,集思广益,欢迎大家来讨论!** 子集问题分析: + * 时间复杂度:$O(n × 2^n)$,因为每一个元素的状态无外乎取与不取,所以时间复杂度为$O(2^n)$,构造每一组子集都需要填进数组,又有需要$O(n)$,最终时间复杂度:$O(n × 2^n)$。 * 空间复杂度:$O(n)$,递归深度为n,所以系统栈所用空间为$O(n)$,每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为$O(n)$。 排列问题分析: + * 时间复杂度:$O(n!)$,这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。每个叶子节点都会有一个构造全排列填进数组的操作(对应的代码:`result.push_back(path)`),该操作的复杂度为$O(n)$。所以,最终时间复杂度为:n * n!,简化为$O(n!)$。 * 空间复杂度:$O(n)$,和子集问题同理。 组合问题分析: + * 时间复杂度:$O(n × 2^n)$,组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。 * 空间复杂度:$O(n)$,和子集问题同理。 diff --git a/problems/周总结/20201203贪心周末总结.md b/problems/周总结/20201203贪心周末总结.md index 10e2d1bb..2918246a 100644 --- a/problems/周总结/20201203贪心周末总结.md +++ b/problems/周总结/20201203贪心周末总结.md @@ -1,5 +1,4 @@ - # 本周小结!(贪心算法系列二) ## 周一 @@ -16,7 +15,7 @@ 如图: -![122.买卖股票的最佳时机II](https://img-blog.csdnimg.cn/2020112917480858.png) +![122.买卖股票的最佳时机II](https://code-thinking-1253855093.file.myqcloud.com/pics/2020112917480858.png) ## 周二 @@ -32,7 +31,7 @@ 如图: -![55.跳跃游戏](https://img-blog.csdnimg.cn/20201124154758229.png) +![55.跳跃游戏](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124154758229.png) ## 周三 @@ -45,7 +44,7 @@ 如图: -![45.跳跃游戏II](https://img-blog.csdnimg.cn/20201201232309103.png) +![45.跳跃游戏II](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232309103-20230310133110942.png) 注意:**图中的移动下标是到当前这步覆盖的最远距离(下标2的位置),此时没有到终点,只能增加第二步来扩大覆盖范围**。 @@ -56,10 +55,10 @@ 而版本二就比较统一的,超过范围,步数就加一,但在移动下标的范围了做了文章。 即如果覆盖最远距离下标是倒数第二点:直接加一就行,默认一定可以到终点。如图: -![45.跳跃游戏II2](https://img-blog.csdnimg.cn/20201201232445286.png) +![45.跳跃游戏II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232445286-20230310133115650.png) 如果覆盖最远距离下标不是倒数第二点,说明本次覆盖已经到终点了。如图: -![45.跳跃游戏II1](https://img-blog.csdnimg.cn/20201201232338693.png) +![45.跳跃游戏II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201201232338693-20230310133120115.png) 有的录友认为版本一好理解,有的录友认为版本二好理解,其实掌握一种就可以了,也不用非要比拼一下代码的简洁性,简洁程度都差不多了。 diff --git a/problems/周总结/20201210复杂度分析周末总结.md b/problems/周总结/20201210复杂度分析周末总结.md index 5e5f696d..203e4486 100644 --- a/problems/周总结/20201210复杂度分析周末总结.md +++ b/problems/周总结/20201210复杂度分析周末总结.md @@ -76,7 +76,8 @@ 文中从计算机硬件出发,分析计算机的计算性能,然后亲自做实验,整理出数据如下: -![程序超时1](https://img-blog.csdnimg.cn/20201208231559175.png) + +![程序超时1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201208231559175-20230310133304038.png) **大家有一个数量级上的概念就可以了!** diff --git a/problems/周总结/20201217贪心周末总结.md b/problems/周总结/20201217贪心周末总结.md index 4d12f92a..fe4cc4a2 100644 --- a/problems/周总结/20201217贪心周末总结.md +++ b/problems/周总结/20201217贪心周末总结.md @@ -37,7 +37,8 @@ 先贪心一边,局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果 如图: -![135.分发糖果](https://img-blog.csdnimg.cn/20201117114916878.png) + +![135.分发糖果](https://code-thinking-1253855093.file.myqcloud.com/pics/20201117114916878-20230310133332759.png) 接着在贪心另一边,左孩子大于右孩子,左孩子的糖果就要比右孩子多。 @@ -49,7 +50,7 @@ 局部最优可以推出全局最优。 如图: -![135.分发糖果1](https://img-blog.csdnimg.cn/20201117115658791.png) +![135.分发糖果1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201117115658791-20230310133346127.png) ## 周三 diff --git a/problems/周总结/20201224贪心周末总结.md b/problems/周总结/20201224贪心周末总结.md index 1bc39247..7436295a 100644 --- a/problems/周总结/20201224贪心周末总结.md +++ b/problems/周总结/20201224贪心周末总结.md @@ -1,5 +1,4 @@ - # 本周小结!(贪心算法系列四) ## 周一 @@ -10,7 +9,7 @@ 如图: -![452.用最少数量的箭引爆气球](https://img-blog.csdnimg.cn/20201123101929791.png) +![452.用最少数量的箭引爆气球](https://code-thinking-1253855093.file.myqcloud.com/pics/20201123101929791-20230310133845522.png) 模拟射气球的过程,很多同学真的要去模拟了,实时把气球从数组中移走,这么写的话就复杂了,从前向后遍历重复的只要跳过就可以的。 @@ -22,7 +21,7 @@ 如图: -![435.无重叠区间](https://img-blog.csdnimg.cn/20201221201553618.png) +![435.无重叠区间](https://code-thinking-1253855093.file.myqcloud.com/pics/20201221201553618.png) 细心的同学就发现了,此题和 [贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)非常像。 @@ -31,6 +30,7 @@ 把[贪心算法:用最少数量的箭引爆气球](https://programmercarl.com/0452.用最少数量的箭引爆气球.html)代码稍做修改,就可以AC本题。 修改后的C++代码如下: + ```CPP class Solution { public: @@ -71,7 +71,7 @@ public: 如图: -![763.划分字母区间](https://img-blog.csdnimg.cn/20201222191924417.png) +![763.划分字母区间](https://code-thinking-1253855093.file.myqcloud.com/pics/20201222191924417-20230310133855435.png) ## 周四 @@ -86,7 +86,7 @@ public: 如图: -![56.合并区间](https://img-blog.csdnimg.cn/20201223200632791.png) +![56.合并区间](https://code-thinking-1253855093.file.myqcloud.com/pics/20201223200632791-20230310133859587.png) ## 总结 diff --git a/problems/周总结/20210114动规周末总结.md b/problems/周总结/20210114动规周末总结.md index 039f3596..fb497864 100644 --- a/problems/周总结/20210114动规周末总结.md +++ b/problems/周总结/20210114动规周末总结.md @@ -30,7 +30,7 @@ for (int i = 1; i < m; i++) { } ``` -![62.不同路径1](https://img-blog.csdnimg.cn/20201209113631392.png) +![62.不同路径1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201209113631392-20230310133703294.png) ## 周二 @@ -44,7 +44,7 @@ dp[i][j]定义依然是:表示从(0 ,0)出发,到(i, j) 有dp[i][j]条 如图: -![63.不同路径II](https://img-blog.csdnimg.cn/20210104114513928.png) +![63.不同路径II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114513928-20230310133707783.png) 这里难住了不少同学,代码如下: @@ -69,11 +69,11 @@ for (int i = 1; i < m; i++) { 拿示例1来举例如题: -![63.不同路径II1](https://img-blog.csdnimg.cn/20210104114548983.png) +![63.不同路径II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114548983-20230310133711888.png) 对应的dp table 如图: -![63.不同路径II2](https://img-blog.csdnimg.cn/20210104114610256.png) +![63.不同路径II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104114610256-20230310133715981.png) ## 周三 @@ -107,9 +107,10 @@ for (int i = 3; i <= n ; i++) { } } ``` + 举例当n为10 的时候,dp数组里的数值,如下: -![343.整数拆分](https://img-blog.csdnimg.cn/20210104173021581.png) +![343.整数拆分](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104173021581-20230310133720552.png) @@ -141,7 +142,7 @@ dp数组如何初始化:只需要初始化dp[0]就可以了,推导的基础 n为5时候的dp数组状态如图: -![96.不同的二叉搜索树3](https://img-blog.csdnimg.cn/20210107093253987.png) +![96.不同的二叉搜索树3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210107093253987-20230310133724531.png) ## 总结 diff --git a/problems/周总结/20210121动规周末总结.md b/problems/周总结/20210121动规周末总结.md index e9427142..7bae5ca9 100644 --- a/problems/周总结/20210121动规周末总结.md +++ b/problems/周总结/20210121动规周末总结.md @@ -1,4 +1,6 @@ + # 本周小结!(动态规划系列三) + 本周我们正式开始讲解背包问题,也是动规里非常重要的一类问题。 背包问题其实有很多细节,如果了解个大概,然后也能一气呵成把代码写出来,但稍稍变变花样可能会陷入迷茫了。 @@ -15,7 +17,7 @@ 关于其他几种常用的背包,大家看这张图就了然于胸了: -![416.分割等和子集1](https://img-blog.csdnimg.cn/20210117171307407.png) +![416.分割等和子集1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210117171307407-20230310133624872.png) 本文用动规五部曲详细讲解了01背包的二维dp数组的实现方法,大家其实可以发现最简单的是推导公式了,推导公式估计看一遍就记下来了,但难就难在确定初始化和遍历顺序上。 @@ -61,14 +63,14 @@ for(int i = 1; i < weight.size(); i++) { // 遍历物品 物品为: | | 重量 | 价值 | -| --- | --- | --- | +| ----- | ---- | ---- | | 物品0 | 1 | 15 | | 物品1 | 3 | 20 | | 物品2 | 4 | 30 | 来看一下对应的dp数组的数值,如图: -![动态规划-背包问题4](https://img-blog.csdnimg.cn/20210118163425129.jpg) +![动态规划-背包问题4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210118163425129-20230310133630224.jpg) 最终结果就是dp[2][4]。 @@ -120,7 +122,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下: -![动态规划-背包问题9](https://img-blog.csdnimg.cn/20210110103614769.png) +![动态规划-背包问题9](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110103614769-20230310133634873.png) ## 周三 diff --git a/problems/周总结/20210128动规周末总结.md b/problems/周总结/20210128动规周末总结.md index 83bddaff..e785af12 100644 --- a/problems/周总结/20210128动规周末总结.md +++ b/problems/周总结/20210128动规周末总结.md @@ -35,7 +35,7 @@ bagSize = (S + sum) / 2 = (3 + 5) / 2 = 4 dp数组状态变化如下: -![494.目标和](https://img-blog.csdnimg.cn/20210125120743274.jpg) +![494.目标和](https://code-thinking-1253855093.file.myqcloud.com/pics/20210125120743274-20230310132918821.jpg) ## 周二 @@ -72,7 +72,8 @@ dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1); 最后dp数组的状态如下所示: -![474.一和零](https://img-blog.csdnimg.cn/20210120111201512.jpg) + +![474.一和零](https://code-thinking-1253855093.file.myqcloud.com/pics/20210120111201512-20230310132936011.jpg) ## 周三 diff --git a/problems/周总结/20210225动规周末总结.md b/problems/周总结/20210225动规周末总结.md index 21cc53ad..8fd292ed 100644 --- a/problems/周总结/20210225动规周末总结.md +++ b/problems/周总结/20210225动规周末总结.md @@ -29,7 +29,7 @@ dp[1] = max(nums[0], nums[1]); 以示例二,输入[2,7,9,3,1]为例。 -![198.打家劫舍](https://img-blog.csdnimg.cn/20210221170954115.jpg) +![198.打家劫舍](https://code-thinking-1253855093.file.myqcloud.com/pics/20210221170954115-20230310133425353.jpg) 红框dp[nums.size() - 1]为结果。 @@ -41,15 +41,15 @@ dp[1] = max(nums[0], nums[1]); * 情况一:考虑不包含首尾元素 -![213.打家劫舍II](https://img-blog.csdnimg.cn/20210129160748643.jpg) +![213.打家劫舍II](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129160748643.jpg) * 情况二:考虑包含首元素,不包含尾元素 -![213.打家劫舍II1](https://img-blog.csdnimg.cn/20210129160821374.jpg) +![213.打家劫舍II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129160821374.jpg) * 情况三:考虑包含尾元素,不包含首元素 -![213.打家劫舍II2](https://img-blog.csdnimg.cn/20210129160842491.jpg) +![213.打家劫舍II2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129160842491.jpg) 需要注意的是,**“考虑” 不等于 “偷”**,例如情况三,虽然是考虑包含尾元素,但不一定要选尾部元素!对于情况三,取nums[1] 和 nums[3]就是最大的。 @@ -135,9 +135,11 @@ dp数组含义:下标为0记录不偷该节点所得到的的最大金钱, 2. 确定终止条件 在遍历的过程中,如果遇到空间点的话,很明显,无论偷还是不偷都是0,所以就返回 + ``` if (cur == NULL) return vector{0, 0}; ``` + 3. 确定遍历顺序 采用后序遍历,代码如下: @@ -175,7 +177,7 @@ return {val2, val1}; 以示例1为例,dp数组状态如下:(**注意用后序遍历的方式推导**) -![337.打家劫舍III](https://img-blog.csdnimg.cn/20210129181331613.jpg) +![337.打家劫舍III](https://code-thinking-1253855093.file.myqcloud.com/pics/20210129181331613.jpg) **最后头结点就是 取下标0 和 下标1的最大值就是偷得的最大金钱**。 @@ -196,6 +198,7 @@ return {val2, val1}; 这里我给出了三种解法: 暴力解法代码: + ```CPP class Solution { public: diff --git a/problems/周总结/20210304动规周末总结.md b/problems/周总结/20210304动规周末总结.md index c912b92d..b814e1b3 100644 --- a/problems/周总结/20210304动规周末总结.md +++ b/problems/周总结/20210304动规周末总结.md @@ -76,7 +76,7 @@ dp[0][4] = 0; 以输入[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.png) 可以看到红色框为最后两次卖出的状态。 @@ -142,7 +142,8 @@ for (int j = 1; j < 2 * k; j += 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-20230310133805763.png) 最后一次卖出,一定是利润最大的,dp[prices.size() - 1][2 * k]即红色部分就是最后求解。 @@ -194,7 +195,8 @@ vector> dp(n, vector(3, 0)); 以 [1,2,3,0,2] 为例,dp数组如下: -![309.最佳买卖股票时机含冷冻期](https://img-blog.csdnimg.cn/20201229163725348.png) + +![309.最佳买卖股票时机含冷冻期](https://code-thinking-1253855093.file.myqcloud.com/pics/20201229163725348.png) 最后两个状态 不持有股票(能购买) 和 不持有股票(冷冻期)都有可能最后结果,取最大的。 diff --git a/problems/哈希表理论基础.md b/problems/哈希表理论基础.md index 41647d48..123fb531 100644 --- a/problems/哈希表理论基础.md +++ b/problems/哈希表理论基础.md @@ -1,3 +1,4 @@ +

@@ -6,6 +7,7 @@ + ## 哈希表 首先什么是 哈希表,哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。 @@ -16,7 +18,7 @@ 哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示: -![哈希表1](https://img-blog.csdnimg.cn/20210104234805168.png) +![哈希表1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104234805168.png) 那么哈希表能解决什么问题呢,**一般哈希表都是用来快速判断一个元素是否出现集合里。** @@ -34,7 +36,7 @@ 哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。 -![哈希表2](https://img-blog.csdnimg.cn/2021010423484818.png) +![哈希表2](https://code-thinking-1253855093.file.myqcloud.com/pics/2021010423484818.png) 如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢? @@ -50,7 +52,7 @@ 如图所示,小李和小王都映射到了索引下标 1 的位置,**这一现象叫做哈希碰撞**。 -![哈希表3](https://img-blog.csdnimg.cn/2021010423494884.png) +![哈希表3](https://code-thinking-1253855093.file.myqcloud.com/pics/2021010423494884.png) 一般哈希碰撞有两种解决方法, 拉链法和线性探测法。 @@ -58,7 +60,7 @@ 刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了 -![哈希表4](https://img-blog.csdnimg.cn/20210104235015226.png) +![哈希表4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104235015226.png) (数据规模是dataSize, 哈希表的大小为tableSize) @@ -70,7 +72,7 @@ 例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示: -![哈希表5](https://img-blog.csdnimg.cn/20210104235109950.png) +![哈希表5](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104235109950.png) 其实关于哈希碰撞还有非常多的细节,感兴趣的同学可以再好好研究一下,这里我就不再赘述了。 @@ -86,19 +88,19 @@ 在C++中,set 和 map 分别提供以下三种数据结构,其底层实现以及优劣如下表所示: -|集合 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率| -|---|---| --- |---| --- | --- | ---| -|std::set |红黑树 |有序 |否 |否 | O(log n)|O(log n) | -|std::multiset | 红黑树|有序 |是 | 否| O(logn) |O(logn) | -|std::unordered_set |哈希表 |无序 |否 |否 |O(1) | O(1)| +| 集合 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 | +| ------------------ | -------- | -------- | ---------------- | ------------ | -------- | -------- | +| std::set | 红黑树 | 有序 | 否 | 否 | O(log n) | O(log n) | +| std::multiset | 红黑树 | 有序 | 是 | 否 | O(logn) | O(logn) | +| std::unordered_set | 哈希表 | 无序 | 否 | 否 | O(1) | O(1) | std::unordered_set底层实现为哈希表,std::set 和std::multiset 的底层实现是红黑树,红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。 -|映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率| -|---|---| --- |---| --- | --- | ---| -|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(logn)|O(logn) | -|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(log n) |O(log n) | -|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)| +| 映射 | 底层实现 | 是否有序 | 数值是否可以重复 | 能否更改数值 | 查询效率 | 增删效率 | +| ------------------ | -------- | -------- | ---------------- | ------------ | -------- | -------- | +| std::map | 红黑树 | key有序 | key不可重复 | key不可修改 | O(logn) | O(logn) | +| std::multimap | 红黑树 | key有序 | key可重复 | key不可修改 | O(log n) | O(log n) | +| std::unordered_map | 哈希表 | key无序 | key不可重复 | key不可修改 | O(1) | O(1) | std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 @@ -114,7 +116,7 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底 实际上功能都是一样一样的, 但是unordered_set在C++11的时候被引入标准库了,而hash_set并没有,所以建议还是使用unordered_set比较好,这就好比一个是官方认证的,hash_set,hash_map 是C++11标准之前民间高手自发造的轮子。 -![哈希表6](https://img-blog.csdnimg.cn/20210104235134572.png) +![哈希表6](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104235134572.png) ## 总结 diff --git a/problems/回溯总结.md b/problems/回溯总结.md index 58fb42f8..21d78bc2 100644 --- a/problems/回溯总结.md +++ b/problems/回溯总结.md @@ -1,12 +1,14 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ > 20张树形结构图、14道精选回溯题目,21篇回溯法精讲文章,由浅入深,一气呵成,这是全网最强回溯算法总结! -# 回溯法理论基础 +# 回溯法理论基础 转眼间[「代码随想录」](https://img-blog.csdnimg.cn/20200815195519696.png)里已经分享连续讲解了21天的回溯算法,是时候做一个大总结了,本篇高能,需要花费很大的精力来看! @@ -49,9 +51,9 @@ void backtracking(参数) { **事实证明这个模板会伴随整个回溯法系列!** -# 组合问题 +# 组合问题 -## 组合问题 +## 组合问题 在[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)中,我们开始用回溯法解决第一道题目:组合问题。 @@ -61,17 +63,17 @@ void backtracking(参数) { 本题我把回溯问题抽象为树形结构,如题: -![77.组合1](https://img-blog.csdnimg.cn/20201118152928844.png) +![77.组合1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118152928844.png) 可以直观的看出其搜索的过程:**for循环横向遍历,递归纵向遍历,回溯不断调整结果集**,这个理念贯穿整个回溯法系列,也是我做了很多回溯的题目,不断摸索其规律才总结出来的。 -对于回溯法的整体框架,网上搜的文章这块都说不清楚,按照天上掉下来的代码对着讲解,不知道究竟是怎么来的,也不知道为什么要这么写。 +对于回溯法的整体框架,网上搜的文章这块都说不清楚,按照天上掉下来的代码对着讲解,不知道究竟是怎么来的,也不知道为什么要这么写。 **所以,录友们刚开始学回溯法,起跑姿势就很标准了!** 优化回溯算法只有剪枝一种方法,在[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中把回溯法代码做了剪枝优化,树形结构如图: -![77.组合4](https://img-blog.csdnimg.cn/20201118153133458.png) +![77.组合4](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118153133458.png) 大家可以一目了然剪的究竟是哪里。 @@ -87,11 +89,11 @@ void backtracking(参数) { 在[回溯算法:求组合总和!](https://programmercarl.com/0216.组合总和III.html)中,相当于 [回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)加了一个元素总和的限制。 树形结构如图: -![216.组合总和III](https://img-blog.csdnimg.cn/20201118201921245.png) +![216.组合总和III](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118201921245.png) 整体思路还是一样的,本题的剪枝会好想一些,即:**已选元素总和如果已经大于n(题中要求的和)了,那么往后遍历就没有意义了,直接剪掉**,如图: -![216.组合总和III1](https://img-blog.csdnimg.cn/20201118202038240.png) +![216.组合总和III1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118202038240.png) 在本题中,依然还可以有一个剪枝,就是[回溯算法:组合问题再剪剪枝](https://programmercarl.com/0077.组合优化.html)中提到的,对for循环选择的起始范围的剪枝。 @@ -113,7 +115,7 @@ void backtracking(参数) { 树形结构如下: -![39.组合总和](https://img-blog.csdnimg.cn/20201118152521990.png) +![39.组合总和](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118152521990.png) 最后还给出了本题的剪枝优化,如下: @@ -123,7 +125,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 优化后树形结构如下: -![39.组合总和1](https://img-blog.csdnimg.cn/20201118202115929.png) +![39.组合总和1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118202115929.png) ### 组合总和(三) @@ -138,7 +140,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上“使用过”,一个维度是同一树层上“使用过”。**没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因**。 -![40.组合总和II1](https://img-blog.csdnimg.cn/2020111820220675.png) +![40.组合总和II1](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111820220675.png) 我在图中将used的变化用橘黄色标注上,**可以看出在candidates[i] == candidates[i - 1]相同的情况下:** @@ -159,7 +161,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 树形结构如下: -![17. 电话号码的字母组合](https://img-blog.csdnimg.cn/20201118202335724.png) +![17. 电话号码的字母组合](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118202335724.png) 如果大家在现场面试的时候,一定要注意各种输入异常的情况,例如本题输入1 * #按键。 @@ -187,10 +189,10 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 树形结构如下: -![131.分割回文串](https://img-blog.csdnimg.cn/20201118202448642.png) +![131.分割回文串](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118202448642.png) -# 子集问题 +# 子集问题 ## 子集问题(一) @@ -198,7 +200,7 @@ for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; 如图: -![78.子集](https://img-blog.csdnimg.cn/20201118202544339.png) +![78.子集](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118202544339.png) 认清这个本质之后,今天的题目就是一道模板题了。 @@ -225,23 +227,23 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 树形结构如下: -![90.子集II](https://img-blog.csdnimg.cn/2020111217110449.png) +![90.子集II](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111217110449.png) ## 递增子序列 在[回溯算法:递增子序列](https://programmercarl.com/0491.递增子序列.html)中,处处都能看到子集的身影,但处处是陷阱,值得好好琢磨琢磨! 树形结构如下: -![491. 递增子序列1](https://img-blog.csdnimg.cn/20201112170832333.png) +![491. 递增子序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112170832333.png) 很多同学都会把这道题目和[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)混在一起。 -**[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?** +**[回溯算法:求子集问题(二)](https://programmercarl.com/0090.子集II.html)也可以使用set针对同一父节点本层去重,但子集问题一定要排序,为什么呢?** 我用没有排序的集合{2,1,2,2}来举个例子画一个图,如下: -![90.子集II2](https://img-blog.csdnimg.cn/2020111316440479.png) +![90.子集II2](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111316440479.png) **相信这个图胜过千言万语的解释了**。 @@ -257,7 +259,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 如图: -![46.全排列](https://img-blog.csdnimg.cn/20201112170304979.png) +![46.全排列](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112170304979.png) **大家此时可以感受出排列问题的不同:** @@ -270,7 +272,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 树形结构如下: -![47.全排列II1](https://img-blog.csdnimg.cn/20201112171930470.png) +![47.全排列II1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112171930470.png) **这道题目神奇的地方就是used[i - 1] == false也可以,used[i - 1] == true也可以!** @@ -278,21 +280,21 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 树层上去重(used[i - 1] == false),的树形结构如下: -![47.全排列II2.png](https://img-blog.csdnimg.cn/20201112172230434.png) +![47.全排列II2.png](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112172230434.png) 树枝上去重(used[i - 1] == true)的树型结构如下: -![47.全排列II3](https://img-blog.csdnimg.cn/20201112172327967.png) +![47.全排列II3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201112172327967.png) **可以清晰的看到使用(used[i - 1] == false),即树层去重,效率更高!** 本题used数组即是记录path里都放了哪些元素,同时也用来去重,一举两得。 -# 去重问题 +# 去重问题 以上我都是统一使用used数组来去重的,其实使用set也可以用来去重! -在[本周小结!(回溯算法系列三)续集](https://programmercarl.com/回溯算法去重问题的另一种写法.html)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。 +在[本周小结!(回溯算法系列三)续集](https://programmercarl.com/回溯算法去重问题的另一种写法.html)中给出了子集、组合、排列问题使用set来去重的解法以及具体代码,并纠正一些同学的常见错误写法。 同时详细分析了 使用used数组去重 和 使用set去重 两种写法的性能差异: @@ -304,7 +306,7 @@ if (startIndex >= nums.size()) { // 终止条件可以不加 **使用set去重,不仅时间复杂度高了,空间复杂度也高了**,在[本周小结!(回溯算法系列三)](https://programmercarl.com/周总结/20201112回溯周末总结.html)中分析过,组合,子集,排列问题的空间复杂度都是O(n),但如果使用set去重,空间复杂度就变成了O(n^2),因为每一层递归都有一个set集合,系统栈空间是n,每一个空间都有set集合。 -那有同学可能疑惑 用used数组也是占用O(n)的空间啊? +那有同学可能疑惑 用used数组也是占用O(n)的空间啊? used数组可是全局变量,每层与每层之间公用一个used数组,所以空间复杂度是O(n + n),最终空间复杂度还是O(n)。 @@ -316,7 +318,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 以输入:[["JFK", "KUL"], ["JFK", "NRT"], ["NRT", "JFK"]为例,抽象为树形结构如下: -![](https://img-blog.csdnimg.cn/2020111518065555.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111518065555.png) 本题可以算是一道hard的题目了,关于本题的难点我在文中已经详细列出。 @@ -325,19 +327,19 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 本题其实是一道深度优先搜索的题目,但是我完全使用回溯法的思路来讲解这道题题目,**算是给大家拓展一下思维方式,其实深搜和回溯也是分不开的,毕竟最终都是用递归**。 -# 棋盘问题 +# 棋盘问题 -## N皇后问题 +## N皇后问题 在[回溯算法:N皇后问题](https://programmercarl.com/0051.N皇后.html)中终于迎来了传说中的N皇后。 下面我用一个3 * 3 的棋盘,将搜索过程抽象为一棵树,如图: -![51.N皇后](https://img-blog.csdnimg.cn/20201118225433127.png) +![51.N皇后](https://code-thinking-1253855093.file.myqcloud.com/pics/20201118225433127.png) 从图中,可以看出,二维矩阵中矩阵的高就是这棵树的高度,矩阵的宽就是树形结构中每一个节点的宽度。 -那么我们用皇后们的约束条件,来回溯搜索这棵树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了**。 +那么我们用皇后们的约束条件,来回溯搜索这棵树,**只要搜索到了树的叶子节点,说明就找到了皇后们的合理位置了**。 如果从来没有接触过N皇后问题的同学看着这样的题会感觉无从下手,可能知道要用回溯法,但也不知道该怎么去搜。 @@ -361,7 +363,7 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 因为这个树形结构太大了,我抽取一部分,如图所示: -![37.解数独](https://img-blog.csdnimg.cn/2020111720451790.png) +![37.解数独](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111720451790.png) 解数独可以说是非常难的题目了,如果还一直停留在一维递归的逻辑中,这道题目可以让大家瞬间崩溃。 @@ -380,22 +382,27 @@ used数组可是全局变量,每层与每层之间公用一个used数组,所 以下在计算空间复杂度的时候我都把系统栈(不是数据结构里的栈)所占空间算进去。 子集问题分析: + * 时间复杂度:O(2^n),因为每一个元素的状态无外乎取与不取,所以时间复杂度为O(2^n) * 空间复杂度:O(n),递归深度为n,所以系统栈所用空间为O(n),每一层递归所用的空间都是常数级别,注意代码里的result和path都是全局变量,就算是放在参数里,传的也是引用,并不会新申请内存空间,最终空间复杂度为O(n) 排列问题分析: + * 时间复杂度:O(n!),这个可以从排列的树形图中很明显发现,每一层节点为n,第二层每一个分支都延伸了n-1个分支,再往下又是n-2个分支,所以一直到叶子节点一共就是 n * n-1 * n-2 * ..... 1 = n!。 * 空间复杂度:O(n),和子集问题同理。 组合问题分析: + * 时间复杂度:O(2^n),组合问题其实就是一种子集的问题,所以组合问题最坏的情况,也不会超过子集问题的时间复杂度。 * 空间复杂度:O(n),和子集问题同理。 -N皇后问题分析: +N皇后问题分析: + * 时间复杂度:O(n!) ,其实如果看树形图的话,直觉上是O(n^n),但皇后之间不能见面所以在搜索的过程中是有剪枝的,最差也就是O(n!),n!表示n * (n-1) * .... * 1。 * 空间复杂度:O(n),和子集问题同理。 解数独问题分析: + * 时间复杂度:O(9^m) , m是'.'的数目。 * 空间复杂度:O(n^2),递归的深度是n^2 @@ -403,7 +410,7 @@ N皇后问题分析: **一般说道回溯算法的复杂度,都说是指数级别的时间复杂度,这也算是一个概括吧!** -# 总结 +# 总结 **[「代码随想录」](https://img-blog.csdnimg.cn/20200815195519696.png)历时21天,14道经典题目分析,20张树形图,21篇回溯法精讲文章,从组合到切割,从子集到排列,从棋盘问题到最后的复杂度分析**,至此收尾了。 @@ -412,11 +419,12 @@ N皇后问题分析: 可以说方方面面都详细介绍到了。 例如: + * 如何理解回溯法的搜索过程? -* 什么时候用startIndex,什么时候不用? -* 如何去重?如何理解“树枝去重”与“树层去重”? +* 什么时候用startIndex,什么时候不用? +* 如何去重?如何理解“树枝去重”与“树层去重”? * 去重的几种方法? -* 如何理解二维递归? +* 如何理解二维递归? **这里的每一个问题,网上几乎找不到能讲清楚的文章,这也是直击回溯算法本质的问题**。 @@ -424,11 +432,11 @@ N皇后问题分析: 此时回溯算法系列就要正式告一段落了。 -**录友们可以回顾一下这21天,每天的打卡,每天在交流群里和大家探讨代码,最终换来的都是不知不觉的成长**。 +**录友们可以回顾一下这21天,每天的打卡,每天在交流群里和大家探讨代码,最终换来的都是不知不觉的成长**。 同样也感谢录友们的坚持,这也是我持续写作的动力,**正是因为大家的积极参与,我才知道这件事件是非常有意义的**。 -回溯专题汇聚为一张图: +回溯专题汇聚为一张图: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211030124742.png) diff --git a/problems/回溯算法去重问题的另一种写法.md b/problems/回溯算法去重问题的另一种写法.md index 73862156..69a28ad9 100644 --- a/problems/回溯算法去重问题的另一种写法.md +++ b/problems/回溯算法去重问题的另一种写法.md @@ -16,7 +16,8 @@ 我用没有排序的集合{2,1,2,2}来举例子画一个图,如图: -![90.子集II2](https://img-blog.csdnimg.cn/2020111316440479.png) + +![90.子集II2](https://code-thinking-1253855093.file.myqcloud.com/pics/2020111316440479-20230310121930316.png) 图中,大家就很明显的看到,子集重复了。 @@ -96,7 +97,7 @@ private: 如图: -![90.子集II1](https://img-blog.csdnimg.cn/202011131625054.png) +![90.子集II1](https://code-thinking-1253855093.file.myqcloud.com/pics/202011131625054.png) 可以看出一旦把unordered_set uset放在类成员位置,它控制的就是整棵树,包括树枝。 diff --git a/problems/回溯算法理论基础.md b/problems/回溯算法理论基础.md index aba21fa0..f11dbaef 100644 --- a/problems/回溯算法理论基础.md +++ b/problems/回溯算法理论基础.md @@ -8,7 +8,7 @@ ## 题目分类大纲如下: -回溯算法大纲 +回溯算法大纲 可以配合我的B站视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/) 一起学习! @@ -110,7 +110,7 @@ if (终止条件) { 如图: -![回溯算法理论基础](https://img-blog.csdnimg.cn/20210130173631174.png) +![回溯算法理论基础](https://code-thinking-1253855093.file.myqcloud.com/pics/20210130173631174.png) 注意图中,我特意举例集合大小和孩子的数量是相等的! diff --git a/problems/数组理论基础.md b/problems/数组理论基础.md index c49e9ec8..67b7b20d 100644 --- a/problems/数组理论基础.md +++ b/problems/数组理论基础.md @@ -82,7 +82,8 @@ int main() { 如图: -![数组内存](https://img-blog.csdnimg.cn/20210310150641186.png) + +![数组内存](https://code-thinking-1253855093.file.myqcloud.com/pics/20210310150641186.png) **所以可以看出在C++中二维数组在地址空间上是连续的**。 @@ -112,7 +113,8 @@ public static void test_arr() { 所以Java的二维数组可能是如下排列的方式: -![算法通关数组3](https://img-blog.csdnimg.cn/20201214111631844.png) + +![算法通关数组3](https://code-thinking-1253855093.file.myqcloud.com/pics/20201214111631844.png) 这里面试中数组相关的理论知识就介绍完了。 diff --git a/problems/栈与队列理论基础.md b/problems/栈与队列理论基础.md index ee4506f4..0075deb6 100644 --- a/problems/栈与队列理论基础.md +++ b/problems/栈与队列理论基础.md @@ -10,7 +10,7 @@ 如图所示: -![栈与队列理论1](https://img-blog.csdnimg.cn/20210104235346563.png) +![栈与队列理论1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104235346563.png) 那么我这里再列出四个关于栈的问题,大家可以思考一下。以下是以C++为例,使用其他编程语言的同学也对应思考一下,自己使用的编程语言里栈和队列是什么样的。 @@ -44,7 +44,8 @@ C++标准库是有多个版本的,要知道我们使用的STL是哪个版本 来说一说栈,栈先进后出,如图所示: -![栈与队列理论2](https://img-blog.csdnimg.cn/20210104235434905.png) + +![栈与队列理论2](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104235434905.png) 栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。 @@ -56,8 +57,8 @@ C++标准库是有多个版本的,要知道我们使用的STL是哪个版本 从下图中可以看出,栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的, 主要就是数组和链表的底层实现。 -![栈与队列理论3](https://img-blog.csdnimg.cn/20210104235459376.png) +![栈与队列理论3](https://code-thinking-1253855093.file.myqcloud.com/pics/20210104235459376.png) **我们常用的SGI STL,如果没有指定底层实现的话,默认是以deque为缺省情况下栈的底层结构。** diff --git a/problems/根据身高重建队列(vector原理讲解).md b/problems/根据身高重建队列(vector原理讲解).md index 315b6da1..94d94e38 100644 --- a/problems/根据身高重建队列(vector原理讲解).md +++ b/problems/根据身高重建队列(vector原理讲解).md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 贪心算法:根据身高重建队列(续集) 在讲解[贪心算法:根据身高重建队列](https://programmercarl.com/0406.根据身高重建队列.html)中,我们提到了使用vector(C++中的动态数组)来进行insert操作是费时的。 @@ -12,6 +14,7 @@ 这里专门写一篇文章来详细说一说这个问题。 使用vector的代码如下: + ```CPP // 版本一,使用vector(动态数组) class Solution { @@ -32,12 +35,14 @@ public: }; ``` + 耗时如下: -![vectorinsert](https://img-blog.csdnimg.cn/20201218203611181.png) +![vectorinsert](https://code-thinking-1253855093.file.myqcloud.com/pics/20201218203611181.png) 其直观上来看数组的insert操作是O(n)的,整体代码的时间复杂度是O(n^2)。 这么一分析好像和版本二链表实现的时间复杂度是一样的啊,为什么提交之后效率会差距这么大呢? + ```CPP // 版本二,使用list(链表) class Solution { @@ -65,7 +70,7 @@ public: 耗时如下: -![使用链表](https://img-blog.csdnimg.cn/20201218200756257.png) +![使用链表](https://code-thinking-1253855093.file.myqcloud.com/pics/20201218200756257.png) 大家都知道对于普通数组,一旦定义了大小就不能改变,例如int a[10];,这个数组a至多只能放10个元素,改不了的。 @@ -76,6 +81,7 @@ public: **首先vector的底层实现也是普通数组**。 vector的大小有两个维度一个是size一个是capicity,size就是我们平时用来遍历vector时候用的,例如: + ``` for (int i = 0; i < vec.size(); i++) { @@ -91,7 +97,7 @@ for (int i = 0; i < vec.size(); i++) { 就是重新申请一个二倍于原数组大小的数组,然后把数据都拷贝过去,并释放原数组内存。(对,就是这么原始粗暴的方法!) 举一个例子,如图: -![vector原理](https://img-blog.csdnimg.cn/20201218185902217.png) +![vector原理](https://code-thinking-1253855093.file.myqcloud.com/pics/20201218185902217.png) 原vector中的size和capicity相同都是3,初始化为1 2 3,此时要push_back一个元素4。 @@ -131,9 +137,10 @@ public: } }; ``` + 耗时如下: -![vector手动模拟insert](https://img-blog.csdnimg.cn/20201218200626718.png) +![vector手动模拟insert](https://code-thinking-1253855093.file.myqcloud.com/pics/20201218200626718.png) 这份代码就是不让vector动态扩容,全程我们自己模拟insert的操作,大家也可以直观的看出是一个O(n^2)的方法了。 diff --git a/problems/背包总结篇.md b/problems/背包总结篇.md index 9f61b2af..c4e8cd9c 100644 --- a/problems/背包总结篇.md +++ b/problems/背包总结篇.md @@ -13,7 +13,7 @@ 关于这几种常见的背包,其关系如下: -![416.分割等和子集1](https://img-blog.csdnimg.cn/20210117171307407.png) +![416.分割等和子集1](https://code-thinking-1253855093.file.myqcloud.com/pics/20230310000726.png) 通过这个图,可以很清晰分清这几种常见背包之间的关系。 diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index 0b2d7ae6..ff0b6aba 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -1,10 +1,12 @@ +

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

-# 动态规划:01背包理论基础 + +# 动态规划:01背包理论基础 **《代码随想录》算法视频公开课:[带你学透0-1背包问题!](https://www.bilibili.com/video/BV1cg411g7Y6/),相信结合视频再看本篇题解,更有助于大家对本题的理解**。 @@ -20,7 +22,7 @@ 如果这几种背包,分不清,我这里画了一个图,如下: -![416.分割等和子集1](https://img-blog.csdnimg.cn/20210117171307407.png) +![416.分割等和子集1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210117171307407.png) 至于背包九讲其其他背包,面试几乎不会问,都是竞赛级别的了,leetcode上连多重背包的题目都没有,所以题库也告诉我们,01背包和完全背包就够用了。 @@ -39,7 +41,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。 -![动态规划-背包问题](https://img-blog.csdnimg.cn/20210117175428387.jpg) +![动态规划-背包问题](https://code-thinking-1253855093.file.myqcloud.com/pics/20210117175428387.jpg) 这是标准的背包问题,以至于很多同学看了这个自然就会想到背包,甚至都不知道暴力的解法应该怎么解了。 @@ -56,7 +58,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 物品为: | | 重量 | 价值 | -| --- | --- | --- | +| ----- | ---- | ---- | | 物品0 | 1 | 15 | | 物品1 | 3 | 20 | | 物品2 | 4 | 30 | @@ -75,7 +77,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 只看这个二维数组的定义,大家一定会有点懵,看下面这个图: -![动态规划-背包问题1](https://img-blog.csdnimg.cn/20210110103003361.png) +![动态规划-背包问题1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110103003361.png) **要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的**,如果哪里看懵了,就来回顾一下i代表什么,j又代表什么。 @@ -96,20 +98,21 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 首先从dp[i][j]的定义出发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。如图: -![动态规划-背包问题2](https://img-blog.csdnimg.cn/2021011010304192.png) +![动态规划-背包问题2](https://code-thinking-1253855093.file.myqcloud.com/pics/2021011010304192.png) 在看其他情况。 状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。 -dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 +dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。 当j >= weight[0]时,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。 代码初始化如下: -``` + +``` for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。 dp[0][j] = 0; } @@ -122,7 +125,7 @@ for (int j = weight[0]; j <= bagweight; j++) { 此时dp数组初始化情况如图所示: -![动态规划-背包问题7](https://img-blog.csdnimg.cn/20210110103109140.png) +![动态规划-背包问题7](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110103109140.png) dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢? @@ -134,7 +137,7 @@ dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化 如图: -![动态规划-背包问题10](https://code-thinking.cdn.bcebos.com/pics/动态规划-背包问题10.jpg) +![动态规划-背包问题10](https://code-thinking-1253855093.file.myqcloud.com/pics/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92-%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%9810.jpg) 最后初始化代码如下: @@ -154,7 +157,7 @@ for (int j = weight[0]; j <= bagweight; j++) { 在如下图中,可以看出,有两个遍历的维度:物品与背包重量 -![动态规划-背包问题3](https://img-blog.csdnimg.cn/2021011010314055.png) +![动态规划-背包问题3](https://code-thinking-1253855093.file.myqcloud.com/pics/2021011010314055.png) 那么问题来了,**先遍历 物品还是先遍历背包重量呢?** @@ -166,7 +169,7 @@ for (int j = weight[0]; j <= bagweight; j++) { // weight数组的大小 就是物品个数 for(int i = 1; i < weight.size(); i++) { // 遍历物品 for(int j = 0; j <= bagweight; j++) { // 遍历背包容量 - if (j < weight[i]) dp[i][j] = dp[i - 1][j]; + if (j < weight[i]) dp[i][j] = dp[i - 1][j]; else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); } @@ -195,11 +198,11 @@ dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 递归公式 dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正上方向),那么先遍历物品,再遍历背包的过程如图所示: -![动态规划-背包问题5](https://img-blog.csdnimg.cn/202101101032124.png) +![动态规划-背包问题5](https://code-thinking-1253855093.file.myqcloud.com/pics/202101101032124.png) 再来看看先遍历背包,再遍历物品呢,如图: -![动态规划-背包问题6](https://img-blog.csdnimg.cn/20210110103244701.png) +![动态规划-背包问题6](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110103244701.png) **大家可以看出,虽然两个for循环遍历的次序不同,但是dp[i][j]所需要的数据就是左上角,根本不影响dp[i][j]公式的推导!** @@ -211,7 +214,7 @@ dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括 来看一下对应的dp数组的数值,如图: -![动态规划-背包问题4](https://img-blog.csdnimg.cn/20210118163425129.jpg) +![动态规划-背包问题4](https://code-thinking-1253855093.file.myqcloud.com/pics/20210118163425129.jpg) 最终结果就是dp[2][4]。 @@ -272,7 +275,7 @@ int main() { ## 其他语言版本 -### java +### java ```java public class BagProblem { @@ -336,33 +339,34 @@ public class BagProblem { ``` ### python + ```python -def test_2_wei_bag_problem1(bag_size, weight, value) -> int: +def test_2_wei_bag_problem1(bag_size, weight, value) -> int: rows, cols = len(weight), bag_size + 1 dp = [[0 for _ in range(cols)] for _ in range(rows)] - - # 初始化dp数组. - for i in range(rows): + + # 初始化dp数组. + for i in range(rows): dp[i][0] = 0 first_item_weight, first_item_value = weight[0], value[0] - for j in range(1, cols): - if first_item_weight <= j: + for j in range(1, cols): + if first_item_weight <= j: dp[0][j] = first_item_value - # 更新dp数组: 先遍历物品, 再遍历背包. - for i in range(1, len(weight)): + # 更新dp数组: 先遍历物品, 再遍历背包. + for i in range(1, len(weight)): cur_weight, cur_val = weight[i], value[i] - for j in range(1, cols): - if cur_weight > j: # 说明背包装不下当前物品. - dp[i][j] = dp[i - 1][j] # 所以不装当前物品. - else: + for j in range(1, cols): + if cur_weight > j: # 说明背包装不下当前物品. + dp[i][j] = dp[i - 1][j] # 所以不装当前物品. + else: # 定义dp数组: dp[i][j] 前i个物品里,放进容量为j的背包,价值总和最大是多少。 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cur_weight]+ cur_val) print(dp) -if __name__ == "__main__": +if __name__ == "__main__": bag_size = 4 weight = [1, 3, 4] value = [15, 20, 30] @@ -370,7 +374,8 @@ if __name__ == "__main__": ``` -### go +### go + ```go func test_2_wei_bag_problem1(weight, value []int, bagweight int) int { // 定义dp数组 @@ -391,7 +396,7 @@ func test_2_wei_bag_problem1(weight, value []int, bagweight int) int { } else { dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]) } - } + } } return dp[len(weight)-1][bagweight] } @@ -410,7 +415,7 @@ func main() { } ``` -### javascript +### javascript ```js function testWeightBagProblem (weight, value, size) { @@ -445,6 +450,7 @@ test(); ### C + ```c #include #include @@ -458,7 +464,7 @@ void backPack(int* weights, int weightSize, int* costs, int costSize, int bagWei // 开辟dp数组 int dp[weightSize][bagWeight + 1]; memset(dp, 0, sizeof(int) * weightSize * (bagWeight + 1)); - + int i, j; // 当背包容量大于物品0的重量时,将物品0放入到背包中 for(j = weights[0]; j <= bagWeight; ++j) { diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index c9414fd6..baa4107f 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -150,7 +150,7 @@ dp[1] = dp[1 - weight[0]] + value[0] = 15 一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下: -![动态规划-背包问题9](https://img-blog.csdnimg.cn/20210110103614769.png) +![动态规划-背包问题9](https://code-thinking-1253855093.file.myqcloud.com/pics/20210110103614769.png) diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index 2a2f61ec..d0b2ce0b 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -68,7 +68,8 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 dp状态图如下: -![动态规划-完全背包](https://img-blog.csdnimg.cn/20210126104510106.jpg) + +![动态规划-完全背包](https://code-thinking-1253855093.file.myqcloud.com/pics/20210126104510106.jpg) 相信很多同学看网上的文章,关于完全背包介绍基本就到为止了。 @@ -91,7 +92,8 @@ dp状态图如下: 遍历物品在外层循环,遍历背包容量在内层循环,状态如图: -![动态规划-完全背包1](https://img-blog.csdnimg.cn/20210126104529605.jpg) + +![动态规划-完全背包1](https://code-thinking-1253855093.file.myqcloud.com/pics/20210126104529605.jpg) 遍历背包容量在外层循环,遍历物品在内层循环,状态如图: diff --git a/problems/链表理论基础.md b/problems/链表理论基础.md index 44afc396..a09974df 100644 --- a/problems/链表理论基础.md +++ b/problems/链表理论基础.md @@ -1,3 +1,4 @@ +

@@ -5,6 +6,7 @@

参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

+ # 关于链表,你该了解这些! 什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。 @@ -12,7 +14,7 @@ 链表的入口节点称为链表的头结点也就是head。 如图所示: -![链表1](https://img-blog.csdnimg.cn/20200806194529815.png) +![链表1](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806194529815.png) # 链表的类型 @@ -31,7 +33,7 @@ 双链表 既可以向前查询也可以向后查询。 如图所示: -![链表2](https://img-blog.csdnimg.cn/20200806194559317.png) +![链表2](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806194559317.png) ## 循环链表 @@ -39,7 +41,7 @@ 循环链表可以用来解决约瑟夫环问题。 -![链表4](https://img-blog.csdnimg.cn/20200806194629603.png) +![链表4](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806194629603.png) # 链表的存储方式 @@ -54,7 +56,7 @@ 如图所示: -![链表3](https://img-blog.csdnimg.cn/20200806194613920.png) +![链表3](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806194613920.png) 这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。 @@ -104,7 +106,7 @@ head->val = 5; 删除D节点,如图所示: -![链表-删除节点](https://img-blog.csdnimg.cn/20200806195114541.png) +![链表-删除节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806195114541-20230310121459257.png) 只要将C节点的next指针 指向E节点就可以了。 @@ -118,7 +120,7 @@ head->val = 5; 如图所示: -![链表-添加节点](https://img-blog.csdnimg.cn/20200806195134331.png) +![链表-添加节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806195134331-20230310121503147.png) 可以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。 @@ -128,7 +130,7 @@ head->val = 5; 再把链表的特性和数组的特性进行一个对比,如图所示: -![链表-链表与数据性能对比](https://img-blog.csdnimg.cn/20200806195200276.png) +![链表-链表与数据性能对比](https://code-thinking-1253855093.file.myqcloud.com/pics/20200806195200276.png) 数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。 @@ -143,6 +145,7 @@ head->val = 5; Java: + ```java public class ListNode { // 结点的值 @@ -195,6 +198,7 @@ class ListNode { ``` Python: + ```python class ListNode: def __init__(self, val, next=None): @@ -203,6 +207,7 @@ class ListNode: ``` Go: + ```go type ListNode struct { Val int @@ -211,6 +216,7 @@ type ListNode struct { ``` Scala: + ```scala class ListNode(_x: Int = 0, _next: ListNode = null) { var next: ListNode = _next @@ -219,6 +225,7 @@ class ListNode(_x: Int = 0, _next: ListNode = null) { ``` Rust: + ```rust #[derive(PartialEq, Eq, Clone, Debug)] pub struct ListNode {