diff --git a/README.md b/README.md index daef7c02..f6567cc4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ > 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者) > 2. **PDF版本** : [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。 +> 3. **刷题顺序** : README已经将刷题顺序排好了,按照顺序一道一道刷就可以。 > 3. **学习社区** : 一起学习打卡/面试技巧/如何选择offer/大厂内推/职场规则/简历修改/技术分享/程序人生。欢迎加入[「代码随想录」学习社区](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) 。 > 4. **提交代码**:本项目统一使用C++语言进行讲解,但已经有Java、Python、Go、JavaScript等等多语言版本,感谢[这里的每一位贡献者](https://github.com/youngyangyang04/leetcode-master/graphs/contributors),如果你也想贡献代码点亮你的头像,[点击这里](https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A)了解提交代码的方式。 > 5. **转载须知** :以下所有文章皆为我([程序员Carl](https://github.com/youngyangyang04))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境! @@ -41,7 +42,7 @@ 对于刷题,我们都是想用最短的时间**按照循序渐进的难度顺序把经典题目都做一遍**,这样效率才是最高的! -所以我整理了leetcode刷题攻略:一个超级详细的刷题顺序,**每道题目都是我精心筛选,都是经典题目高频面试题**,大家只要按照这个顺序刷就可以了,**你没看错,就是题目顺序都排好了,文章顺序就是刷题顺序!挨个刷就可以,不用自己再去题海里选题了!** +所以我整理了leetcode刷题攻略:一个超级详细的刷题顺序,**每道题目都是我精心筛选,都是经典题目高频面试题**,大家只要按照这个顺序刷就可以了,**你没看错,README已经把题目顺序都排好了,文章顺序就是刷题顺序!挨个刷就可以,不用自己再去题海里选题了!** 而且每道题目我都写了的详细题解(图文并茂,难点配有视频),力扣上我的题解都是排在对应题目的首页,质量是有目共睹的。 @@ -86,6 +87,8 @@ * 编程语言 * [C++面试&C++学习指南知识点整理](https://github.com/youngyangyang04/TechCPP) +* 项目 + * [基于跳表的轻量级KV存储引擎](https://github.com/youngyangyang04/Skiplist-CPP) * 编程素养 * [看了这么多代码,谈一谈代码风格!](./problems/前序/代码风格.md) @@ -117,7 +120,7 @@ (持续更新中.....) -## 备战秋招 +## 知识星球精选 1. [选择方向的时候,我也迷茫了](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g) 2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg) @@ -125,6 +128,11 @@ 4. [马上秋招了,慌得很!](https://mp.weixin.qq.com/s/7q7W8Cb2-a5U5atZdOnOFA) 5. [Carl看了上百份简历,总结了这些!](https://mp.weixin.qq.com/s/sJa87MZD28piCOVMFkIbwQ) 6. [面试中遇到了发散性问题.....](https://mp.weixin.qq.com/s/SSonDxi2pjkSVwHNzZswng) +7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw) +8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg) +9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ) +10. [你的简历里「专业技能」写的够专业么?](https://mp.weixin.qq.com/s/bp6y-e5FVN28H9qc8J9zrg) + ## 数组 @@ -388,7 +396,8 @@ ## 单调栈 -1. [每日温度](./problems/0739.每日温度.md) +1. [单调栈:每日温度](./problems/0739.每日温度.md) +2. [单调栈:下一个更大元素I](./problems/0496.下一个更大元素I.md) ## 图论 diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md index 02e9996f..5be94996 100644 --- a/problems/0001.两数之和.md +++ b/problems/0001.两数之和.md @@ -107,7 +107,7 @@ public int[] twoSum(int[] nums, int target) { Python: -```python3 +```python class Solution: def twoSum(self, nums: List[int], target: int) -> List[int]: hashmap={} @@ -187,7 +187,23 @@ var twoSum = function (nums, target) { }; ``` +php +```php +function twoSum(array $nums, int $target): array +{ + for ($i = 0; $i < count($nums);$i++) { + // 计算剩下的数 + $residue = $target - $nums[$i]; + // 匹配的index,有则返回index, 无则返回false + $match_index = array_search($residue, $nums); + if ($match_index !== false && $match_index != $i) { + return array($i, $match_index); + } + } + return []; +} +``` ----------------------- diff --git a/problems/0015.三数之和.md b/problems/0015.三数之和.md index 5b77a170..36abb58c 100644 --- a/problems/0015.三数之和.md +++ b/problems/0015.三数之和.md @@ -354,6 +354,47 @@ def is_valid(strs) end ``` +php: + +```php +function threeSum(array $nums): array +{ + $result = []; + $length = count($nums); + if ($length < 3) { + return []; + } + sort($nums); + for ($i = 0; $i < $length; $i++) { + // 如果大于0结束 + if ($nums[$i] > 0) break; + // 去重 + if ($i > 0 && $nums[$i] == $nums[$i - 1]) continue; + $left = $i + 1; + $right = $length - 1; + // 比较 + while ($left < $right) { + $sum = $nums[$i] + $nums[$left] + $nums[$right]; + if ($sum < 0) { + $left++; + } elseif ($sum > 0) { + $right--; + } else { + array_push($result, [$nums[$i], $nums[$left], $nums[$right]]); + while ($left < $right && $nums[$left] == $nums[$left + 1]) $left++; + while ($left < $right && $nums[$right - 1] == $nums[$right]) $right--; + $left++; + $right--; + } + } + } + + return $result; +} +``` + + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index aefee698..1562052c 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -342,6 +342,46 @@ class Solution: Go: + +> 主要在于递归中传递下一个数字 + +```go +func letterCombinations(digits string) []string { + lenth:=len(digits) + if lenth==0 ||lenth>4{ + return nil + } + digitsMap:= [10]string{ + "", // 0 + "", // 1 + "abc", // 2 + "def", // 3 + "ghi", // 4 + "jkl", // 5 + "mno", // 6 + "pqrs", // 7 + "tuv", // 8 + "wxyz", // 9 + } + res:=make([]string,0) + recursion("",digits,0,digitsMap,&res) + return res +} +func recursion(tempString ,digits string, Index int,digitsMap [10]string, res *[]string) {//index表示第几个数字 + if len(tempString)==len(digits){//终止条件,字符串长度等于digits的长度 + *res=append(*res,tempString) + return + } + tmpK:=digits[Index]-'0' // 将index指向的数字转为int(确定下一个数字) + letter:=digitsMap[tmpK]// 取数字对应的字符集 + for i:=0;i stack = new Stack<>(); - Map map = new HashMap() { - { - put('}', '{'); - put(']', '['); - put(')', '('); - } - }; - - for (Character c : s.toCharArray()) { // 顺序读取字符 - if (!stack.isEmpty() && map.containsKey(c)) { // 是右括号 && 栈不为空 - if (stack.peek() == map.get(c)) { // 取其对应的左括号直接和栈顶比 - stack.pop(); // 相同则抵消,出栈 - } else { - return false; // 不同则直接返回 - } - } else { - stack.push(c); // 左括号,直接入栈 - } - } - return stack.isEmpty(); // 看左右是否抵消完 - } -} ``` Python: diff --git a/problems/0028.实现strStr.md b/problems/0028.实现strStr.md index 04508440..aaa28d3d 100644 --- a/problems/0028.实现strStr.md +++ b/problems/0028.实现strStr.md @@ -59,7 +59,7 @@ KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部 * 总结 -读完本篇可以顺便,把leetcode上28.实现strStr()题目做了。 +读完本篇可以顺便把leetcode上28.实现strStr()题目做了。 # 什么是KMP @@ -126,16 +126,15 @@ next数组就是一个前缀表(prefix table)。 # 最长公共前后缀? -文章中字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串; +文章中字符串的**前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串**。 -后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。 +**后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串**。 -**正确理解什么是前缀什么是后缀很重要。** +**正确理解什么是前缀什么是后缀很重要**! 那么网上清一色都说 “kmp 最长公共前后缀” 又是什么回事呢? - -我查了一遍 算法导论 和 算法4里KMP的章节,都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 准确一些。 +我查了一遍 算法导论 和 算法4里KMP的章节,都没有提到 “最长公共前后缀”这个词,也不知道从哪里来了,我理解是用“最长相等前后缀” 更准确一些。 **因为前缀表要求的就是相同前后缀的长度。** @@ -220,7 +219,7 @@ next数组就可以是前缀表,但是很多实现都是把前缀表统一减 # 使用next数组来匹配 -以下我们以前缀表统一减一之后的next数组来做演示。 +**以下我们以前缀表统一减一之后的next数组来做演示**。 有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。 @@ -236,7 +235,7 @@ next数组就可以是前缀表,但是很多实现都是把前缀表统一减 暴力的解法显而易见是O(n * m),所以**KMP在字符串匹配中极大的提高的搜索的效率。** -为了和[字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)字符串命名统一,方便大家理解,以下文章统称haystack为文本串, needle为模式串。 +为了和力扣题目28.实现strStr保持一致,方便大家理解,以下文章统称haystack为文本串, needle为模式串。 都知道使用KMP算法,一定要构造next数组。 @@ -402,7 +401,7 @@ for (int i = 0; i < s.size(); i++) { // 注意i就从0开始 } ``` -此时所有逻辑的代码都已经写出来了,本题整体代码如下: +此时所有逻辑的代码都已经写出来了,力扣 28.实现strStr 题目的整体代码如下: # 前缀表统一减一 C++代码实现 @@ -448,7 +447,9 @@ public: # 前缀表(不减一)C++实现 -那么前缀表就不减一了,也不右移的,到底行不行呢?行! +那么前缀表就不减一了,也不右移的,到底行不行呢? + +**行!** 我之前说过,这仅仅是KMP算法实现上的问题,如果就直接使用前缀表可以换一种回退方式,找j=next[j-1] 来进行回退。 @@ -544,7 +545,7 @@ public: 我们介绍了什么是KMP,KMP可以解决什么问题,然后分析KMP算法里的next数组,知道了next数组就是前缀表,再分析为什么要是前缀表而不是什么其他表。 -接着从给出的模式串中,我们一步一步的推导出了前缀表,得出前缀表无论是统一减一还是不同意减一得到的next数组仅仅是kmp的实现方式的不同。 +接着从给出的模式串中,我们一步一步的推导出了前缀表,得出前缀表无论是统一减一还是不减一得到的next数组仅仅是kmp的实现方式的不同。 其中还分析了KMP算法的时间复杂度,并且和暴力方法做了对比。 @@ -815,4 +816,4 @@ func strStr(haystack string, needle string) int { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
\ No newline at end of file +
diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md index 4eb60704..e43708b8 100644 --- a/problems/0037.解数独.md +++ b/problems/0037.解数独.md @@ -321,6 +321,59 @@ class Solution: backtrack(board) ``` +Python3: + +```python3 +class Solution: + def __init__(self) -> None: + self.board = [] + + def isValid(self, row: int, col: int, target: int) -> bool: + for idx in range(len(self.board)): + # 同列是否重复 + if self.board[idx][col] == str(target): + return False + # 同行是否重复 + if self.board[row][idx] == str(target): + return False + # 9宫格里是否重复 + box_row, box_col = (row // 3) * 3 + idx // 3, (col // 3) * 3 + idx % 3 + if self.board[box_row][box_col] == str(target): + return False + return True + + def getPlace(self) -> List[int]: + for row in range(len(self.board)): + for col in range(len(self.board)): + if self.board[row][col] == ".": + return [row, col] + return [-1, -1] + + def isSolved(self) -> bool: + row, col = self.getPlace() # 找个空位置 + + if row == -1 and col == -1: # 没有空位置,棋盘被填满的 + return True + + for i in range(1, 10): + if self.isValid(row, col, i): # 检查这个空位置放i,是否合适 + self.board[row][col] = str(i) # 放i + if self.isSolved(): # 合适,立刻返回, 填下一个空位置。 + return True + self.board[row][col] = "." # 不合适,回溯 + + return False # 空位置没法解决 + + def solveSudoku(self, board: List[List[str]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + if board is None or len(board) == 0: + return + self.board = board + self.isSolved() +``` + Go: Javascript: diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md index e3e4a117..ba8128b5 100644 --- a/problems/0039.组合总和.md +++ b/problems/0039.组合总和.md @@ -286,6 +286,39 @@ class Solution: ``` Go: + +> 主要在于递归中传递下一个数字 + +```go +func combinationSum(candidates []int, target int) [][]int { + var trcak []int + var res [][]int + backtracking(0,0,target,candidates,trcak,&res) + return res +} +func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int){ + //终止条件 + if sum==target{ + tmp:=make([]int,len(trcak)) + copy(tmp,trcak)//拷贝 + *res=append(*res,tmp)//放入结果集 + return + } + if sum>target{return} + //回溯 + for i:=startIndex;i 主要在于如何在回溯中去重 + +```go +func combinationSum2(candidates []int, target int) [][]int { + var trcak []int + var res [][]int + var history map[int]bool + history=make(map[int]bool) + sort.Ints(candidates) + backtracking(0,0,target,candidates,trcak,&res,history) + return res +} +func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int,history map[int]bool){ + //终止条件 + if sum==target{ + tmp:=make([]int,len(trcak)) + copy(tmp,trcak)//拷贝 + *res=append(*res,tmp)//放入结果集 + return + } + if sum>target{return} + //回溯 + // used[i - 1] == true,说明同一树支candidates[i - 1]使用过 + // used[i - 1] == false,说明同一树层candidates[i - 1]使用过 + for i:=startIndex;i0&&candidates[i]==candidates[i-1]&&history[i-1]==false{ + continue + } + //更新路径集合和sum + trcak=append(trcak,candidates[i]) + sum+=candidates[i] + history[i]=true + //递归 + backtracking(i+1,sum,target,candidates,trcak,res,history) + //回溯 + trcak=trcak[:len(trcak)-1] + sum-=candidates[i] + history[i]=false + } +} +``` javaScript: ```js diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index 4128da4c..a161d944 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -30,7 +30,7 @@ ## 思路 -本题相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。 +本题相对于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。 但思路是相似的,还是要看最大覆盖范围。 @@ -132,7 +132,7 @@ public: ## 总结 -相信大家可以发现,这道题目相当于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。 +相信大家可以发现,这道题目相当于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。 但代码又十分简单,贪心就是这么巧妙。 @@ -228,11 +228,6 @@ var jump = function(nums) { }; ``` -/* -dp[i]表示从起点到当前位置的最小跳跃次数 -dp[i]=min(dp[j]+1,dp[i]) 表示从j位置用一步跳跃到当前位置,这个j位置可能有很多个,却最小一个就可以 -*/ -``` diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index 06367851..3ee7271e 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -27,7 +27,10 @@ ## 思路 -此时我们已经学习了[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + +此时我们已经学习了[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、 [131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。 相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。 @@ -81,7 +84,7 @@ if (path.size() == nums.size()) { * 单层搜索的逻辑 -这里和[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。 +这里和[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[131.切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。 因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。 @@ -244,31 +247,35 @@ func backtrack(nums,pathNums []int,used []bool){ } } +``` + Javascript: -```javascript +```js +/** + * @param {number[]} nums + * @return {number[][]} + */ var permute = function(nums) { - let result = [] - let path = [] - function backtracing(used) { - if(path.length === nums.length) { - result.push(path.slice(0)) - return + const res = [], path = []; + backtracking(nums, nums.length, []); + return res; + + function backtracking(n, k, used) { + if(path.length === k) { + res.push(Array.from(path)); + return; } - for(let i = 0; i < nums.length; i++) { - if(used[nums[i]]) { - continue - } - used[nums[i]] = true - path.push(nums[i]) - backtracing(used) - path.pop() - used[nums[i]] = false + for (let i = 0; i < k; i++ ) { + if(used[i]) continue; + path.push(n[i]); + used[i] = true; // 同支 + backtracking(n, k, used); + path.pop(); + used[i] = false; } } - backtracing([]) - return result }; ``` diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index b4fb3470..079ca834 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -29,6 +29,8 @@ * -10 <= nums[i] <= 10 ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + 这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index 5242fce2..a8919ec3 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -41,6 +41,9 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并 ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了组合、切割、子集、排列问题之后,遇到这种二位矩阵还会有点不知所措。 首先来看一下皇后们的约束条件: @@ -353,14 +356,6 @@ class Solution { } ``` -## 其他语言版本 - - -Java: - - -Python: - Go: ```Go diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 47cb41af..50e70b3e 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -279,9 +279,7 @@ Python: ```python class Solution: # 动态规划 def uniquePaths(self, m: int, n: int) -> int: - dp = [[0 for i in range(n)] for j in range(m)] - for i in range(m): dp[i][0] = 1 - for j in range(n): dp[0][j] = 1 + dp = [[1 for i in range(n)] for j in range(m)] for i in range(1, m): for j in range(1, n): dp[i][j] = dp[i][j - 1] + dp[i - 1][j] diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index 52f00322..a61ffd02 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -232,6 +232,38 @@ class Solution: return dp[-1][-1] ``` +```python +class Solution: + """ + 使用一维dp数组 + """ + + def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int: + m, n = len(obstacleGrid), len(obstacleGrid[0]) + + # 初始化dp数组 + # 该数组缓存当前行 + curr = [0] * n + for j in range(n): + if obstacleGrid[0][j] == 1: + break + curr[j] = 1 + + for i in range(1, m): # 从第二行开始 + for j in range(n): # 从第一列开始,因为第一列可能有障碍物 + # 有障碍物处无法通行,状态就设成0 + if obstacleGrid[i][j] == 1: + curr[j] = 0 + elif j > 0: + # 等价于 + # dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + curr[j] = curr[j] + curr[j - 1] + # 隐含的状态更新 + # dp[i][0] = dp[i - 1][0] + + return curr[n - 1] +``` + Go: diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index 680af587..96899d37 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -283,7 +283,18 @@ func climbStairs(n int) int { return dp[n] } ``` - +Javascript: +```Javascript +var climbStairs = function(n) { + // dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶 + // dp[i] = dp[i - 1] + dp[i - 2] + let dp = [1 , 2] + for(let i = 2; i < n; i++) { + dp[i] = dp[i - 1] + dp[i - 2] + } + return dp[n - 1] +}; +``` ----------------------- diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index a8b9a215..40ad7684 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -338,6 +338,46 @@ class Solution(object): return ans``` ``` +```python3 +class Solution: + def __init__(self) -> None: + self.s = "" + self.res = [] + + def isVaild(self, s: str) -> bool: + if len(s) > 1 and s[0] == "0": + return False + + if 0 <= int(s) <= 255: + return True + + return False + + def backTrack(self, path: List[str], start: int) -> None: + if start == len(self.s) and len(path) == 4: + self.res.append(".".join(path)) + return + + for end in range(start + 1, len(self.s) + 1): + # 剪枝 + # 保证切割完,s没有剩余的字符。 + if len(self.s) - end > 3 * (4 - len(path) - 1): + continue + if self.isVaild(self.s[start:end]): + # 在参数处,更新状态,实则创建一个新的变量 + # 不会影响当前的状态,当前的path变量没有改变 + # 因此递归完不用path.pop() + self.backTrack(path + [self.s[start:end]], end) + + def restoreIpAddresses(self, s: str) -> List[str]: + # prune + if len(s) > 3 * 4: + return [] + self.s = s + self.backTrack([], 0) + return self.res +``` + JavaScript: ```js @@ -367,6 +407,47 @@ var restoreIpAddresses = function(s) { } }; ``` +Go: +> 回溯(对于前导 0的IP(特别注意s[startIndex]=='0'的判断,不应该写成s[startIndex]==0,因为s截取出来不是数字)) + +```go +func restoreIpAddresses(s string) []string { + var res,path []string + backTracking(s,path,0,&res) + return res +} +func backTracking(s string,path []string,startIndex int,res *[]string){ + //终止条件 + if startIndex==len(s)&&len(path)==4{ + tmpIpString:=path[0]+"."+path[1]+"."+path[2]+"."+path[3] + *res=append(*res,tmpIpString) + } + for i:=startIndex;i1&&s[startIndex]=='0'{//对于前导 0的IP(特别注意s[startIndex]=='0'的判断,不应该写成s[startIndex]==0,因为s截取出来不是数字) + return false + } + if checkInt>255{ + return false + } + return true +} + +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index a9631315..56f50d46 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -211,6 +211,23 @@ func numTrees(n int)int{ } ``` +Javascript: +```Javascript +const numTrees =(n) => { + let dp = new Array(n+1).fill(0); + dp[0] = 1; + dp[1] = 1; + + for(let i = 2; i <= n; i++) { + for(let j = 1; j <= i; j++) { + dp[i] += dp[j-1] * dp[i-j]; + } + } + + return dp[n]; +}; +``` + ----------------------- diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md index eb877abb..b93d8cd5 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -376,6 +376,97 @@ func isBST(root *TreeNode, min, max int) bool { return isBST(root.Left, min, root.Val) && isBST(root.Right, root.Val, max) } ``` +```go +// 中序遍历解法 +func isValidBST(root *TreeNode) bool { + // 保存上一个指针 + var prev *TreeNode + var travel func(node *TreeNode) bool + travel = func(node *TreeNode) bool { + if node == nil { + return true + } + leftRes := travel(node.Left) + // 当前值小于等于前一个节点的值,返回false + if prev != nil && node.Val <= prev.Val { + return false + } + prev = node + rightRes := travel(node.Right) + return leftRes && rightRes + } + return travel(root) +} +``` + +JavaScript版本 + +> 辅助数组解决 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ +var isValidBST = function (root) { + let arr = []; + const buildArr = (root) => { + if (root) { + buildArr(root.left); + arr.push(root.val); + buildArr(root.right); + } + } + buildArr(root); + for (let i = 1; i < arr.length; ++i) { + if (arr[i] <= arr[i - 1]) + return false; + } + return true; +}; +``` + +> 递归中解决 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {boolean} + */ +let pre = null; +var isValidBST = function (root) { + let pre = null; + const inOrder = (root) => { + if (root === null) + return true; + let left = inOrder(root.left); + + if (pre !== null && pre.val >= root.val) + return false; + pre = root; + + let right = inOrder(root.right); + return left && right; + } + return inOrder(root); +}; +``` diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 89d0dda7..b13fdd8d 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -18,7 +18,7 @@ * 637.二叉树的层平均值 * 429.N叉树的前序遍历 * 515.在每个树行中找最大值 -* 116. 填充每个节点的下一个右侧节点指针 +* 116.填充每个节点的下一个右侧节点指针 * 117.填充每个节点的下一个右侧节点指针II @@ -80,15 +80,10 @@ public: } }; ``` + python代码: ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: if not root: @@ -98,7 +93,7 @@ class Solution: out_list = [] while quene: - length = len(queue) # 这里一定要先求出队列的长度,不能用range(len(queue)),因为queue长度是变化的 + length = len(queue) in_list = [] for _ in range(length): curnode = queue.pop(0) # (默认移除列表最后一个元素)这里需要移除队列最头上的那个 @@ -110,7 +105,94 @@ class Solution: return out_list ``` +java: +```Java +// 102.二叉树的层序遍历 +class Solution { + public List> resList = new ArrayList>(); + + public List> levelOrder(TreeNode root) { + //checkFun01(root,0); + checkFun02(root); + + return resList; + } + + //DFS--递归方式 + public void checkFun01(TreeNode node, Integer deep) { + if (node == null) return; + deep++; + + if (resList.size() < deep) { + //当层级增加时,list的Item也增加,利用list的索引值进行层级界定 + List item = new ArrayList(); + resList.add(item); + } + resList.get(deep - 1).add(node.val); + + checkFun01(node.left, deep); + checkFun01(node.right, deep); + } + + //BFS--迭代方式--借助队列 + public void checkFun02(TreeNode node) { + if (node == null) return; + Queue que = new LinkedList(); + que.offer(node); + + while (!que.isEmpty()) { + List itemList = new ArrayList(); + int len = que.size(); + + while (len > 0) { + TreeNode tmpNode = que.poll(); + itemList.add(tmpNode.val); + + if (tmpNode.left != null) que.offer(tmpNode.left); + if (tmpNode.right != null) que.offer(tmpNode.right); + len--; + } + + resList.add(itemList); + } + + } +} +``` + +go: + +```go +/** +102. 二叉树的层序遍历 + */ +func levelOrder(root *TreeNode) [][]int { + res:=[][]int{} + if root==nil{//防止为空 + return res + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []int + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i List[List[int]]: if not root: @@ -218,7 +294,88 @@ class Solution: # 内存消耗:15.2 MB, 在所有 Python3 提交中击败了63.76%的用户 ``` +Java: +```java +// 107. 二叉树的层序遍历 II +public class N0107 { + + /** + * 解法:队列,迭代。 + * 层序遍历,再翻转数组即可。 + */ + public List> solution1(TreeNode root) { + List> list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + List levelList = new ArrayList<>(); + + int levelSize = que.size(); + for (int i = 0; i < levelSize; i++) { + TreeNode peek = que.peekFirst(); + levelList.add(que.pollFirst().val); + + if (peek.left != null) { + que.offerLast(peek.left); + } + if (peek.right != null) { + que.offerLast(peek.right); + } + } + list.add(levelList); + } + + List> result = new ArrayList<>(); + for (int i = list.size() - 1; i >= 0; i-- ) { + result.add(list.get(i)); + } + + return result; + } +} +``` + +go: + +```GO +/** +107. 二叉树的层序遍历 II + */ +func levelOrderBottom(root *TreeNode) [][]int { + queue:=list.New() + res:=[][]int{} + if root==nil{ + return res + } + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + tmp:=[]int{} + for i:=0;i List[int]: if not root: @@ -322,7 +473,87 @@ class Solution: ``` +Java: +```java +// 199.二叉树的右视图 +public class N0199 { + /** + * 解法:队列,迭代。 + * 每次返回每层的最后一个字段即可。 + * + * 小优化:每层右孩子先入队。代码略。 + */ + public List rightSideView(TreeNode root) { + List list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + int levelSize = que.size(); + + for (int i = 0; i < levelSize; i++) { + TreeNode poll = que.pollFirst(); + + if (poll.left != null) { + que.addLast(poll.left); + } + if (poll.right != null) { + que.addLast(poll.right); + } + + if (i == levelSize - 1) { + list.add(poll.val); + } + } + } + + return list; + } +} +``` + +go: + +```GO + +/** +199. 二叉树的右视图 + */ +func rightSideView(root *TreeNode) []int { + queue:=list.New() + res:=[][]int{} + var finaRes []int + if root==nil{ + return finaRes + } + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + tmp:=[]int{} + for i:=0;i List[float]: if not root: @@ -426,7 +651,93 @@ class Solution: # 内存消耗:17 MB, 在所有 Python3 提交中击败了89.68%的用户 ``` +java: +```java + +// 637. 二叉树的层平均值 +public class N0637 { + + /** + * 解法:队列,迭代。 + * 每次返回每层的最后一个字段即可。 + */ + public List averageOfLevels(TreeNode root) { + List list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + TreeNode peek = que.peekFirst(); + + int levelSize = que.size(); + double levelSum = 0.0; + for (int i = 0; i < levelSize; i++) { + TreeNode poll = que.pollFirst(); + + levelSum += poll.val; + + if (poll.left != null) { + que.addLast(poll.left); + } + if (poll.right != null) { + que.addLast(poll.right); + } + } + list.add(levelSum / levelSize); + } + return list; + } +} +``` + +go: + +```GO +/** +637. 二叉树的层平均值 + */ +func averageOfLevels(root *TreeNode) []float64 { + res:=[][]int{} + var finRes []float64 + if root==nil{//防止为空 + return finRes + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []int + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i List[List[int]]: @@ -562,7 +866,97 @@ class Solution: # 内存消耗:16.5 MB, 在所有 Python3 提交中击败了89.19%的用户 ``` +java: +```java + +// 429. N 叉树的层序遍历 +public class N0429 { + /** + * 解法1:队列,迭代。 + */ + public List> levelOrder(Node root) { + List> list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + int levelSize = que.size(); + List levelList = new ArrayList<>(); + + for (int i = 0; i < levelSize; i++) { + Node poll = que.pollFirst(); + + levelList.add(poll.val); + + List children = poll.children; + if (children == null || children.size() == 0) { + continue; + } + for (Node child : children) { + if (child != null) { + que.offerLast(child); + } + } + } + list.add(levelList); + } + + return list; + } + + class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } + } +} +``` + + +go: + +```GO +/** +429. N 叉树的层序遍历 + */ + +func levelOrder(root *Node) [][]int { + queue:=list.New() + res:=[][]int{}//结果集 + if root==nil{ + return res + } + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len()//记录当前层的数量 + var tmp []int + for T:=0;T List[int]: + if root is None: + return [] + queue = [root] + out_list = [] + while queue: + length = len(queue) + in_list = [] + for _ in range(length): + curnode = queue.pop(0) + in_list.append(curnode.val) + if curnode.left: queue.append(curnode.left) + if curnode.right: queue.append(curnode.right) + out_list.append(max(in_list)) + return out_list +``` + +go: + +```GO +/** +515. 在每个树行中找最大值 + */ +func largestValues(root *TreeNode) []int { + res:=[][]int{} + var finRes []int + if root==nil{//防止为空 + return finRes + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []int + //层次遍历 + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i max { + max = val + } + } + return max +} +``` + javascript代码: ```javascript @@ -712,6 +1175,84 @@ public: }; ``` + +python代码: + +```python +# 层序遍历解法 +class Solution: + def connect(self, root: 'Node') -> 'Node': + if not root: + return None + queue = [root] + while queue: + n = len(queue) + for i in range(n): + node = queue.pop(0) + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + if i == n - 1: + break + node.next = queue[0] + return root + +# 链表解法 +class Solution: + def connect(self, root: 'Node') -> 'Node': + first = root + while first: + cur = first + while cur: # 遍历每一层的节点 + if cur.left: cur.left.next = cur.right # 找左节点的next + if cur.right and cur.next: cur.right.next = cur.next.left # 找右节点的next + cur = cur.next # cur同层移动到下一节点 + first = first.left # 从本层扩展到下一层 + return root +``` + +go: + +```GO +/** +116. 填充每个节点的下一个右侧节点指针 +117. 填充每个节点的下一个右侧节点指针 II + */ + +func connect(root *Node) *Node { + res:=[][]*Node{} + if root==nil{//防止为空 + return root + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []*Node + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i 'Node': + if not root: + return None + queue = [root] + while queue: # 遍历每一层 + length = len(queue) + tail = None # 每一层维护一个尾节点 + for i in range(length): # 遍历当前层 + curnode = queue.pop(0) + if tail: + tail.next = curnode # 让尾节点指向当前节点 + tail = curnode # 让当前节点成为尾节点 + if curnode.left : queue.append(curnode.left) + if curnode.right: queue.append(curnode.right) + return root -## 总结 - -二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时是不是又发现队列的应用了)。 - -虽然不能一口气打十个,打八个也还行。 - -* 102.二叉树的层序遍历 -* 107.二叉树的层次遍历II -* 199.二叉树的右视图 -* 637.二叉树的层平均值 -* 429.N叉树的前序遍历 -* 515.在每个树行中找最大值 -* 116. 填充每个节点的下一个右侧节点指针 -* 117.填充每个节点的下一个右侧节点指针II - -如果非要打十个,还得找叶师傅! - -![我要打十个](https://tva1.sinaimg.cn/large/008eGmZEly1gnadnltbpjg309603w4qp.gif) - - - - -## 其他语言版本 - - -Java: - -```Java -// 102.二叉树的层序遍历 -class Solution { - public List> resList = new ArrayList>(); - - public List> levelOrder(TreeNode root) { - //checkFun01(root,0); - checkFun02(root); - - return resList; - } - - //DFS--递归方式 - public void checkFun01(TreeNode node, Integer deep) { - if (node == null) return; - deep++; - - if (resList.size() < deep) { - //当层级增加时,list的Item也增加,利用list的索引值进行层级界定 - List item = new ArrayList(); - resList.add(item); - } - resList.get(deep - 1).add(node.val); - - checkFun01(node.left, deep); - checkFun01(node.right, deep); - } - - //BFS--迭代方式--借助队列 - public void checkFun02(TreeNode node) { - if (node == null) return; - Queue que = new LinkedList(); - que.offer(node); - - while (!que.isEmpty()) { - List itemList = new ArrayList(); - int len = que.size(); - - while (len > 0) { - TreeNode tmpNode = que.poll(); - itemList.add(tmpNode.val); - - if (tmpNode.left != null) que.offer(tmpNode.left); - if (tmpNode.right != null) que.offer(tmpNode.right); - len--; - } - - resList.add(itemList); - } - - } -} - - -// 107. 二叉树的层序遍历 II -public class N0107 { - - /** - * 解法:队列,迭代。 - * 层序遍历,再翻转数组即可。 - */ - public List> solution1(TreeNode root) { - List> list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - List levelList = new ArrayList<>(); - - int levelSize = que.size(); - for (int i = 0; i < levelSize; i++) { - TreeNode peek = que.peekFirst(); - levelList.add(que.pollFirst().val); - - if (peek.left != null) { - que.offerLast(peek.left); - } - if (peek.right != null) { - que.offerLast(peek.right); - } - } - list.add(levelList); - } - - List> result = new ArrayList<>(); - for (int i = list.size() - 1; i >= 0; i-- ) { - result.add(list.get(i)); - } - - return result; - } -} - -// 199.二叉树的右视图 -public class N0199 { - /** - * 解法:队列,迭代。 - * 每次返回每层的最后一个字段即可。 - * - * 小优化:每层右孩子先入队。代码略。 - */ - public List rightSideView(TreeNode root) { - List list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - int levelSize = que.size(); - - for (int i = 0; i < levelSize; i++) { - TreeNode poll = que.pollFirst(); - - if (poll.left != null) { - que.addLast(poll.left); - } - if (poll.right != null) { - que.addLast(poll.right); - } - - if (i == levelSize - 1) { - list.add(poll.val); - } - } - } - - return list; - } -} - -// 637. 二叉树的层平均值 -public class N0637 { - - /** - * 解法:队列,迭代。 - * 每次返回每层的最后一个字段即可。 - */ - public List averageOfLevels(TreeNode root) { - List list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - TreeNode peek = que.peekFirst(); - - int levelSize = que.size(); - double levelSum = 0.0; - for (int i = 0; i < levelSize; i++) { - TreeNode poll = que.pollFirst(); - - levelSum += poll.val; - - if (poll.left != null) { - que.addLast(poll.left); - } - if (poll.right != null) { - que.addLast(poll.right); - } - } - list.add(levelSum / levelSize); - } - return list; - } -} - -// 429. N 叉树的层序遍历 -public class N0429 { - /** - * 解法1:队列,迭代。 - */ - public List> levelOrder(Node root) { - List> list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - int levelSize = que.size(); - List levelList = new ArrayList<>(); - - for (int i = 0; i < levelSize; i++) { - Node poll = que.pollFirst(); - - levelList.add(poll.val); - - List children = poll.children; - if (children == null || children.size() == 0) { - continue; - } - for (Node child : children) { - if (child != null) { - que.offerLast(child); - } - } - } - list.add(levelList); - } - - return list; - } - - class Node { - public int val; - public List children; - - public Node() {} - - public Node(int _val) { - val = _val; - } - - public Node(int _val, List _children) { - val = _val; - children = _children; - } - } -} +# 链表解法 +class Solution: + def connect(self, root: 'Node') -> 'Node': + if not root: + return None + first = root + while first: # 遍历每一层 + dummyHead = Node(None) # 为下一行创建一个虚拟头节点,相当于下一行所有节点链表的头结点(每一层都会创建); + tail = dummyHead # 为下一行维护一个尾节点指针(初始化是虚拟节点) + cur = first + while cur: # 遍历当前层的节点 + if cur.left: # 链接下一行的节点 + tail.next = cur.left + tail = tail.next + if cur.right: + tail.next = cur.right + tail = tail.next + cur = cur.next # cur同层移动到下一节点 + first = dummyHead.next # 此处为换行操作,更新到下一行 + return root ``` +go: -Python: - - -Go: -```Go -func levelOrder(root *TreeNode) [][]int { - result:=make([][]int,0) - if root==nil{ - return result - } - - queue:=make([]*TreeNode,0) - queue=append(queue,root) - - for len(queue)>0{ - list:=make([]int,0) - l:=len(queue) - - for i:=0;i 二叉树的层序遍历(GO语言完全版) - -```go -/** -102. 二叉树的层序遍历 - */ -func levelOrder(root *TreeNode) [][]int { - res:=[][]int{} - if root==nil{//防止为空 - return res - } - queue:=list.New() - queue.PushBack(root) - var tmpArr []int - for queue.Len()>0 { - length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) - for i:=0;i0{ - length:=queue.Len() - tmp:=[]int{} - for i:=0;i0{ - length:=queue.Len() - tmp:=[]int{} - for i:=0;i0 { - length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) - for i:=0;i0{ - length:=queue.Len()//记录当前层的数量 - var tmp []int - for T:=0;T0 { - length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) - for i:=0;i max { - max = val - } - } - return max -} +```GO /** 116. 填充每个节点的下一个右侧节点指针 117. 填充每个节点的下一个右侧节点指针 II @@ -1293,26 +1377,31 @@ func connect(root *Node) *Node { return root } ``` -Javascript: -```javascript -var levelOrder = function (root) { - let ans = []; - if (!root) return ans; - let queue = [root]; - while (queue.length) { - let size = queue.length; - let temp = []; - while (size--) { - let n = queue.shift(); - temp.push(n.val); - if (n.left) queue.push(n.left); - if (n.right) queue.push(n.right); - } - ans.push(temp); - } - return ans; -}; -``` + +## 总结 + +二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时是不是又发现队列的应用了)。 + +虽然不能一口气打十个,打八个也还行。 + +* 102.二叉树的层序遍历 +* 107.二叉树的层次遍历II +* 199.二叉树的右视图 +* 637.二叉树的层平均值 +* 429.N叉树的前序遍历 +* 515.在每个树行中找最大值 +* 116.填充每个节点的下一个右侧节点指针 +* 117.填充每个节点的下一个右侧节点指针II + +如果非要打十个,还得找叶师傅! + +![我要打十个](https://tva1.sinaimg.cn/large/008eGmZEly1gnadnltbpjg309603w4qp.gif) + + + + +# 其他语言版本 + > 二叉树的层序遍历(Javascript语言完全版) (迭代 + 递归) diff --git a/problems/0106.从中序与后序遍历序列构造二叉树.md b/problems/0106.从中序与后序遍历序列构造二叉树.md index 600a38e0..4c5a70a0 100644 --- a/problems/0106.从中序与后序遍历序列构造二叉树.md +++ b/problems/0106.从中序与后序遍历序列构造二叉树.md @@ -775,6 +775,20 @@ var buildTree = function(inorder, postorder) { }; ``` +从前序与中序遍历序列构造二叉树 + +```javascript +var buildTree = function(preorder, inorder) { + if(!preorder.length) + return null; + let root = new TreeNode(preorder[0]); + let mid = inorder.findIndex((number) => number === root.val); + root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid)); + root.right = buildTree(preorder.slice(mid + 1, preorder.length), inorder.slice(mid + 1, inorder.length)); + return root; +}; +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) diff --git a/problems/0108.将有序数组转换为二叉搜索树.md b/problems/0108.将有序数组转换为二叉搜索树.md index 2692be47..8ec5f3dc 100644 --- a/problems/0108.将有序数组转换为二叉搜索树.md +++ b/problems/0108.将有序数组转换为二叉搜索树.md @@ -54,7 +54,7 @@ 如下两棵树,都是这个数组的平衡二叉搜索树: -![108.将有序数组转换为二叉搜索树](https://code-thinking.cdn.bcebos.com/pics/108.%E5%B0%86%E6%9C%89%E5%BA%8F%E6%95%B0%E7%BB%84%E8%BD%AC%E6%8D%A2%E4%B8%BA%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91.png) +![108.将有序数组转换为二叉搜索树](https://code-thinking.cdn.bcebos.com/pics/108.将有序数组转换为二叉搜索树.png) 如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1,取右边元素就是树2。 @@ -209,6 +209,8 @@ public: Java: + +递归: 左闭右开 [left,right) ```Java class Solution { public TreeNode sortedArrayToBST(int[] nums) { @@ -232,6 +234,75 @@ class Solution { ``` +递归: 左闭右闭 [left,right] +```java +class Solution { + public TreeNode sortedArrayToBST(int[] nums) { + TreeNode root = traversal(nums, 0, nums.length - 1); + return root; + } + + // 左闭右闭区间[left, right) + private TreeNode traversal(int[] nums, int left, int right) { + if (left > right) return null; + + int mid = left + ((right - left) >> 1); + TreeNode root = new TreeNode(nums[mid]); + root.left = traversal(nums, left, mid - 1); + root.right = traversal(nums, mid + 1, right); + return root; + } +} +``` +迭代: 左闭右闭 [left,right] +```java +class Solution { + public TreeNode sortedArrayToBST(int[] nums) { + if (nums.length == 0) return null; + + //根节点初始化 + TreeNode root = new TreeNode(-1); + Queue nodeQueue = new LinkedList<>(); + Queue leftQueue = new LinkedList<>(); + Queue rightQueue = new LinkedList<>(); + + // 根节点入队列 + nodeQueue.offer(root); + // 0为左区间下表初始位置 + leftQueue.offer(0); + // nums.size() - 1为右区间下表初始位置 + rightQueue.offer(nums.length - 1); + + while (!nodeQueue.isEmpty()) { + TreeNode currNode = nodeQueue.poll(); + int left = leftQueue.poll(); + int right = rightQueue.poll(); + int mid = left + ((right - left) >> 1); + + // 将mid对应的元素给中间节点 + currNode.val = nums[mid]; + + // 处理左区间 + if (left <= mid - 1) { + currNode.left = new TreeNode(-1); + nodeQueue.offer(currNode.left); + leftQueue.offer(left); + rightQueue.offer(mid - 1); + } + + // 处理右区间 + if (right >= mid + 1) { + currNode.right = new TreeNode(-1); + nodeQueue.offer(currNode.right); + leftQueue.offer(mid + 1); + rightQueue.offer(right); + } + } + return root; + } +} +``` + Python: ```python3 # Definition for a binary tree node. @@ -258,6 +329,59 @@ class Solution: Go: +> 递归(隐含回溯) + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + //递归(隐含回溯) +func sortedArrayToBST(nums []int) *TreeNode { + if len(nums)==0{return nil}//终止条件,最后数组为空则可以返回 + root:=&TreeNode{nums[len(nums)/2],nil,nil}//按照BSL的特点,从中间构造节点 + root.Left=sortedArrayToBST(nums[:len(nums)/2])//数组的左边为左子树 + root.Right=sortedArrayToBST(nums[len(nums)/2+1:])//数字的右边为右子树 + return root +} +``` + +JavaScript版本 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {number[]} nums + * @return {TreeNode} + */ +var sortedArrayToBST = function (nums) { + const buildTree = (Arr, left, right) => { + if (left > right) + return null; + + let mid = Math.floor(left + (right - left) / 2); + + let root = new TreeNode(Arr[mid]); + root.left = buildTree(Arr, left, mid - 1); + root.right = buildTree(Arr, mid + 1, right); + return root; + } + return buildTree(nums, 0, nums.length - 1); +}; +``` + + ----------------------- diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index 48795722..a36faeff 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -29,7 +29,7 @@ ## 思路 -看完了这篇[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。 +看完了这篇[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。 直觉上好像和求最大深度差不多,其实还是差不少的。 @@ -154,7 +154,7 @@ public: ## 迭代法 -相对于[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 +相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index 54f79d1d..d798ca90 100644 --- a/problems/0112.路径总和.md +++ b/problems/0112.路径总和.md @@ -117,7 +117,7 @@ if (cur->left) { // 左 } if (cur->right) { // 右 count -= cur->right->val; - if (traversal(cur->right, count - cur->right->val)) return true; + if (traversal(cur->right, count)) return true; count += cur->right->val; } return false; diff --git a/problems/0115.不同的子序列.md b/problems/0115.不同的子序列.md index d3bc6d97..62af9d0f 100644 --- a/problems/0115.不同的子序列.md +++ b/problems/0115.不同的子序列.md @@ -186,6 +186,40 @@ class Solution: return dp[-1][-1] ``` +Python3: +```python +class SolutionDP2: + """ + 既然dp[i]只用到dp[i - 1]的状态, + 我们可以通过缓存dp[i - 1]的状态来对dp进行压缩, + 减少空间复杂度。 + (原理等同同于滚动数组) + """ + + def numDistinct(self, s: str, t: str) -> int: + n1, n2 = len(s), len(t) + if n1 < n2: + return 0 + + dp = [0 for _ in range(n2 + 1)] + dp[0] = 1 + + for i in range(1, n1 + 1): + # 必须深拷贝 + # 不然prev[i]和dp[i]是同一个地址的引用 + prev = dp.copy() + # 剪枝,保证s的长度大于等于t + # 因为对于任意i,i > n1, dp[i] = 0 + # 没必要跟新状态。 + end = i if i < n2 else n2 + for j in range(1, end + 1): + if s[i - 1] == t[j - 1]: + dp[j] = prev[j - 1] + prev[j] + else: + dp[j] = prev[j] + return dp[-1] +``` + Go: diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md index 9d23fd13..43453409 100644 --- a/problems/0131.分割回文串.md +++ b/problems/0131.分割回文串.md @@ -312,6 +312,50 @@ class Solution: ``` Go: +> 注意切片(go切片是披着值类型外衣的引用类型) + +```go +func partition(s string) [][]string { + var tmpString []string//切割字符串集合 + var res [][]string//结果集合 + backTracking(s,tmpString,0,&res) + return res +} +func backTracking(s string,tmpString []string,startIndex int,res *[][]string){ + if startIndex==len(s){//到达字符串末尾了 + //进行一次切片拷贝,怕之后的操作影响tmpString切片内的值 + t := make([]string, len(tmpString)) + copy(t, tmpString) + *res=append(*res,t) + } + for i:=startIndex;i'9'; } - private int stoi(String s) { return Integer.valueOf(s); } - public static void main(String[] args) { new EvalRPN().evalRPN(new String[] {"10","6","9","3","+","-11","*","/","*","17","+","5","+"}); } } ``` + Go: ```Go func evalRPN(tokens []string) int { diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index d76734e4..ffa3446a 100644 --- a/problems/0151.翻转字符串里的单词.md +++ b/problems/0151.翻转字符串里的单词.md @@ -16,19 +16,19 @@ https://leetcode-cn.com/problems/reverse-words-in-a-string/ 给定一个字符串,逐个翻转字符串中的每个单词。 -示例 1: -输入: "the sky is blue" -输出: "blue is sky the" +示例 1: +输入: "the sky is blue" +输出: "blue is sky the" -示例 2: -输入: "  hello world!  " -输出: "world! hello" -解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 +示例 2: +输入: "  hello world!  " +输出: "world! hello" +解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。 -示例 3: -输入: "a good   example" -输出: "example good a" -解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 +示例 3: +输入: "a good   example" +输出: "example good a" +解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。 # 思路 @@ -50,12 +50,15 @@ https://leetcode-cn.com/problems/reverse-words-in-a-string/ * 将整个字符串反转 * 将每个单词反转 -如动画所示: +举个例子,源字符串为:"the sky is blue " -![151翻转字符串里的单词](https://tva1.sinaimg.cn/large/008eGmZEly1gp0kv5gl4mg30gy0c4nbp.gif) +* 移除多余空格 : "the sky is blue" +* 字符串反转:"eulb si yks eht" +* 单词反转:"blue is sky the" 这样我们就完成了翻转字符串里的单词。 + 思路很明确了,我们说一说代码的实现细节,就拿移除多余空格来说,一些同学会上来写如下代码: ```C++ @@ -80,13 +83,13 @@ void removeExtraSpaces(string& s) { 如果不仔细琢磨一下erase的时间复杂读,还以为以上的代码是O(n)的时间复杂度呢。 -想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA),最优的算法来移除元素也要O(n)。 +想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww),最优的算法来移除元素也要O(n)。 erase操作上面还套了一个for循环,那么以上代码移除冗余空格的代码时间复杂度为O(n^2)。 那么使用双指针法来去移除空格,最后resize(重新设置)一下字符串的大小,就可以做到O(n)的时间复杂度。 -如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)是如何移除元素的。 +如果对这个操作比较生疏了,可以再看一下这篇文章:[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)是如何移除元素的。 那么使用双指针来移除冗余空格代码如下: fastIndex走的快,slowIndex走的慢,最后slowIndex就标记着移除多余空格后新字符串的长度。 @@ -122,7 +125,7 @@ void removeExtraSpaces(string& s) { 此时我们已经实现了removeExtraSpaces函数来移除冗余空格。 -还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)和[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)里已经讲过了。 +还做实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)和[541.反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)里已经讲过了。 代码如下: @@ -135,11 +138,8 @@ void reverse(string& s, int start, int end) { } ``` -## 本题C++整体代码 +本题C++整体代码 -效率: - - ```C++ // 版本一 @@ -203,6 +203,7 @@ public: return s; } + // 当然这里的主函数reverseWords写的有一些冗余的,可以精简一些,精简之后的主函数为: /* 主函数简单写法 string reverseWords(string s) { removeExtraSpaces(s); @@ -220,25 +221,8 @@ public: }; ``` -当然这里的主函数reverseWords写的有一些冗余的,可以精简一些,精简之后的主函数为: - -```C++ -// 注意这里仅仅是主函数,其他函数和版本一一致 -string reverseWords(string s) { - removeExtraSpaces(s); - reverse(s, 0, s.size() - 1); - for(int i = 0; i < s.size(); i++) { - int j = i; - // 查找单词间的空格,翻转单词 - while(j < s.size() && s[j] != ' ') j++; - reverse(s, i, j - 1); - i = j; - } - return s; -} -``` - - +效率: + @@ -316,7 +300,57 @@ class Solution { } ``` -Python: + +```Python3 +class Solution: + #1.去除多余的空格 + def trim_spaces(self,s): + n=len(s) + left=0 + right=n-1 + + while left<=right and s[left]==' ': #去除开头的空格 + left+=1 + while left<=right and s[right]==' ': #去除结尾的空格 + right=right-1 + tmp=[] + while left<=right: #去除单词中间多余的空格 + if s[left]!=' ': + tmp.append(s[left]) + elif tmp[-1]!=' ': #当前位置是空格,但是相邻的上一个位置不是空格,则该空格是合理的 + tmp.append(s[left]) + left+=1 + return tmp +#2.翻转字符数组 + def reverse_string(self,nums,left,right): + while left ListNode: + + def reverse(pre,cur): + if not cur: + return pre + + tmp = cur.next + cur.next = pre + + return reverse(cur,tmp) + + return reverse(None,head) + +``` + + + Go: ```go diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index 90280451..42687514 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -109,7 +109,7 @@ public: }; ``` -时间复杂度:$O(n)$ +时间复杂度:$O(n)$ 空间复杂度:$O(1)$ **一些录友会疑惑为什么时间复杂度是O(n)**。 @@ -118,8 +118,8 @@ public: ## 相关题目推荐 -* 904.水果成篮 -* 76.最小覆盖子串 +* [904.水果成篮](https://leetcode-cn.com/problems/fruit-into-baskets/) +* [76.最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/) diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index 9f75b23d..15220464 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -34,7 +34,7 @@ 本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。 -相对于[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),无非就是多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。 +相对于[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),无非就是多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。 想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。 @@ -53,7 +53,7 @@ * **确定递归函数参数** -和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。 +和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。 这里我依然定义path 和 result为全局变量。 @@ -103,7 +103,7 @@ if (path.size() == k) { * **单层搜索过程** -本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9],所以for循环固定i<=9 +本题和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9],所以for循环固定i<=9 如图: ![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) @@ -227,6 +227,44 @@ public: Java: + +模板方法 +```java +class Solution { + List> result = new ArrayList<>(); + LinkedList path = new LinkedList<>(); + + public List> combinationSum3(int k, int n) { + backTracking(n, k, 1, 0); + return result; + } + + private void backTracking(int targetSum, int k, int startIndex, int sum) { + // 减枝 + if (sum > targetSum) { + return; + } + + if (path.size() == k) { + if (sum == targetSum) result.add(new ArrayList<>(path)); + return; + } + + // 减枝 9 - (k - path.size()) + 1 + for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { + path.add(i); + sum += i; + backTracking(targetSum, k, i + 1, sum); + //回溯 + path.removeLast(); + //回溯 + sum -= i; + } + } +} +``` + +其他方法 ```java class Solution { List> res = new ArrayList<>(); @@ -284,6 +322,37 @@ class Solution: Go: + +> 回溯+减枝 + +```go +func combinationSum3(k int, n int) [][]int { + var track []int// 遍历路径 + var result [][]int// 存放结果集 + backTree(n,k,1,&track,&result) + return result +} +func backTree(n,k,startIndex int,track *[]int,result *[][]int){ + if len(*track)==k{ + var sum int + tmp:=make([]int,k) + for k,v:=range *track{ + sum+=v + tmp[k]=v + } + if sum==n{ + *result=append(*result,tmp) + } + return + } + for i:=startIndex;i<=9-(k-len(*track))+1;i++{//减枝(k-len(*track)表示还剩多少个可填充的元素) + *track=append(*track,i)//记录路径 + backTree(n,k,i+1,track,result)//递归 + *track=(*track)[:len(*track)-1]//回溯 + } +} +``` + javaScript: ```js diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index 998e22f3..ec68b6c6 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -335,6 +335,30 @@ func countNodes(root *TreeNode) int { } ``` +利用完全二叉树特性的递归解法 +```go +func countNodes(root *TreeNode) int { + if root == nil { + return 0 + } + leftH, rightH := 0, 0 + leftNode := root.Left + rightNode := root.Right + for leftNode != nil { + leftNode = leftNode.Left + leftH++ + } + for rightNode != nil { + rightNode = rightNode.Right + rightH++ + } + if leftH == rightH { + return (2 << leftH) - 1 + } + return countNodes(root.Left) + countNodes(root.Right) + 1 +} +``` + JavaScript: diff --git a/problems/0225.用队列实现栈.md b/problems/0225.用队列实现栈.md index 055c2e3d..85b981e5 100644 --- a/problems/0225.用队列实现栈.md +++ b/problems/0225.用队列实现栈.md @@ -34,7 +34,7 @@ https://leetcode-cn.com/problems/implement-stack-using-queues/ 有的同学可能疑惑这种题目有什么实际工程意义,**其实很多算法题目主要是对知识点的考察和教学意义远大于其工程实践的意义,所以面试题也是这样!** -刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/P6tupDwRFi6Ay-L7DT4NVg)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行! +刚刚做过[栈与队列:我用栈来实现队列怎么样?](https://mp.weixin.qq.com/s/Cj6R0qu8rFA7Et9V_ZMjCA)的同学可能依然想着用一个输入队列,一个输出队列,就可以模拟栈的功能,仔细想一下还真不行! **队列模拟栈,其实一个队列就够了**,那么我们先说一说两个队列来实现栈的思路。 @@ -46,18 +46,21 @@ https://leetcode-cn.com/problems/implement-stack-using-queues/ 如下面动画所示,**用两个队列que1和que2实现队列的功能,que2其实完全就是一个备份的作用**,把que1最后面的元素以外的元素都备份到que2,然后弹出最后面的元素,再把其他元素从que2导回que1。 -模拟的队列执行语句如下: -queue.push(1); -queue.push(2); -queue.pop(); // 注意弹出的操作 -queue.push(3); -queue.push(4); -queue.pop(); // 注意弹出的操作 -queue.pop(); -queue.pop(); -queue.empty(); +模拟的队列执行语句如下: -![225.用队列实现栈](https://code-thinking.cdn.bcebos.com/gifs/225.%E7%94%A8%E9%98%9F%E5%88%97%E5%AE%9E%E7%8E%B0%E6%A0%88.gif) +``` +queue.push(1); +queue.push(2); +queue.pop(); // 注意弹出的操作 +queue.push(3); +queue.push(4); +queue.pop(); // 注意弹出的操作 +queue.pop(); +queue.pop(); +queue.empty(); +``` + +![225.用队列实现栈](https://code-thinking.cdn.bcebos.com/gifs/225.用队列实现栈.gif) 详细如代码注释所示: @@ -152,7 +155,7 @@ public: }; ``` -## 其他语言版本 +# 其他语言版本 Java: diff --git a/problems/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md index 2b628ec4..bb0d6d34 100644 --- a/problems/0226.翻转二叉树.md +++ b/problems/0226.翻转二叉树.md @@ -205,6 +205,7 @@ public: Java: ```Java +//DFS递归 class Solution { /** * 前后序遍历都可以 @@ -226,6 +227,31 @@ class Solution { root.right = tmp; } } + +//BFS +class Solution { + public TreeNode invertTree(TreeNode root) { + if (root == null) {return null;} + ArrayDeque deque = new ArrayDeque<>(); + deque.offer(root); + while (!deque.isEmpty()) { + int size = deque.size(); + while (size-- > 0) { + TreeNode node = deque.poll(); + swap(node); + if (node.left != null) {deque.offer(node.left);} + if (node.right != null) {deque.offer(node.right);} + } + } + return root; + } + + public void swap(TreeNode root) { + TreeNode temp = root.left; + root.left = root.right; + root.right = temp; + } +} ``` Python: diff --git a/problems/0232.用栈实现队列.md b/problems/0232.用栈实现队列.md index c2af71f7..be6e1df6 100644 --- a/problems/0232.用栈实现队列.md +++ b/problems/0232.用栈实现队列.md @@ -15,10 +15,10 @@ https://leetcode-cn.com/problems/implement-queue-using-stacks/ 使用栈实现队列的下列操作: -push(x) -- 将一个元素放入队列的尾部。 -pop() -- 从队列首部移除元素。 -peek() -- 返回队列首部的元素。 -empty() -- 返回队列是否为空。 +push(x) -- 将一个元素放入队列的尾部。 +pop() -- 从队列首部移除元素。 +peek() -- 返回队列首部的元素。 +empty() -- 返回队列是否为空。 示例: @@ -46,18 +46,18 @@ queue.empty(); // 返回 false 下面动画模拟以下队列的执行过程如下: -执行语句: -queue.push(1); -queue.push(2); -queue.pop(); **注意此时的输出栈的操作** -queue.push(3); -queue.push(4); -queue.pop(); -queue.pop();**注意此时的输出栈的操作** -queue.pop(); -queue.empty(); +执行语句: +queue.push(1); +queue.push(2); +queue.pop(); **注意此时的输出栈的操作** +queue.push(3); +queue.push(4); +queue.pop(); +queue.pop();**注意此时的输出栈的操作** +queue.pop(); +queue.empty(); -![232.用栈实现队列版本2](https://code-thinking.cdn.bcebos.com/gifs/232.%E7%94%A8%E6%A0%88%E5%AE%9E%E7%8E%B0%E9%98%9F%E5%88%97%E7%89%88%E6%9C%AC2.gif) +![232.用栈实现队列版本2](https://code-thinking.cdn.bcebos.com/gifs/232.用栈实现队列版本2.gif) 在push数据的时候,只要数据放进输入栈就好,**但在pop的时候,操作就复杂一些,输出栈如果为空,就把进栈数据全部导入进来(注意是全部导入)**,再从出栈弹出数据,如果输出栈不为空,则直接从出栈弹出数据就可以了。 @@ -125,8 +125,6 @@ public: - - ## 其他语言版本 Java: diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md index 15ff7af4..dffc89e6 100644 --- a/problems/0235.二叉搜索树的最近公共祖先.md +++ b/problems/0235.二叉搜索树的最近公共祖先.md @@ -265,7 +265,94 @@ class Solution: else: return root ``` Go: +> BSL法 +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +//利用BSL的性质(前序遍历有序) +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + if root==nil{return nil} + if root.Val>p.Val&&root.Val>q.Val{//当前节点的值大于给定的值,则说明满足条件的在左边 + return lowestCommonAncestor(root.Left,p,q) + }else if root.Val 普通法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +//递归会将值层层返回 +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + //终止条件 + if root==nil||root.Val==p.Val||root.Val==q.Val{return root}//最后为空或者找到一个值时,就返回这个值 + //后序遍历 + findLeft:=lowestCommonAncestor(root.Left,p,q) + findRight:=lowestCommonAncestor(root.Right,p,q) + //处理单层逻辑 + if findLeft!=nil&&findRight!=nil{return root}//说明在root节点的两边 + if findLeft==nil{//左边没找到,就说明在右边找到了 + return findRight + }else {return findLeft} +} +``` + +JavaScript版本: +1. 使用递归的方法 +```javascript +var lowestCommonAncestor = function(root, p, q) { + // 使用递归的方法 + // 1. 使用给定的递归函数lowestCommonAncestor + // 2. 确定递归终止条件 + if(root === null) { + return root; + } + if(root.val>p.val&&root.val>q.val) { + // 向左子树查询 + let left = lowestCommonAncestor(root.left,p,q); + return left !== null&&left; + } + if(root.valp.val&&root.val>q.val) { + root = root.left; + }else if(root.val + 提示: -1 <= nums.length <= 10^5 --10^4 <= nums[i] <= 10^4 -1 <= k <= nums.length +* 1 <= nums.length <= 10^5 +* -10^4 <= nums[i] <= 10^4 +* 1 <= k <= nums.length @@ -84,7 +84,7 @@ public: 动画如下: -![239.滑动窗口最大值](https://code-thinking.cdn.bcebos.com/gifs/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.gif) +![239.滑动窗口最大值](https://code-thinking.cdn.bcebos.com/gifs/239.滑动窗口最大值.gif) 对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。 @@ -100,11 +100,11 @@ public: 为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下: -![239.滑动窗口最大值-2](https://code-thinking.cdn.bcebos.com/gifs/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC-2.gif) +![239.滑动窗口最大值-2](https://code-thinking.cdn.bcebos.com/gifs/239.滑动窗口最大值-2.gif) 那么我们用什么数据结构来实现这个单调队列呢? -使用deque最为合适,在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)中,我们就提到了常用的queue在没有指定容器的情况下,deque就是默认底层容器。 +使用deque最为合适,在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/HCXfQ_Bhpi63YaX0ZRSnAQ)中,我们就提到了常用的queue在没有指定容器的情况下,deque就是默认底层容器。 基于刚刚说过的单调队列pop和push的规则,代码不难实现,如下: @@ -201,13 +201,12 @@ public: - - -## 其他语言版本 +# 其他语言版本 Java: ```Java +//解法一 //自定义数组 class MyQueue { Deque deque = new LinkedList<>(); @@ -260,6 +259,40 @@ class Solution { return res; } } + +//解法二 +//利用双端队列手动实现单调队列 +/** + * 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可 + * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标) + */ +class Solution { + public int[] maxSlidingWindow(int[] nums, int k) { + ArrayDeque deque = new ArrayDeque<>(); + int n = nums.length; + int[] res = new int[n - k + 1]; + int idx = 0; + for(int i = 0; i < n; i++) { + // 根据题意,i为nums下标,是要在[i - k + 1, k] 中选到最大值,只需要保证两点 + // 1.队列头结点需要在[i - k + 1, k]范围内,不符合则要弹出 + while(!deque.isEmpty() && deque.peek() < i - k + 1){ + deque.poll(); + } + // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出 + while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) { + deque.pollLast(); + } + + deque.offer(i); + + // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了 + if(i >= k - 1){ + res[idx++] = nums[deque.peek()]; + } + } + return res; + } +} ``` Python: @@ -302,36 +335,6 @@ class Solution: Go: -```go -func maxSlidingWindow(nums []int, k int) []int { - var queue []int - var rtn []int - - for f := 0; f < len(nums); f++ { - //维持队列递减, 将 k 插入合适的位置, queue中 <=k 的 元素都不可能是窗口中的最大值, 直接弹出 - for len(queue) > 0 && nums[f] > nums[queue[len(queue)-1]] { - queue = queue[:len(queue)-1] - } - // 等大的后来者也应入队 - if len(queue) == 0 || nums[f] <= nums[queue[len(queue)-1]] { - queue = append(queue, f) - } - - if f >= k - 1 { - rtn = append(rtn, nums[queue[0]]) - //弹出离开窗口的队首 - if f - k + 1 == queue[0] { - queue = queue[1:] - } - } - } - - return rtn - -} - -``` - ```go // 封装单调队列的方式解题 type MyQueue struct { diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index fe7a2604..ce596396 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -283,6 +283,7 @@ public: Java: ```Java +//解法一 class Solution { /** * 递归法 @@ -321,6 +322,52 @@ class Solution { } } +//解法二(常规前序遍历,不用回溯),更容易理解 +class Solution { + public List binaryTreePaths(TreeNode root) { + List res = new ArrayList<>(); + helper(root, new StringBuilder(), res); + return res; + } + + public void helper(TreeNode root, StringBuilder sb, List res) { + if (root == null) {return;} + // 遇到叶子结点就放入当前路径到res集合中 + if (root.left == null && root.right ==null) { + sb.append(root.val); + res.add(sb.toString()); + // 记得结束当前方法 + return; + } + helper(root.left,new StringBuilder(sb).append(root.val + "->"),res); + helper(root.right,new StringBuilder(sb).append(root.val + "->"),res); + } +} + +//针对解法二优化,思路本质是一样的 +class Solution { + public List binaryTreePaths(TreeNode root) { + List res = new ArrayList<>(); + helper(root, "", res); + return res; + } + + public void helper(TreeNode root, String path, List res) { + if (root == null) {return;} + // 由原始解法二可以知道,root的值肯定会下面某一个条件加入到path中,那么干脆直接在这一步加入即可 + StringBuilder sb = new StringBuilder(path); + sb.append(root.val); + if (root.left == null && root.right ==null) { + res.add(sb.toString()); + }else{ + // 如果是非叶子结点则还需要跟上一个 “->” + sb.append("->"); + helper(root.left,sb.toString(),res); + helper(root.right,sb.toString(),res); + } + } +} + ``` Python: @@ -351,6 +398,29 @@ class Solution: ``` Go: +```go +func binaryTreePaths(root *TreeNode) []string { + res := make([]string, 0) + var travel func(node *TreeNode, s string) + travel = func(node *TreeNode, s string) { + if node.Left == nil && node.Right == nil { + v := s + strconv.Itoa(node.Val) + res = append(res, v) + return + } + s = s + strconv.Itoa(node.Val) + "->" + if node.Left != nil { + travel(node.Left, s) + } + if node.Right != nil { + travel(node.Right, s) + } + } + travel(root, "") + return res +} +``` + JavaScript: 1.递归版本 ```javascript diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index 60d6d165..d0922de1 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -214,8 +214,26 @@ class Solution: return dp[n] ``` +Python3: +```python +class Solution: + def numSquares(self, n: int) -> int: + # 初始化 + # 组成和的完全平方数的最多个数,就是只用1构成 + # 因此,dp[i] = i + dp = [i for i in range(n + 1)] + # dp[0] = 0 无意义,只是为了方便记录特殊情况: + # n本身就是完全平方数,dp[n] = min(dp[n], dp[n - n] + 1) = 1 + for i in range(1, n): # 遍历物品 + if i * i > n: + break + num = i * i + for j in range(num, n + 1): # 遍历背包 + dp[j] = min(dp[j], dp[j - num] + 1) + return dp[n] +``` Go: ```go diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 97059e4a..3c7e34d8 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -33,6 +33,9 @@ ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[分割问题](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA),[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),[排列问题](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)。 直觉上来看 这道题和回溯法没有什么关系,更像是图论中的深度优先搜索。 diff --git a/problems/0337.打家劫舍III.md b/problems/0337.打家劫舍III.md index 0a4b752b..3504a574 100644 --- a/problems/0337.打家劫舍III.md +++ b/problems/0337.打家劫舍III.md @@ -287,24 +287,85 @@ class Solution { Python: -> 动态规划 -```python +> 暴力递归 + +```python3 + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right class Solution: def rob(self, root: TreeNode) -> int: - result = self.robTree(root) + if root is None: + return 0 + if root.left is None and root.right is None: + return root.val + # 偷父节点 + val1 = root.val + if root.left: + val1 += self.rob(root.left.left) + self.rob(root.left.right) + if root.right: + val1 += self.rob(root.right.left) + self.rob(root.right.right) + # 不偷父节点 + val2 = self.rob(root.left) + self.rob(root.right) + return max(val1, val2) +``` + +> 记忆化递归 + +```python3 + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + memory = {} + def rob(self, root: TreeNode) -> int: + if root is None: + return 0 + if root.left is None and root.right is None: + return root.val + if self.memory.get(root) is not None: + return self.memory[root] + # 偷父节点 + val1 = root.val + if root.left: + val1 += self.rob(root.left.left) + self.rob(root.left.right) + if root.right: + val1 += self.rob(root.right.left) + self.rob(root.right.right) + # 不偷父节点 + val2 = self.rob(root.left) + self.rob(root.right) + self.memory[root] = max(val1, val2) + return max(val1, val2) +``` + +> 动态规划 +```python3 +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def rob(self, root: TreeNode) -> int: + result = self.rob_tree(root) return max(result[0], result[1]) - #长度为2的数组,0:不偷,1:偷 - def robTree(self, cur): - if not cur: - return (0, 0) #这里返回tuple, 也可以返回list - left = self.robTree(cur.left) - right = self.robTree(cur.right) - #偷cur - val1 = cur.val + left[0] + right[0] - #不偷cur - val2 = max(left[0], left[1]) + max(right[0], right[1]) - return (val2, val1) + def rob_tree(self, node): + if node is None: + return (0, 0) # (偷当前节点金额,不偷当前节点金额) + left = self.rob_tree(node.left) + right = self.rob_tree(node.right) + val1 = node.val + left[1] + right[1] # 偷当前节点,不能偷子节点 + val2 = max(left[0], left[1]) + max(right[0], right[1]) # 不偷当前节点,可偷可不偷子节点 + return (val1, val2) ``` Go: diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index cf60575f..7b0dbd0f 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -225,7 +225,20 @@ class Solution: Go: +Javascript: +```Javascript +var integerBreak = function(n) { + let dp = new Array(n + 1).fill(0) + dp[2] = 1 + for(let i = 3; i <= n; i++) { + for(let j = 1; j < i; j++) { + dp[i] = Math.max(dp[i], dp[i - j] * j, (i - j) * j) + } + } + return dp[n] +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0344.反转字符串.md b/problems/0344.反转字符串.md index 1b86e847..aeb13a30 100644 --- a/problems/0344.反转字符串.md +++ b/problems/0344.反转字符串.md @@ -20,14 +20,13 @@ https://leetcode-cn.com/problems/reverse-string/ 你可以假设数组中的所有字符都是 ASCII 码表中的可打印字符。 -示例 1: +示例 1: +输入:["h","e","l","l","o"] +输出:["o","l","l","e","h"] -输入:["h","e","l","l","o"] -输出:["o","l","l","e","h"] -示例 2: - -输入:["H","a","n","n","a","h"] -输出:["h","a","n","n","a","H"] +示例 2: +输入:["H","a","n","n","a","h"] +输出:["h","a","n","n","a","H"] # 思路 @@ -56,7 +55,7 @@ https://leetcode-cn.com/problems/reverse-string/ 接下来再来讲一下如何解决反转字符串的问题。 -大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)。 +大家应该还记得,我们已经讲过了[206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)。 在反转链表中,使用了双指针的方法。 @@ -64,7 +63,7 @@ https://leetcode-cn.com/problems/reverse-string/ 因为字符串也是一种数组,所以元素在内存中是连续分布,这就决定了反转链表和反转字符串方式上还是有所差异的。 -如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ),[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg)。 +如果对数组和链表原理不清楚的同学,可以看这两篇,[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw),[必须掌握的数组理论知识](https://mp.weixin.qq.com/s/c2KABb-Qgg66HrGf8z-8Og)。 对于字符串,我们定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。 @@ -119,8 +118,7 @@ s[i] ^= s[j]; 相信大家本着我所讲述的原则来做字符串相关的题目,在选择库函数的角度上会有所原则,也会有所收获。 - -## C++代码 +C++代码如下: ```C++ class Solution { @@ -157,7 +155,7 @@ class Solution { ``` Python: -```python3 +```python class Solution: def reverseString(self, s: List[str]) -> None: """ @@ -168,6 +166,17 @@ class Solution: s[left], s[right] = s[right], s[left] left += 1 right -= 1 + +# 下面的写法更加简洁,但是都是同样的算法 +# class Solution: +# def reverseString(self, s: List[str]) -> None: +# """ +# Do not return anything, modify s in-place instead. +# """ + # 不需要判别是偶数个还是奇数个序列,因为奇数个的时候,中间那个不需要交换就可 +# for i in range(len(s)//2): +# s[i], s[len(s)-1-i] = s[len(s)-1-i], s[i] +# return s ``` Go: diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md index 090480a4..5c635d39 100644 --- a/problems/0349.两个数组的交集.md +++ b/problems/0349.两个数组的交集.md @@ -118,7 +118,7 @@ class Solution { ``` Python: -```python3 +```python class Solution: def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: result_set = set() diff --git a/problems/0383.赎金信.md b/problems/0383.赎金信.md index 2f3c4f4d..a5315b0e 100644 --- a/problems/0383.赎金信.md +++ b/problems/0383.赎金信.md @@ -135,8 +135,52 @@ class Solution { ``` -Python: -```py +Python写法一(使用数组作为哈希表): + +```python +class Solution: + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + + arr = [0] * 26 + + for x in magazine: + arr[ord(x) - ord('a')] += 1 + + for x in ransomNote: + if arr[ord(x) - ord('a')] == 0: + return False + else: + arr[ord(x) - ord('a')] -= 1 + + return True +``` + +Python写法二(使用defaultdict): + +```python +class Solution: + def canConstruct(self, ransomNote: str, magazine: str) -> bool: + + from collections import defaultdict + + hashmap = defaultdict(int) + + for x in magazine: + hashmap[x] += 1 + + for x in ransomNote: + value = hashmap.get(x) + if value is None or value == 0: + return False + else: + hashmap[x] -= 1 + + return True +``` + +Python写法三: + +```python class Solution(object): def canConstruct(self, ransomNote, magazine): """ @@ -166,6 +210,7 @@ class Solution(object): ``` Go: + ```go func canConstruct(ransomNote string, magazine string) bool { record := make([]int, 26) diff --git a/problems/0392.判断子序列.md b/problems/0392.判断子序列.md index 9048ac44..d97d2684 100644 --- a/problems/0392.判断子序列.md +++ b/problems/0392.判断子序列.md @@ -140,8 +140,29 @@ public: ## 其他语言版本 -Java: - +Java: +``` +class Solution { + public boolean isSubsequence(String s, String t) { + int length1 = s.length(); int length2 = t.length(); + int[][] dp = new int[length1+1][length2+1]; + for(int i = 1; i <= length1; i++){ + for(int j = 1; j <= length2; j++){ + if(s.charAt(i-1) == t.charAt(j-1)){ + dp[i][j] = dp[i-1][j-1] + 1; + }else{ + dp[i][j] = dp[i][j-1]; + } + } + } + if(dp[length1][length2] == length1){ + return true; + }else{ + return false; + } + } +} +``` Python: ```python diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index 9aca5b94..2b447da4 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -43,9 +43,9 @@ 本题有两个维度,h和k,看到这种题目一定要想如何确定一个维度,然后在按照另一个维度重新排列。 -其实如果大家认真做了[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),就会发现和此题有点点的像。 +其实如果大家认真做了[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),就会发现和此题有点点的像。 -在[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。 +在[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。 **如果两个维度一起考虑一定会顾此失彼**。 @@ -161,11 +161,11 @@ public: ## 总结 -关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。 +关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。 **其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼**。 -这道题目可以说比[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。 +这道题目可以说比[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。 最后我给出了两个版本的代码,可以明显看是使用C++中的list(底层链表实现)比vector(数组)效率高得多。 @@ -214,13 +214,10 @@ Python: ```python class Solution: def reconstructQueue(self, people: List[List[int]]) -> List[List[int]]: - people.sort(key=lambda x: (x[0], -x[1]), reverse=True) + people.sort(key=lambda x: (-x[0], x[1])) que = [] for p in people: - if p[1] > len(que): - que.append(p) - else: - que.insert(p[1], p) + que.insert(p[1], p) return que ``` diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 0d306c74..75c665cd 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -240,6 +240,26 @@ class Solution: Go: +javaScript: + +```js +var canPartition = function(nums) { + const sum = (nums.reduce((p, v) => p + v)); + if (sum & 1) return false; + const dp = Array(sum / 2 + 1).fill(0); + for(let i = 0; i < nums.length; i++) { + for(let j = sum / 2; j >= nums[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); + if (dp[j] === sum / 2) { + return true; + } + } + } + return dp[sum / 2] === sum / 2; +}; +``` + + ----------------------- diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index 248526a1..37bb0b5d 100644 --- a/problems/0435.无重叠区间.md +++ b/problems/0435.无重叠区间.md @@ -122,9 +122,9 @@ public: ## 补充 -本题其实和[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 +本题其实和[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 -把[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,就可以AC本题。 +把[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,就可以AC本题。 ```C++ class Solution { @@ -228,7 +228,27 @@ class Solution: Go: +Javascript: +```Javascript +var eraseOverlapIntervals = function(intervals) { + intervals.sort((a, b) => { + return a[1] - b[1] + }) + let count = 1 + let end = intervals[0][1] + + for(let i = 1; i < intervals.length; i++) { + let interval = intervals[i] + if(interval[0] >= right) { + end = interval[1] + count += 1 + } + } + + return intervals.length - count +}; +``` ----------------------- diff --git a/problems/0450.删除二叉搜索树中的节点.md b/problems/0450.删除二叉搜索树中的节点.md index 3ed44c0a..4695ed50 100644 --- a/problems/0450.删除二叉搜索树中的节点.md +++ b/problems/0450.删除二叉搜索树中的节点.md @@ -67,7 +67,6 @@ if (root == nullptr) return root; 第五种情况有点难以理解,看下面动画: ![450.删除二叉搜索树中的节点](https://tva1.sinaimg.cn/large/008eGmZEly1gnbj3k596mg30dq0aigyz.gif) - 动画中颗二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。 @@ -359,6 +358,51 @@ func deleteNode1(root *TreeNode)*TreeNode{ } ``` +JavaScript版本 + +> 递归 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} key + * @return {TreeNode} + */ +var deleteNode = function (root, key) { + if (root === null) + return root; + if (root.val === key) { + if (!root.left) + return root.right; + else if (!root.right) + return root.left; + else { + let cur = root.right; + while (cur.left) { + cur = cur.left; + } + cur.left = root.left; + let temp = root; + root = root.right; + delete root; + return root; + } + } + if (root.val > key) + root.left = deleteNode(root.left, key); + if (root.val < key) + root.right = deleteNode(root.right, key); + return root; +}; +``` diff --git a/problems/0454.四数相加II.md b/problems/0454.四数相加II.md index 835178dd..0621ab5b 100644 --- a/problems/0454.四数相加II.md +++ b/problems/0454.四数相加II.md @@ -120,7 +120,7 @@ class Solution { Python: -``` +```python class Solution(object): def fourSumCount(self, nums1, nums2, nums3, nums4): """ @@ -147,7 +147,31 @@ class Solution(object): if key in hashmap: count += hashmap[key] return count + +# 下面这个写法更为简洁,但是表达的是同样的算法 +# class Solution: +# def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int: +# from collections import defaultdict + +# hashmap = defaultdict(int) + +# for x1 in nums1: +# for x2 in nums2: +# hashmap[x1+x2] += 1 + +# count=0 +# for x3 in nums3: +# for x4 in nums4: +# key = -x3-x4 +# value = hashmap.get(key) + + # dict的get方法会返回None(key不存在)或者key对应的value + # 所以如果value==0,就会继续执行or,count+0,否则就会直接加value + # 这样就不用去写if判断了 + +# count += value or 0 +# return count ``` diff --git a/problems/0459.重复的子字符串.md b/problems/0459.重复的子字符串.md index deb755bf..368489a5 100644 --- a/problems/0459.重复的子字符串.md +++ b/problems/0459.重复的子字符串.md @@ -17,18 +17,18 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/ 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。 -示例 1: -输入: "abab" -输出: True -解释: 可由子字符串 "ab" 重复两次构成。 +示例 1: +输入: "abab" +输出: True +解释: 可由子字符串 "ab" 重复两次构成。 -示例 2: -输入: "aba" -输出: False +示例 2: +输入: "aba" +输出: False -示例 3: -输入: "abcabcabcabc" -输出: True +示例 3: +输入: "abcabcabcabc" +输出: True 解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。) # 思路 @@ -41,13 +41,11 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/ * [帮你把KMP算法学个通透!(求next数组代码篇)](https://www.bilibili.com/video/BV1M5411j7Xx) -如果KMP还不够了解,可以看我的这个视频[帮你把KMP算法学个通透!B站](https://www.bilibili.com/video/BV1PD4y1o7nd/) - -我们在[字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)里提到了,在一个串中查找是否出现过另一个串,这是KMP的看家本领。 +我们在[字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)里提到了,在一个串中查找是否出现过另一个串,这是KMP的看家本领。 那么寻找重复子串怎么也涉及到KMP算法了呢? -这里就要说一说next数组了,next 数组记录的就是最长相同前后缀( [字符串:听说你对KMP有这些疑问?](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀), 如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。 +这里就要说一说next数组了,next 数组记录的就是最长相同前后缀( [字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀), 如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。 最长相等前后缀的长度为:next[len - 1] + 1。 @@ -62,7 +60,7 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/ 如图: -![459.重复的子字符串_1](https://code-thinking.cdn.bcebos.com/pics/459.%E9%87%8D%E5%A4%8D%E7%9A%84%E5%AD%90%E5%AD%97%E7%AC%A6%E4%B8%B2_1.png) +![459.重复的子字符串_1](https://code-thinking.cdn.bcebos.com/pics/459.重复的子字符串_1.png) next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf的最长相同前后缀的长度。 @@ -70,7 +68,7 @@ next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf (len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。 -代码如下:(这里使用了前缀表统一减一的实现方式) +C++代码如下:(这里使用了前缀表统一减一的实现方式) ```C++ class Solution { @@ -104,7 +102,7 @@ public: ``` -前缀表(不减一)的代码实现 +前缀表(不减一)的C++代码实现 ```C++ class Solution { @@ -139,12 +137,11 @@ public: # 拓展 -此时我们已经分享了三篇KMP的文章,首先是[字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)讲解KMP算法的基础理论,给出next数组究竟是如何来了,前缀表又是怎么回事,为什么要选择前缀表。 +在[字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中讲解KMP算法的基础理论,给出next数组究竟是如何来了,前缀表又是怎么回事,为什么要选择前缀表。 -然后通过[字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)讲解一道KMP的经典题目,判断文本串里是否出现过模式串,这里涉及到构造next数组的代码实现,以及使用next数组完成模式串与文本串的匹配过程。 - -后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串:听说你对KMP有这些疑问?](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ)中又给出了详细的讲解。 +讲解一道KMP的经典题目,力扣:28. 实现 strStr(),判断文本串里是否出现过模式串,这里涉及到构造next数组的代码实现,以及使用next数组完成模式串与文本串的匹配过程。 +后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串:KMP算法精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)给出了详细的讲解。 ## 其他语言版本 @@ -301,4 +298,4 @@ func repeatedSubstringPattern(s string) bool { * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
\ No newline at end of file +
diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index fd925f76..6b73c080 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -244,6 +244,35 @@ func max(a,b int) int { } ``` +Javascript: +```javascript +const findMaxForm = (strs, m, n) => { + const dp = Array.from(Array(m+1), () => Array(n+1).fill(0)); + let numOfZeros, numOfOnes; + + for(let str of strs) { + numOfZeros = 0; + numOfOnes = 0; + + for(let c of str) { + if (c === '0') { + numOfZeros++; + } else { + numOfOnes++; + } + } + + for(let i = m; i >= numOfZeros; i--) { + for(let j = n; j >= numOfOnes; j--) { + dp[i][j] = Math.max(dp[i][j], dp[i - numOfZeros][j - numOfOnes] + 1); + } + } + } + + return dp[m][n]; +}; +``` + ----------------------- diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 5538a2c9..85c9d307 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -28,6 +28,9 @@ ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。 这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)。 diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index c917ed5c..b7da0252 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -158,7 +158,7 @@ dp[j] 表示:填满j(包括j)这么大容积的包,有dp[i]种方法 那么只需要搞到一个2(nums[i]),有dp[3]方法可以凑齐容量为3的背包,相应的就有多少种方法可以凑齐容量为5的背包。 -那么需要把 这些方法累加起来就可以了,dp[i] += dp[j - nums[i]] +那么需要把 这些方法累加起来就可以了,dp[j] += dp[j - nums[i]] 所以求组合类问题的公式,都是类似这种: @@ -306,6 +306,36 @@ func findTargetSumWays(nums []int, target int) int { } ``` +Javascript: +```javascript +const findTargetSumWays = (nums, target) => { + + const sum = nums.reduce((a, b) => a+b); + + if(target > sum) { + return 0; + } + + if((target + sum) % 2) { + return 0; + } + + const halfSum = (target + sum) / 2; + nums.sort((a, b) => a - b); + + let dp = new Array(halfSum+1).fill(0); + dp[0] = 1; + + for(let i = 0; i < nums.length; i++) { + for(let j = halfSum; j >= nums[i]; j--) { + dp[j] += dp[j - nums[i]]; + } + } + + return dp[halfSum]; +}; +``` + ----------------------- diff --git a/problems/0496.下一个更大元素I.md b/problems/0496.下一个更大元素I.md new file mode 100644 index 00000000..88e89e52 --- /dev/null +++ b/problems/0496.下一个更大元素I.md @@ -0,0 +1,187 @@ + +# 496.下一个更大元素 I + +题目链接:https://leetcode-cn.com/problems/next-greater-element-i/ + +给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。 + +请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。 + +nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。 + +示例 1: + +输入: nums1 = [4,1,2], nums2 = [1,3,4,2]. +输出: [-1,3,-1] +解释: +对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。 +对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。 +对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。 + +示例 2: +输入: nums1 = [2,4], nums2 = [1,2,3,4]. +输出: [3,-1] +解释: +对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。 +对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出-1 。 +  +提示: + +* 1 <= nums1.length <= nums2.length <= 1000 +* 0 <= nums1[i], nums2[i] <= 10^4 +* nums1和nums2中所有整数 互不相同 +* nums1 中的所有整数同样出现在 nums2 中 + +# 思路 + +做本题之前,建议先做一下[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q) + +在[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q)中是求每个元素下一个比当前元素大的元素的位置。 + +本题则是说nums1 是 nums2的子集,找nums1中的元素在nums2中下一个比当前元素大的元素。 + +看上去和[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q) 就如出一辙了。 + +几乎是一样的,但是这么绕了一下,其实还上升了一点难度。 + +需要对单调栈使用的更熟练一些,才能顺利的把本题写出来。 + +从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。 + +一些同学可能看到两个数组都已经懵了,不知道要定一个一个多大的result数组来存放结果了。 + +**这么定义这个result数组初始化应该为多少呢?** + +题目说如果不存在对应位置就输出 -1 ,所以result数组如果某位置没有被赋值,那么就应该是是-1,所以就初始化为-1。 + +在遍历nums2的过程中,我们要判断nums2[i]是否在nums1中出现过,因为最后是要根据nums1元素的下标来更新result数组。 + +**注意题目中说是两个没有重复元素 的数组 nums1 和 nums2**。 + +没有重复元素,我们就可以用map来做映射了。根据数值快速找到下标,还可以判断nums2[i]是否在nums1中出现过。 + +C++中,当我们要使用集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的。我在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中也做了详细的解释。 + +那么预处理代码如下: + +```C++ +unordered_map umap; // key:下表元素,value:下表 +for (int i = 0; i < nums1.size(); i++) { + umap[nums1[i]] = i; +} +``` + +使用单调栈,首先要想单调栈是从大到小还是从小到大。 + +本题和739. 每日温度是一样的。 + +栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。 + +可能这里有一些同学不理解,那么可以自己尝试一下用递减栈,能不能求出来。其实递减栈就是求右边第一个比自己小的元素了。 + + +接下来就要分析如下三种情况,一定要分析清楚。 + +1. 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况 + +此时满足递增栈(栈头到栈底的顺序),所以直接入栈。 + +2. 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况 + +如果相等的话,依然直接入栈,因为我们要求的是右边第一个比自己大的元素,而不是大于等于! + +3. 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况 + +此时如果入栈就不满足递增栈了,这也是找到右边第一个比自己大的元素的时候。 + +判断栈顶元素是否在nums1里出现过,(注意栈里的元素是nums2的元素),如果出现过,开始记录结果。 + +记录结果这块逻辑有一点小绕,要清楚,此时栈顶元素在nums2中右面第一个大的元素是nums2[i]即当前遍历元素。 + +代码如下: + +```C++ +while (!st.empty() && nums2[i] > nums2[st.top()]) { + if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 + int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 + result[index] = nums2[i]; + } + st.pop(); +} +st.push(i); +``` + +以上分析完毕,C++代码如下: + + +```C++ +// 版本一 +class Solution { +public: + vector nextGreaterElement(vector& nums1, vector& nums2) { + stack st; + vector result(nums1.size(), -1); + if (nums1.size() == 0) return result; + + unordered_map umap; // key:下表元素,value:下表 + for (int i = 0; i < nums1.size(); i++) { + umap[nums1[i]] = i; + } + st.push(0); + for (int i = 1; i < nums2.size(); i++) { + if (nums2[i] < nums2[st.top()]) { // 情况一 + st.push(i); + } else if (nums2[i] == nums2[st.top()]) { // 情况二 + st.push(i); + } else { // 情况三 + while (!st.empty() && nums2[i] > nums2[st.top()]) { + if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 + int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 + result[index] = nums2[i]; + } + st.pop(); + } + st.push(i); + } + } + return result; + } +}; +``` + +针对版本一,进行代码精简后,代码如下: + + +```C++ +// 版本二 +class Solution { +public: + vector nextGreaterElement(vector& nums1, vector& nums2) { + stack st; + vector result(nums1.size(), -1); + if (nums1.size() == 0) return result; + + unordered_map umap; // key:下表元素,value:下表 + for (int i = 0; i < nums1.size(); i++) { + umap[nums1[i]] = i; + } + st.push(0); + for (int i = 1; i < nums2.size(); i++) { + while (!st.empty() && nums2[i] > nums2[st.top()]) { + if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 + int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 + result[index] = nums2[i]; + } + st.pop(); + } + st.push(i); + } + return result; + } +}; +``` + +精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。 + +建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简! + diff --git a/problems/0501.二叉搜索树中的众数.md b/problems/0501.二叉搜索树中的众数.md index dfd589ce..66be790f 100644 --- a/problems/0501.二叉搜索树中的众数.md +++ b/problems/0501.二叉搜索树中的众数.md @@ -345,6 +345,40 @@ public: Java: +暴力法 +```java +class Solution { + public int[] findMode(FindModeInBinarySearchTree.TreeNode root) { + Map map = new HashMap<>(); + List list = new ArrayList<>(); + if (root == null) return list.stream().mapToInt(Integer::intValue).toArray(); + // 获得频率 Map + searchBST(root, map); + List> mapList = map.entrySet().stream() + .sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue())) + .collect(Collectors.toList()); + list.add(mapList.get(0).getKey()); + // 把频率最高的加入 list + for (int i = 1; i < mapList.size(); i++) { + if (mapList.get(i).getValue() == mapList.get(i - 1).getValue()) { + list.add(mapList.get(i).getKey()); + } else { + break; + } + } + return list.stream().mapToInt(Integer::intValue).toArray(); + } + + void searchBST(FindModeInBinarySearchTree.TreeNode curr, Map map) { + if (curr == null) return; + map.put(curr.val, map.getOrDefault(curr.val, 0) + 1); + searchBST(curr.left, map); + searchBST(curr.right, map); + } + +} +``` + ```Java class Solution { ArrayList resList; @@ -428,8 +462,168 @@ class Solution: return self.res ``` Go: +暴力法(非BSL) +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func findMode(root *TreeNode) []int { + var history map[int]int + var maxValue int + var maxIndex int + var result []int + history=make(map[int]int) + traversal(root,history) + for k,value:=range history{ + if value>maxValue{ + maxValue=value + maxIndex=k + } + } + for k,value:=range history{ + if value==history[maxIndex]{ + result=append(result,k) + } + } + return result +} +func traversal(root *TreeNode,history map[int]int){ + if root.Left!=nil{ + traversal(root.Left,history) + } + if value,ok:=history[root.Val];ok{ + history[root.Val]=value+1 + }else{ + history[root.Val]=1 + } + if root.Right!=nil{ + traversal(root.Right,history) + } +} +``` +计数法,不使用额外空间,利用二叉树性质,中序遍历 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + func findMode(root *TreeNode) []int { + res := make([]int, 0) + count := 1 + max := 1 + var prev *TreeNode + var travel func(node *TreeNode) + travel = func(node *TreeNode) { + if node == nil { + return + } + travel(node.Left) + if prev != nil && prev.Val == node.Val { + count++ + } else { + count = 1 + } + if count >= max { + if count > max && len(res) > 0 { + res = []int{node.Val} + } else { + res = append(res, node.Val) + } + max = count + } + prev = node + travel(node.Right) + } + travel(root) + return res +} +``` + +JavaScript版本: +使用额外空间map的方法: +```javascript +var findMode = function(root) { + // 使用递归中序遍历 + let map = new Map(); + // 1. 确定递归函数以及函数参数 + const traverTree = function(root) { + // 2. 确定递归终止条件 + if(root === null) { + return ; + } + traverTree(root.left); + // 3. 单层递归逻辑 + map.set(root.val,map.has(root.val)?map.get(root.val)+1:1); + traverTree(root.right); + } + traverTree(root); + //上面把数据都存储到map + //下面开始寻找map里面的 + // 定义一个最大出现次数的初始值为root.val的出现次数 + let maxCount = map.get(root.val); + // 定义一个存放结果的数组res + let res = []; + for(let [key,value] of map) { + // 如果当前值等于最大出现次数就直接在res增加该值 + if(value === maxCount) { + res.push(key); + } + // 如果value的值大于原本的maxCount就清空res的所有值,因为找到了更大的 + if(value>maxCount) { + res = []; + maxCount = value; + res.push(key); + } + } + return res; +}; +``` +不使用额外空间,利用二叉树性质,中序遍历(有序): +```javascript +var findMode = function(root) { + // 不使用额外空间,使用中序遍历,设置出现最大次数初始值为1 + let count = 0,maxCount = 1; + let pre = root,res = []; + // 1.确定递归函数及函数参数 + const travelTree = function(cur) { + // 2. 确定递归终止条件 + if(cur === null) { + return ; + } + travelTree(cur.left); + // 3. 单层递归逻辑 + if(pre.val === cur.val) { + count++; + }else { + count = 1; + } + pre = cur; + if(count === maxCount) { + res.push(cur.val); + } + if(count > maxCount) { + res = []; + maxCount = count; + res.push(cur.val); + } + travelTree(cur.right); + } + travelTree(root); + return res; +}; +``` ----------------------- diff --git a/problems/0509.斐波那契数.md b/problems/0509.斐波那契数.md index cf004b50..dddac899 100644 --- a/problems/0509.斐波那契数.md +++ b/problems/0509.斐波那契数.md @@ -220,7 +220,17 @@ func fib(n int) int { return c } ``` - +Javascript: +```Javascript +var fib = function(n) { + let dp = [0, 1] + for(let i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2] + } + console.log(dp) + return dp[n] +}; +``` diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md index 08043ea3..3f907da9 100644 --- a/problems/0518.零钱兑换II.md +++ b/problems/0518.零钱兑换II.md @@ -243,6 +243,22 @@ func change(amount int, coins []int) int { } ``` +Javascript: +```javascript +const change = (amount, coins) => { + let dp = Array(amount + 1).fill(0); + dp[0] = 1; + + for(let i =0; i < coins.length; i++) { + for(let j = coins[i]; j <= amount; j++) { + dp[j] += dp[j - coins[i]]; + } + } + + return dp[amount]; +} +``` + ----------------------- diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md index 0bbc4908..47b2b434 100644 --- a/problems/0530.二叉搜索树的最小绝对差.md +++ b/problems/0530.二叉搜索树的最小绝对差.md @@ -255,7 +255,63 @@ func findMIn(root *TreeNode,res *[]int){ findMIn(root.Right,res) } ``` +```go +// 中序遍历的同时计算最小值 +func getMinimumDifference(root *TreeNode) int { + // 保留前一个节点的指针 + var prev *TreeNode + // 定义一个比较大的值 + min := math.MaxInt64 + var travel func(node *TreeNode) + travel = func(node *TreeNode) { + if node == nil { + return + } + travel(node.Left) + if prev != nil && node.Val - prev.Val < min { + min = node.Val - prev.Val + } + prev = node + travel(node.Right) + } + travel(root) + return min +} +``` +JavaScript版本 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {number} + */ +var getMinimumDifference = function (root) { + let arr = []; + const buildArr = (root) => { + if (root) { + buildArr(root.left); + arr.push(root.val); + buildArr(root.right); + } + } + buildArr(root); + let diff = arr[arr.length - 1]; + for (let i = 1; i < arr.length; ++i) { + if (diff > arr[i] - arr[i - 1]) + diff = arr[i] - arr[i - 1]; + } + return diff; +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0538.把二叉搜索树转换为累加树.md b/problems/0538.把二叉搜索树转换为累加树.md index 765e78d3..3ecb8195 100644 --- a/problems/0538.把二叉搜索树转换为累加树.md +++ b/problems/0538.把二叉搜索树转换为累加树.md @@ -219,7 +219,90 @@ class Solution: Go: +> 弄一个sum暂存其和值 +```go + //右中左 +func bstToGst(root *TreeNode) *TreeNode { + var sum int + RightMLeft(root,&sum) + return root +} +func RightMLeft(root *TreeNode,sum *int) *TreeNode { + if root==nil{return nil}//终止条件,遇到空节点就返回 + RightMLeft(root.Right,sum)//先遍历右边 + temp:=*sum//暂存总和值 + *sum+=root.Val//将总和值变更 + root.Val+=temp//更新节点值 + RightMLeft(root.Left,sum)//遍历左节点 + return root +} +``` + +JavaScript版本 + +> 递归 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +var convertBST = function(root) { + let pre = 0; + const ReverseInOrder = (cur) => { + if(cur) { + ReverseInOrder(cur.right); + cur.val += pre; + pre = cur.val; + ReverseInOrder(cur.left); + } + } + ReverseInOrder(root); + return root; +}; +``` + +> 迭代 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @return {TreeNode} + */ +var convertBST = function (root) { + let pre = 0; + let cur = root; + let stack = []; + while (cur !== null || stack.length !== 0) { + while (cur !== null) { + stack.push(cur); + cur = cur.right; + } + cur = stack.pop(); + cur.val += pre; + pre = cur.val; + cur = cur.left; + } + return root; +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0541.反转字符串II.md b/problems/0541.反转字符串II.md index 00581fc0..47e85161 100644 --- a/problems/0541.反转字符串II.md +++ b/problems/0541.反转字符串II.md @@ -22,8 +22,8 @@ https://leetcode-cn.com/problems/reverse-string-ii/ 示例: -输入: s = "abcdefg", k = 2 -输出: "bacdfeg" +输入: s = "abcdefg", k = 2 +输出: "bacdfeg" # 思路 @@ -38,7 +38,7 @@ https://leetcode-cn.com/problems/reverse-string-ii/ **所以当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。** 性能如下: - + 那么这里具体反转的逻辑我们要不要使用库函数呢,其实用不用都可以,使用reverse来实现反转也没毛病,毕竟不是解题关键部分。 @@ -65,7 +65,7 @@ public: }; ``` -那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)道理是一样的。 +那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)道理是一样的。 下面我实现的reverse函数区间是左闭右闭区间,代码如下: @@ -103,6 +103,7 @@ public: Java: ```Java +//解法一 class Solution { public String reverseStr(String s, int k) { StringBuffer res = new StringBuffer(); @@ -128,6 +129,28 @@ class Solution { return res.toString(); } } + +//解法二(似乎更容易理解点) +//题目的意思其实概括为 每隔2k个反转前k个,尾数不够k个时候全部反转 +class Solution { + public String reverseStr(String s, int k) { + char[] ch = s.toCharArray(); + for(int i = 0; i < ch.length; i += 2 * k){ + int start = i; + //这里是判断尾数够不够k个来取决end指针的位置 + int end = Math.min(ch.length - 1, start + k - 1); + //用异或运算反转 + while(start < end){ + ch[start] ^= ch[end]; + ch[end] ^= ch[start]; + ch[start] ^= ch[end]; + start++; + end--; + } + } + return new String(ch); + } +} ``` Python: diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md index be6bb425..19b58bd3 100644 --- a/problems/0617.合并二叉树.md +++ b/problems/0617.合并二叉树.md @@ -319,7 +319,7 @@ Python: # self.val = val # self.left = left # self.right = right -//递归法*前序遍历 +# 递归法*前序遍历 class Solution: def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: if not root1: return root2 // 如果t1为空,合并之后就应该是t2 @@ -328,6 +328,32 @@ class Solution: root1.left = self.mergeTrees(root1.left , root2.left) //左 root1.right = self.mergeTrees(root1.right , root2.right) //右 return root1 //root1修改了结构和数值 + +# 迭代法-覆盖原来的树 +class Solution: + def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: + if not root1: return root2 + if not root2: return root1 + # 迭代,将树2覆盖到树1 + queue1 = [root1] + queue2 = [root2] + root = root1 + while queue1 and queue2: + root1 = queue1.pop(0) + root2 = queue2.pop(0) + root1.val += root2.val + if not root1.left: # 如果树1左儿子不存在,则覆盖后树1的左儿子为树2的左儿子 + root1.left = root2.left + elif root1.left and root2.left: + queue1.append(root1.left) + queue2.append(root2.left) + + if not root1.right: # 同理,处理右儿子 + root1.right = root2.right + elif root1.right and root2.right: + queue1.append(root1.right) + queue2.append(root2.right) + return root ``` Go: @@ -368,8 +394,52 @@ func mergeTrees(t1 *TreeNode, t2 *TreeNode) *TreeNode { Right: mergeTrees(t1.Right,t2.Right)} return root } + +// 前序遍历简洁版 +func mergeTrees(root1 *TreeNode, root2 *TreeNode) *TreeNode { + if root1 == nil { + return root2 + } + if root2 == nil { + return root1 + } + root1.Val += root2.Val + root1.Left = mergeTrees(root1.Left, root2.Left) + root1.Right = mergeTrees(root1.Right, root2.Right) + return root1 +} ``` +JavaScript: + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root1 + * @param {TreeNode} root2 + * @return {TreeNode} + */ +var mergeTrees = function (root1, root2) { + const preOrder = (root1, root2) => { + if (!root1) + return root2 + if (!root2) + return root1; + root1.val += root2.val; + root1.left = preOrder(root1.left, root2.left); + root1.right = preOrder(root1.right, root2.right); + return root1; + } + return preOrder(root1, root2); +}; +``` ----------------------- diff --git a/problems/0654.最大二叉树.md b/problems/0654.最大二叉树.md index f0d3e594..4e1e7a72 100644 --- a/problems/0654.最大二叉树.md +++ b/problems/0654.最大二叉树.md @@ -311,6 +311,42 @@ func findMax(nums []int) (index int){ } ``` +JavaScript版本 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {number[]} nums + * @return {TreeNode} + */ +var constructMaximumBinaryTree = function (nums) { + const BuildTree = (arr, left, right) => { + if (left > right) + return null; + let maxValue = -1; + let maxIndex = -1; + for (let i = left; i <= right; ++i) { + if (arr[i] > maxValue) { + maxValue = arr[i]; + maxIndex = i; + } + } + let root = new TreeNode(maxValue); + root.left = BuildTree(arr, left, maxIndex - 1); + root.right = BuildTree(arr, maxIndex + 1, right); + return root; + } + let root = BuildTree(nums, 0, nums.length - 1); + return root; +}; +``` diff --git a/problems/0669.修剪二叉搜索树.md b/problems/0669.修剪二叉搜索树.md index 06d99b9d..26394eaa 100644 --- a/problems/0669.修剪二叉搜索树.md +++ b/problems/0669.修剪二叉搜索树.md @@ -286,8 +286,87 @@ class Solution: return root ``` Go: +```go + +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func trimBST(root *TreeNode, low int, high int) *TreeNode { + if root==nil{ + return nil + } + if root.Valhigh{//如果该节点的值大于最大值,则该节点更换为该节点的左节点值,继续遍历 + left:=trimBST(root.Left,low,high) + return left + } + root.Left=trimBST(root.Left,low,high) + root.Right=trimBST(root.Right,low,high) + return root +} +``` +JavaScript版本: +迭代: +```js +var trimBST = function(root, low, high) { + if(root === null) { + return null; + } + while(root !==null &&(root.valhigh)) { + if(root.valhigh) { + cur.right = cur.right.left; + } + cur = cur.right; + } + return root; +}; +``` + +递归: +```js +var trimBST = function (root,low,high) { + if(root === null) { + return null; + } + if(root.valhigh) { + let left = trimBST(root.left,low,high); + return left; + } + root.left = trimBST(root.left,low,high); + root.right = trimBST(root.right,low,high); + return root; + } +``` ----------------------- diff --git a/problems/0700.二叉搜索树中的搜索.md b/problems/0700.二叉搜索树中的搜索.md index 16b21f26..d6899ac5 100644 --- a/problems/0700.二叉搜索树中的搜索.md +++ b/problems/0700.二叉搜索树中的搜索.md @@ -290,7 +290,64 @@ func searchBST(root *TreeNode, val int) *TreeNode { } ``` +JavaScript版本 +> 递归 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} val + * @return {TreeNode} + */ +var searchBST = function (root, val) { + if (!root || root.val === val) { + return root; + } + if (root.val > val) + return searchBST(root.left, val); + if (root.val < val) + return searchBST(root.right, val); + return null; +}; +``` + +> 迭代 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} val + * @return {TreeNode} + */ +var searchBST = function (root, val) { + while (root !== null) { + if (root.val > val) + root = root.left; + else if (root.val < val) + root = root.right; + else + return root; + } + return root; +}; +``` diff --git a/problems/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index 6a8ba7fc..61027453 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -271,6 +271,9 @@ class Solution: Go: + +递归法 + ```Go func insertIntoBST(root *TreeNode, val int) *TreeNode { if root == nil { @@ -285,9 +288,144 @@ func insertIntoBST(root *TreeNode, val int) *TreeNode { return root } ``` +迭代法 +```go +func insertIntoBST(root *TreeNode, val int) *TreeNode { + if root == nil { + return &TreeNode{Val:val} + } + node := root + var pnode *TreeNode + for node != nil { + if val > node.Val { + pnode = node + node = node.Right + } else { + pnode = node + node = node.Left + } + } + if val > pnode.Val { + pnode.Right = &TreeNode{Val: val} + } else { + pnode.Left = &TreeNode{Val: val} + } + return root +} +``` +JavaScript版本 +> 有返回值的递归写法 +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} val + * @return {TreeNode} + */ +var insertIntoBST = function (root, val) { + const setInOrder = (root, val) => { + if (root === null) { + let node = new TreeNode(val); + return node; + } + if (root.val > val) + root.left = setInOrder(root.left, val); + else if (root.val < val) + root.right = setInOrder(root.right, val); + return root; + } + return setInOrder(root, val); +}; +``` + +> 无返回值的递归 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} val + * @return {TreeNode} + */ +var insertIntoBST = function (root, val) { + let parent = new TreeNode(0); + const preOrder = (cur, val) => { + if (cur === null) { + let node = new TreeNode(val); + if (parent.val > val) + parent.left = node; + else + parent.right = node; + return; + } + parent = cur; + if (cur.val > val) + preOrder(cur.left, val); + if (cur.val < val) + preOrder(cur.right, val); + } + if (root === null) + root = new TreeNode(val); + preOrder(root, val); + return root; +}; +``` + +> 迭代 + +```javascript +/** + * Definition for a binary tree node. + * function TreeNode(val, left, right) { + * this.val = (val===undefined ? 0 : val) + * this.left = (left===undefined ? null : left) + * this.right = (right===undefined ? null : right) + * } + */ +/** + * @param {TreeNode} root + * @param {number} val + * @return {TreeNode} + */ +var insertIntoBST = function (root, val) { + if (root === null) { + root = new TreeNode(val); + } else { + let parent = new TreeNode(0); + let cur = root; + while (cur) { + parent = cur; + if (cur.val > val) + cur = cur.left; + else + cur = cur.right; + } + let node = new TreeNode(val); + if (parent.val > val) + parent.left = node; + else + parent.right = node; + } + return root; +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0707.设计链表.md b/problems/0707.设计链表.md index 7d5fde1f..b67a36eb 100644 --- a/problems/0707.设计链表.md +++ b/problems/0707.设计链表.md @@ -340,7 +340,7 @@ class MyLinkedList { ``` Python: -```python3 +```python # 单链表 class Node: diff --git a/problems/0714.买卖股票的最佳时机含手续费.md b/problems/0714.买卖股票的最佳时机含手续费.md index abd20625..2e8f9208 100644 --- a/problems/0714.买卖股票的最佳时机含手续费.md +++ b/problems/0714.买卖股票的最佳时机含手续费.md @@ -217,7 +217,29 @@ class Solution: # 贪心思路 Go: +Javascript: +```Javascript +// 贪心思路 +var maxProfit = function(prices, fee) { + let result = 0 + let minPrice = prices[0] + for(let i = 1; i < prices.length; i++) { + if(prices[i] < minPrice) { + minPrice = prices[i] + } + if(prices[i] >= minPrice && prices[i] <= minPrice + fee) { + continue + } + if(prices[i] > minPrice + fee) { + result += prices[i] - minPrice - fee + // 买入和卖出只需要支付一次手续费 + minPrice = prices[i] -fee + } + } + return result +}; +``` ----------------------- diff --git a/problems/0738.单调递增的数字.md b/problems/0738.单调递增的数字.md index 5bddb234..75c7f250 100644 --- a/problems/0738.单调递增的数字.md +++ b/problems/0738.单调递增的数字.md @@ -147,23 +147,43 @@ class Solution { Python: -```python +```python3 class Solution: def monotoneIncreasingDigits(self, n: int) -> int: - strNum = list(str(n)) - flag = len(strNum) - for i in range(len(strNum) - 1, 0, -1): - if int(strNum[i]) < int(strNum[i - 1]): - strNum[i - 1] = str(int(strNum[i - 1]) - 1) - flag = i - for i in range(flag, len(strNum)): - strNum[i] = '9' - return int("".join(strNum)) + a = list(str(n)) + for i in range(len(a)-1,0,-1): + if int(a[i]) < int(a[i-1]): + a[i-1] = str(int(a[i-1]) - 1) + a[i:] = '9' * (len(a) - i) #python不需要设置flag值,直接按长度给9就好了 + return int("".join(a)) ``` Go: +Javascript: +```Javascript +var monotoneIncreasingDigits = function(n) { + n = n.toString() + n = n.split('').map(item => { + return +item + }) + let flag = Infinity + for(let i = n.length - 1; i > 0; i--) { + if(n [i - 1] > n[i]) { + flag = i + n[i - 1] = n[i - 1] - 1 + n[i] = 9 + } + } + for(let i = flag; i < n.length; i++) { + n[i] = 9 + } + + n = n.join('') + return +n +}; +``` ----------------------- diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index eeea6ead..e72fd6a4 100644 --- a/problems/0739.每日温度.md +++ b/problems/0739.每日温度.md @@ -217,47 +217,46 @@ Go: > 暴力法 ```go -func dailyTemperatures(temperatures []int) []int { - length:=len(temperatures) - res:=make([]int,length) - for i:=0;i t[i] { + res = append(res, j-i) + break + } } - for j=temperatures[j]{//大于等于 - j++ - } - if j 单调栈法 ```go -func dailyTemperatures(temperatures []int) []int { - length:=len(temperatures) - res:=make([]int,length) - stack:=[]int{} - for i:=0;i0&&temperatures[i]>temperatures[stack[len(stack)-1]]{ - res[stack[len(stack)-1]]=i-stack[len(stack)-1]//存放结果集 - stack=stack[:len(stack)-1]//删除stack[len(stack)-1]的元素 - } - //如果栈顶元素大于等于新来的元素,则加入到栈中。当栈内元素个数为0时,直接入栈 - if len(stack)==0||temperatures[i]<=temperatures[stack[len(stack)-1]]{ - stack = append(stack, i) +// 单调递减栈 +func dailyTemperatures(num []int) []int { + ans := make([]int, len(num)) + stack := []int{} + for i, v := range num { + // 栈不空,且当前遍历元素 v 破坏了栈的单调性 + for len(stack) != 0 && v > num[stack[len(stack)-1]] { + // pop + top := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + ans[top] = i - top } + stack = append(stack, i) } - return res + return ans } ``` diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index bcdd71dc..b36e00b7 100644 --- a/problems/0763.划分字母区间.md +++ b/problems/0763.划分字母区间.md @@ -128,7 +128,53 @@ class Solution: Go: +```go +func partitionLabels(s string) []int { + var res []int; + var marks [26]int; + size, left, right := len(s), 0, 0; + for i := 0; i < size; i++ { + marks[s[i] - 'a'] = i; + } + for i := 0; i < size; i++ { + right = max(right, marks[s[i] - 'a']); + if i == right { + res = append(res, right - left + 1); + left = i + 1; + } + } + return res; +} + +func max(a, b int) int { + if a < b { + a = b; + } + return a; +} +``` + +Javascript: +```Javascript +var partitionLabels = function(s) { + let hash = {} + for(let i = 0; i < s.length; i++) { + hash[s[i]] = i + } + let result = [] + let left = 0 + let right = 0 + for(let i = 0; i < s.length; i++) { + right = Math.max(right, hash[s[i]]) + if(right === i) { + result.push(right - left + 1) + left = i + 1 + } + } + return result +}; +``` ----------------------- diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index 8f1a3fdb..a0eb5883 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -369,7 +369,42 @@ class Solution: ``` Go: +Javascript: +```Javascript +var minCameraCover = function(root) { + let result = 0 + function traversal(cur) { + if(cur === null) { + return 2 + } + let left = traversal(cur.left) + let right = traversal(cur.right) + + if(left === 2 && right === 2) { + return 0 + } + + if(left === 0 || right === 0) { + result++ + return 1 + } + + if(left === 1 || right === 1) { + return 2 + } + + return -1 + } + + if(traversal(root) === 0) { + result++ + } + + return result + +}; +``` ----------------------- diff --git a/problems/1005.K次取反后最大化的数组和.md b/problems/1005.K次取反后最大化的数组和.md index 387de147..c3e99f7e 100644 --- a/problems/1005.K次取反后最大化的数组和.md +++ b/problems/1005.K次取反后最大化的数组和.md @@ -138,6 +138,30 @@ class Solution: ``` Go: +```Go +func largestSumAfterKNegations(nums []int, K int) int { + sort.Slice(nums, func(i, j int) bool { + return math.Abs(float64(nums[i])) > math.Abs(float64(nums[j])) + }) + + for i := 0; i < len(nums); i++ { + if K > 0 && nums[i] < 0 { + nums[i] = -nums[i] + K-- + } + } + + if K%2 == 1 { + nums[len(nums)-1] = -nums[len(nums)-1] + } + + result := 0 + for i := 0; i < len(nums); i++ { + result += nums[i] + } + return result +} +``` Javascript: diff --git a/problems/1035.不相交的线.md b/problems/1035.不相交的线.md index be159543..c7481306 100644 --- a/problems/1035.不相交的线.md +++ b/problems/1035.不相交的线.md @@ -26,7 +26,8 @@ 拿示例一A = [1,4,2], B = [1,2,4]为例,相交情况如图: -![1035.不相交的线](https://img-blog.csdnimg.cn/20210321164517460.png) +![](https://gitee.com/programmercarl/pics/raw/master/pic1/20210527113415.png) + 其实也就是说A和B的最长公共子序列是[1,4],长度为2。 这个公共子序列指的是相对顺序不变(即数字4在字符串A中数字1的后面,那么数字4也应该在字符串B数字1的后面) diff --git a/problems/1047.删除字符串中的所有相邻重复项.md b/problems/1047.删除字符串中的所有相邻重复项.md index 305a287d..c6a49376 100644 --- a/problems/1047.删除字符串中的所有相邻重复项.md +++ b/problems/1047.删除字符串中的所有相邻重复项.md @@ -23,15 +23,14 @@ https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/ 示例: -输入:"abbaca" -输出:"ca" -解释: -例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。 +* 输入:"abbaca" +* 输出:"ca" +* 解释:例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。   提示: -1 <= S.length <= 20000 -S 仅由小写英文字母组成。 +* 1 <= S.length <= 20000 +* S 仅由小写英文字母组成。 # 思路 @@ -64,7 +63,7 @@ S 仅由小写英文字母组成。 如动画所示: -![1047.删除字符串中的所有相邻重复项](https://code-thinking.cdn.bcebos.com/gifs/1047.%E5%88%A0%E9%99%A4%E5%AD%97%E7%AC%A6%E4%B8%B2%E4%B8%AD%E7%9A%84%E6%89%80%E6%9C%89%E7%9B%B8%E9%82%BB%E9%87%8D%E5%A4%8D%E9%A1%B9.gif) +![1047.删除字符串中的所有相邻重复项](https://code-thinking.cdn.bcebos.com/gifs/1047.删除字符串中的所有相邻重复项.gif) 从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒叙的,所以在对字符串进行反转一下,就得到了最终的结果。 @@ -127,7 +126,9 @@ Java: ```Java class Solution { public String removeDuplicates(String S) { - Deque deque = new LinkedList<>(); + //ArrayDeque会比LinkedList在除了删除元素这一点外会快一点 + //参考:https://stackoverflow.com/questions/6163166/why-is-arraydeque-better-than-linkedlist + ArrayDeque deque = new ArrayDeque<>(); char ch; for (int i = 0; i < S.length(); i++) { ch = S.charAt(i); @@ -171,6 +172,29 @@ class Solution { } ``` +拓展:双指针 +```java +class Solution { + public String removeDuplicates(String s) { + char[] ch = s.toCharArray(); + int fast = 0; + int slow = 0; + while(fast < s.length()){ + // 直接用fast指针覆盖slow指针的值 + ch[slow] = ch[fast]; + // 遇到前后相同值的,就跳过,即slow指针后退一步,下次循环就可以直接被覆盖掉了 + if(slow > 0 && ch[slow] == ch[slow - 1]){ + slow--; + }else{ + slow++; + } + fast++; + } + return new String(ch,0,slow); + } +} +``` + Python: ```python3 class Solution: diff --git a/problems/二叉树理论基础.md b/problems/二叉树理论基础.md index 55383e91..d4dfe0c6 100644 --- a/problems/二叉树理论基础.md +++ b/problems/二叉树理论基础.md @@ -188,6 +188,21 @@ struct TreeNode { Java: +```java +public class TreeNode { + int val; + TreeNode left; + TreeNode right; + TreeNode() {} + TreeNode(int val) { this.val = val; } + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } +} +``` + Python: diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index 8706dc47..30b921ff 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -155,9 +155,82 @@ public: ## 其他语言版本 - Java: +```java +// 前序遍历顺序:中-左-右,入栈顺序:中-右-左 +class Solution { + public List preorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + if (root == null){ + return result; + } + Stack stack = new Stack<>(); + stack.push(root); + while (!stack.isEmpty()){ + TreeNode node = stack.pop(); + result.add(node.val); + if (node.right != null){ + stack.push(node.right); + } + if (node.left != null){ + stack.push(node.left); + } + } + return result; + } +} + +// 中序遍历顺序: 左-中-右 入栈顺序: 左-右 +class Solution { + public List inorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + if (root == null){ + return result; + } + Stack stack = new Stack<>(); + TreeNode cur = root; + while (cur != null || !stack.isEmpty()){ + if (cur != null){ + stack.push(cur); + cur = cur.left; + }else{ + cur = stack.pop(); + result.add(cur.val); + cur = cur.right; + } + } + return result; + } +} + +// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果 +class Solution { + public List postorderTraversal(TreeNode root) { + List result = new ArrayList<>(); + if (root == null){ + return result; + } + Stack stack = new Stack<>(); + stack.push(root); + while (!stack.isEmpty()){ + TreeNode node = stack.pop(); + result.add(node.val); + if (node.left != null){ + stack.push(node.left); + } + if (node.right != null){ + stack.push(node.right); + } + } + Collections.reverse(result); + return result; + } +} +``` + + + Python: ```python3 diff --git a/problems/二叉树的递归遍历.md b/problems/二叉树的递归遍历.md index f2072e30..68f17257 100644 --- a/problems/二叉树的递归遍历.md +++ b/problems/二叉树的递归遍历.md @@ -221,12 +221,12 @@ class Solution: Go: 前序遍历: -``` +```go func PreorderTraversal(root *TreeNode) (res []int) { var traversal func(node *TreeNode) traversal = func(node *TreeNode) { if node == nil { - return + return } res = append(res,node.Val) traversal(node.Left) @@ -239,12 +239,12 @@ func PreorderTraversal(root *TreeNode) (res []int) { ``` 中序遍历: -``` +```go func InorderTraversal(root *TreeNode) (res []int) { var traversal func(node *TreeNode) traversal = func(node *TreeNode) { if node == nil { - return + return } traversal(node.Left) res = append(res,node.Val) @@ -256,12 +256,12 @@ func InorderTraversal(root *TreeNode) (res []int) { ``` 后序遍历: -``` +```go func PostorderTraversal(root *TreeNode) (res []int) { var traversal func(node *TreeNode) traversal = func(node *TreeNode) { if node == nil { - return + return } traversal(node.Left) traversal(node.Right) diff --git a/problems/剑指Offer05.替换空格.md b/problems/剑指Offer05.替换空格.md index a4c0149f..f68d8e22 100644 --- a/problems/剑指Offer05.替换空格.md +++ b/problems/剑指Offer05.替换空格.md @@ -13,9 +13,9 @@ https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/ 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 -示例 1: -输入:s = "We are happy." -输出:"We%20are%20happy." +示例 1: +输入:s = "We are happy." +输出:"We%20are%20happy." # 思路 @@ -42,9 +42,9 @@ i指向新长度的末尾,j指向旧长度的末尾。 时间复杂度,空间复杂度均超过100%的用户。 - + -## C++代码 +C++代码如下: ```C++ class Solution { @@ -76,17 +76,17 @@ public: }; ``` -时间复杂度:O(n) -空间复杂度:O(1) +* 时间复杂度:O(n) +* 空间复杂度:O(1) 此时算上本题,我们已经做了七道双指针相关的题目了分别是: -* [27.移除元素](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) -* [15.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) -* [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) -* [206.翻转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) -* [142.环形链表II](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) -* [344.反转字符串](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) +* [27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww) +* [15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) +* [18.四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q) +* [206.翻转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A) +* [142.环形链表II](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) +* [344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w) # 拓展 @@ -121,10 +121,6 @@ for (int i = 0; i < a.size(); i++) { 所以想处理字符串,我们还是会定义一个string类型。 - - - - ## 其他语言版本 @@ -150,8 +146,6 @@ public static String replaceSpace(StringBuffer str) { } ``` -Python: - Go: ```go diff --git a/problems/剑指Offer58-II.左旋转字符串.md b/problems/剑指Offer58-II.左旋转字符串.md index 39c8382c..1701086e 100644 --- a/problems/剑指Offer58-II.左旋转字符串.md +++ b/problems/剑指Offer58-II.左旋转字符串.md @@ -16,16 +16,16 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 -示例 1: -输入: s = "abcdefg", k = 2 -输出: "cdefgab" +示例 1: +输入: s = "abcdefg", k = 2 +输出: "cdefgab" -示例 2: -输入: s = "lrloseumgh", k = 6 -输出: "umghlrlose" +示例 2: +输入: s = "lrloseumgh", k = 6 +输出: "umghlrlose" -限制: -1 <= k < s.length <= 10000 +限制: +1 <= k < s.length <= 10000 # 思路 @@ -34,7 +34,7 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。 -那么我们可以想一下上一题目[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。 +那么我们可以想一下上一题目[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。 这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。 @@ -50,13 +50,13 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 如图: - + 最终得到左旋2个单元的字符串:cdefgab 思路明确之后,那么代码实现就很简单了 -# C++代码 +C++代码如下: ```C++ class Solution { @@ -73,15 +73,16 @@ public: # 总结 + 此时我们已经反转好多次字符串了,来一起回顾一下吧。 -在这篇文章[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA),第一次讲到反转一个字符串应该怎么做,使用了双指针法。 +在这篇文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w),第一次讲到反转一个字符串应该怎么做,使用了双指针法。 -然后发现[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。 +然后发现[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。 -后来在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。 +后来在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。 -最后再讲到本地,本题则是先局部反转再 整体反转,与[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)类似,但是也是一种新的思路。 +最后再讲到本题,本题则是先局部反转再 整体反转,与[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)类似,但是也是一种新的思路。 好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。 @@ -93,7 +94,6 @@ public: **如果想让这套题目有意义,就不要申请额外空间。** - ## 其他语言版本 Java: @@ -117,7 +117,27 @@ class Solution { } } ``` -Python: + +```python +# 方法一:可以使用切片方法 +class Solution: + def reverseLeftWords(self, s: str, n: int) -> str: + return s[n:] + s[0:n] + +# 方法二:也可以使用上文描述的方法,有些面试中不允许使用切片,那就使用上文作者提到的方法 +# class Solution: +# def reverseLeftWords(self, s: str, n: int) -> str: +# s = list(s) +# s[0:n] = list(reversed(s[0:n])) +# s[n:] = list(reversed(s[n:])) +# s.reverse() + +# return "".join(s) + + +# 时间复杂度:O(n) +# 空间复杂度:O(n),python的string为不可变,需要开辟同样大小的list空间来修改 +``` Go: @@ -151,4 +171,4 @@ func reverse(b []byte, left, right int){ * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
\ No newline at end of file +
diff --git a/problems/动态规划-股票问题总结篇.md b/problems/动态规划-股票问题总结篇.md index cac77b65..590a8008 100644 --- a/problems/动态规划-股票问题总结篇.md +++ b/problems/动态规划-股票问题总结篇.md @@ -376,7 +376,7 @@ p[i][3] = dp[i - 1][2]; 综上分析,递推代码如下: ```C++ -dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3], dp[i - 1][1]) - prices[i]; +dp[i][0] = max(dp[i - 1][0], max(dp[i - 1][3]- prices[i], dp[i - 1][1]) - prices[i]; dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]); dp[i][2] = dp[i - 1][0] + prices[i]; dp[i][3] = dp[i - 1][2]; diff --git a/problems/双指针总结.md b/problems/双指针总结.md index 13f2f174..b03d3ff2 100644 --- a/problems/双指针总结.md +++ b/problems/双指针总结.md @@ -12,7 +12,7 @@ # 数组篇 -在[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。 +在[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中,原地移除数组上的元素,我们说到了数组上的元素,不能真正的删除,只能覆盖。 一些同学可能会写出如下代码(伪代码): @@ -30,11 +30,11 @@ for (int i = 0; i < array.size(); i++) { # 字符串篇 -在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。 +在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中讲解了反转字符串,注意这里强调要原地反转,要不然就失去了题目的意义。 使用双指针法,**定义两个指针(也可以说是索引下表),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。**,时间复杂度是O(n)。 -在[替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了! +在[替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA) 中介绍使用双指针填充字符串的方法,如果想把这道题目做到极致,就不要只用额外的辅助空间了! 思路就是**首先扩充数组到每个空格替换成"%20"之后的大小。然后双指针从后向前替换空格。** @@ -44,7 +44,7 @@ for (int i = 0; i < array.size(); i++) { **其实很多数组(字符串)填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** -那么在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中,我们使用双指针法,用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。 +那么在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中,我们使用双指针法,用O(n)的时间复杂度完成字符串删除类的操作,因为题目要产出冗余空格。 **在删除冗余空格的过程中,如果不注意代码效率,很容易写成了O(n^2)的时间复杂度。其实使用双指针法O(n)就可以搞定。** @@ -54,19 +54,19 @@ for (int i = 0; i < array.size(); i++) { 翻转链表是现场面试,白纸写代码的好题,考察了候选者对链表以及指针的熟悉程度,而且代码也不长,适合在白纸上写。 -在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)中,讲如何使用双指针法来翻转链表,**只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。** +在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,讲如何使用双指针法来翻转链表,**只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表。** 思路还是很简单的,代码也不长,但是想在白纸上一次性写出bugfree的代码,并不是容易的事情。 -在链表中求环,应该是双指针在链表里最经典的应用,在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。 +在链表中求环,应该是双指针在链表里最经典的应用,在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)中讲解了如何通过双指针判断是否有环,而且还要找到环的入口。 **使用快慢指针(双指针法),分别定义 fast 和 slow指针,从头结点出发,fast指针每次移动两个节点,slow指针每次移动一个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环。** -那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)。 +那么找到环的入口,其实需要点简单的数学推理,我在文章中把找环的入口清清楚楚的推理的一遍,如果对找环入口不够清楚的同学建议自己看一看[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)。 # N数之和篇 -在[哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)中,讲到使用哈希法可以解决1.两数之和的问题 +在[哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)中,讲到使用哈希法可以解决1.两数之和的问题 其实使用双指针也可以解决1.两数之和的问题,只不过1.两数之和求的是两个元素的下标,没法用双指针,如果改成求具体两个元素的数值就可以了,大家可以尝试用双指针做一个leetcode上两数之和的题目,就可以体会到我说的意思了。 @@ -82,7 +82,7 @@ for (int i = 0; i < array.size(); i++) { 只用双指针法时间复杂度为O(n^2),但比哈希法的O(n^2)效率高得多,哈希法在使用两层for循环的时候,能做的剪枝操作很有限。 -在[双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环,依然是使用双指针法。** +在[双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q)中,讲到了四数之和,其实思路是一样的,**在三数之和的基础上再套一层for循环,依然是使用双指针法。** 对于三数之和使用双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 @@ -94,18 +94,6 @@ for (int i = 0; i < array.size(); i++) { 本文中一共介绍了leetcode上九道使用双指针解决问题的经典题目,除了链表一些题目一定要使用双指针,其他题目都是使用双指针来提高效率,一般是将O(n^2)的时间复杂度,降为O(n)。 建议大家可以把文中涉及到的题目在好好做一做,琢磨琢磨,基本对双指针法就不在话下了。 -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - ----------------------- diff --git a/problems/周总结/20201003二叉树周末总结.md b/problems/周总结/20201003二叉树周末总结.md index b7c123bc..0cb8b654 100644 --- a/problems/周总结/20201003二叉树周末总结.md +++ b/problems/周总结/20201003二叉树周末总结.md @@ -40,9 +40,8 @@ public: return isSame; } - bool isSymmetric(TreeNode* root) { - if (root == NULL) return true; - return compare(root->left, root->right); + bool isSymmetric(TreeNode* p, TreeNode* q) { + return compare(p, q); } }; ``` diff --git a/problems/字符串总结.md b/problems/字符串总结.md index 21fdc9bc..71be6422 100644 --- a/problems/字符串总结.md +++ b/problems/字符串总结.md @@ -43,9 +43,10 @@ for (int i = 0; i < a.size(); i++) { 所以想处理字符串,我们还是会定义一个string类型。 + # 要不要使用库函数 -在文章[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)中强调了**打基础的时候,不要太迷恋于库函数。** +在文章[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w)中强调了**打基础的时候,不要太迷恋于库函数。** 甚至一些同学习惯于调用substr,split,reverse之类的库函数,却不知道其实现原理,也不知道其时间复杂度,这样实现出来的代码,如果在面试现场,面试官问:“分析其时间复杂度”的话,一定会一脸懵逼! @@ -56,15 +57,15 @@ for (int i = 0; i < a.size(); i++) { # 双指针法 -在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。** +在[344.反转字符串](https://mp.weixin.qq.com/s/_rNm66OJVl92gBDIbGpA3w) ,我们使用双指针法实现了反转字符串的操作,**双指针法在数组,链表和字符串中很常用。** -接着在[字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。 +接着在[字符串:替换空格](https://mp.weixin.qq.com/s/69HNjR4apcRSAo_KyknPjA),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。 **其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。** -那么针对数组删除操作的问题,其实在[数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA)中就已经提到了使用双指针法进行移除操作。 +那么针对数组删除操作的问题,其实在[27. 移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww)中就已经提到了使用双指针法进行移除操作。 -同样的道理在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中我们使用O(n)的时间复杂度,完成了删除冗余空格。 +同样的道理在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中我们使用O(n)的时间复杂度,完成了删除冗余空格。 一些同学会使用for循环里调用库函数erase来移除元素,这其实是O(n^2)的操作,因为erase就是O(n)的操作,所以这也是典型的不知道库函数的时间复杂度,上来就用的案例了。 @@ -72,7 +73,7 @@ for (int i = 0; i < a.size(); i++) { 在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。 -[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。 +[541. 反转字符串II](https://mp.weixin.qq.com/s/pzXt6PQ029y7bJ9YZB2mVQ)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。 其实**当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章**。 @@ -80,34 +81,34 @@ for (int i = 0; i < a.size(); i++) { 因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。 -在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。 +在[151.翻转字符串里的单词](https://mp.weixin.qq.com/s/4j6vPFHkFAXnQhmSkq2X9g)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。 这道题目通过 **先整体反转再局部反转**,实现了反转字符串里的单词。 后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。 -在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ)中,我们通过**先局部反转再整体反转**达到了左旋的效果。 +在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/Px_L-RfT2b_jXKcNmccPsw)中,我们通过**先局部反转再整体反转**达到了左旋的效果。 # KMP KMP的主要思想是**当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。** -KMP的精髓所在就是前缀表,在[字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。 +KMP的精髓所在就是前缀表,在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表。 前缀表:起始位置到下表i之前(包括i)的子串中,有多大长度的相同前缀后缀。 那么使用KMP可以解决两类经典问题: -1. 匹配问题:[28. 实现 strStr()](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg) -2. 重复子串问题:[459.重复的子字符串](https://mp.weixin.qq.com/s/lR2JPtsQSR2I_9yHbBmBuQ) +1. 匹配问题:[28. 实现 strStr()](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg) +2. 重复子串问题:[459.重复的子字符串](https://mp.weixin.qq.com/s/32Pve4j8IWvdgxYEZdTeFg) -在[字符串:听说你对KMP有这些疑问?](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ) 强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。 +再一次强调了什么是前缀,什么是后缀,什么又是最长相等前后缀。 前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。 后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。 -然后**针对前缀表到底要不要减一,这其实是不同KMP实现的方式**,我们在[字符串:前缀表不右移,难道就写不出KMP了?](https://mp.weixin.qq.com/s/p3hXynQM2RRROK5c6X7xfw)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。 +然后**针对前缀表到底要不要减一,这其实是不同KMP实现的方式**,我们在[KMP精讲](https://mp.weixin.qq.com/s/MoRBHbS4hQXn7LcPdmHmIg)中针对之前两个问题,分别给出了两个不同版本的的KMP实现。 其中主要**理解j=next[x]这一步最为关键!** @@ -123,18 +124,6 @@ KMP算法是字符串查找最重要的算法,但彻底理解KMP并不容易 -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - ----------------------- diff --git a/problems/栈与队列理论基础.md b/problems/栈与队列理论基础.md index 04f99981..db871a3c 100644 --- a/problems/栈与队列理论基础.md +++ b/problems/栈与队列理论基础.md @@ -85,20 +85,7 @@ std::queue> third; // 定义以list为底层容器的队列 所以STL 队列也不被归类为容器,而被归类为container adapter( 容器适配器)。 -我这里讲的都是(clck)C++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖起内部原理,才能夯实基础。 - - - -## 其他语言版本 - - -Java: - - -Python: - - -Go: +我这里讲的都是C++ 语言中情况, 使用其他语言的同学也要思考栈与队列的底层实现问题, 不要对数据结构的使用浅尝辄止,而要深挖起内部原理,才能夯实基础。 diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index 1269d9c1..2a99c97f 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -82,7 +82,7 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 那么可以有两个方向推出来dp[i][j], -* 由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j] +* 由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。) * 由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值 所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); @@ -99,10 +99,17 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 状态转移方程 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; +} // 正序遍历 for (int j = weight[0]; j <= bagWeight; j++) { dp[0][j] = value[0]; @@ -116,14 +123,11 @@ for (int j = weight[0]; j <= bagWeight; j++) { dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢? +其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是又左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。 -dp[i][j]在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,因为0就是最小的了,不会影响取最大价值的结果。 +初始-1,初始-2,初始100,都可以! -如果题目给的价值有负数,那么非0下标就要初始化为负无穷了。例如:一个物品的价值是-2,但对应的位置依然初始化为0,那么取最大值的时候,就会取0而不是-2了,所以要初始化为负无穷。 - -而背包问题的物品价值都是正整数,所以初始化为0,就可以了。 - -**这样才能让dp数组在递归公式的过程中取最大的价值,而不是被初始值覆盖了**。 +但只不过一开始就统一把dp数组统一初始为0,更方便一些。 如图: @@ -159,7 +163,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]; // 这个是为了展现dp数组里元素的变化 + 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]); } @@ -380,6 +384,52 @@ func main() { } ``` +javaScript: + +```js +function testWeightBagProblem (wight, value, size) { + const len = wight.length, + dp = Array.from({length: len + 1}).map( + () => Array(size + 1).fill(0) + ); + + for(let i = 1; i <= len; i++) { + for(let j = 0; j <= size; j++) { + if(wight[i - 1] <= j) { + dp[i][j] = Math.max( + dp[i - 1][j], + value[i - 1] + dp[i - 1][j - wight[i - 1]] + ) + } else { + dp[i][j] = dp[i - 1][j]; + } + } + } + +// console.table(dp); + + return dp[len][size]; +} + +function testWeightBagProblem2 (wight, value, size) { + const len = wight.length, + dp = Array(size + 1).fill(0); + for(let i = 1; i <= len; i++) { + for(let j = size; j >= wight[i - 1]; j--) { + dp[j] = Math.max(dp[j], value[i - 1] + dp[j - wight[i - 1]]); + } + } + return dp[size]; +} + + +function test () { + console.log(testWeightBagProblem([1, 3, 4, 5], [15, 20, 30, 55], 6)); +} + +test(); +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index e85d31b4..36856cd6 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -5,6 +5,7 @@

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

+ # 动态规划:关于01背包问题,你该了解这些!(滚动数组) 昨天[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中是用二维dp数组来讲解01背包。 @@ -35,7 +36,7 @@ **其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);** -**于其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了**,只用dp[j](一维数组,也可以理解是一个滚动数组)。 +**与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了**,只用dp[j](一维数组,也可以理解是一个滚动数组)。 这就是滚动数组的由来,需要满足的条件是上一层可以重复利用,直接拷贝到当前层。 @@ -214,7 +215,7 @@ int main() { Java: ```java - public static void main(String[] args) { + public static void main(String[] args) { int[] weight = {1, 3, 4}; int[] value = {15, 20, 30}; int bagWight = 4; @@ -242,7 +243,24 @@ Java: Python: +```python +def test_1_wei_bag_problem(): + weight = [1, 3, 4] + value = [15, 20, 30] + bag_weight = 4 + # 初始化: 全为0 + dp = [0] * (bag_weight + 1) + # 先遍历物品, 再遍历背包容量 + for i in range(len(weight)): + for j in range(bag_weight, weight[i] - 1, -1): + # 递归公式 + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + + print(dp) + +test_1_wei_bag_problem() +``` Go: ```go @@ -276,6 +294,29 @@ func main() { } ``` +javaScript: + +```js + +function testWeightBagProblem(wight, value, size) { + const len = wight.length, + dp = Array(size + 1).fill(0); + for(let i = 1; i <= len; i++) { + for(let j = size; j >= wight[i - 1]; j--) { + dp[j] = Math.max(dp[j], value[i - 1] + dp[j - wight[i - 1]]); + } + } + return dp[size]; +} + + +function test () { + console.log(testWeightBagProblem([1, 3, 4, 5], [15, 20, 30, 55], 6)); +} + +test(); +``` + -----------------------