diff --git a/README.md b/README.md index 7eba656d..daef7c02 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ > 1. **介绍**:本项目是一套完整的刷题计划,旨在帮助大家少走弯路,循序渐进学算法,[关注作者](#关于作者) > 2. **PDF版本** : [「代码随想录」算法精讲 PDF 版本](https://mp.weixin.qq.com/s/RsdcQ9umo09R6cfnwXZlrQ) 。 > 3. **学习社区** : 一起学习打卡/面试技巧/如何选择offer/大厂内推/职场规则/简历修改/技术分享/程序人生。欢迎加入[「代码随想录」学习社区](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) 。 -> 4. **转载须知** :以下所有文章皆为我([程序员Carl](https://github.com/youngyangyang04))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境! +> 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))的原创。引用本项目文章请注明出处,发现恶意抄袭或搬运,会动用法律武器维护自己的权益。让我们一起维护一个良好的技术创作环境!

@@ -11,11 +12,16 @@

- + - +

+

+ + + + # LeetCode 刷题攻略 @@ -111,14 +117,24 @@ (持续更新中.....) +## 备战秋招 + +1. [选择方向的时候,我也迷茫了](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g) +2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg) +3. [关于实习,大家可能有点迷茫!](https://mp.weixin.qq.com/s/xcxzi7c78kQGjvZ8hh7taA) +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) + ## 数组 1. [数组过于简单,但你该了解这些!](./problems/数组理论基础.md) 2. [数组:每次遇到二分法,都是一看就会,一写就废](./problems/0704.二分查找.md) 3. [数组:就移除个元素很难么?](./problems/0027.移除元素.md) -4. [数组:滑动窗口拯救了你](./problems/0209.长度最小的子数组.md) -5. [数组:这个循环可以转懵很多人!](./problems/0059.螺旋矩阵II.md) -6. [数组:总结篇](./problems/数组总结篇.md) +4. [数组:有序数组的平方,还有序么?](./problems/0977.有序数组的平方.md) +5. [数组:滑动窗口拯救了你](./problems/0209.长度最小的子数组.md) +6. [数组:这个循环可以转懵很多人!](./problems/0059.螺旋矩阵II.md) +7. [数组:总结篇](./problems/数组总结篇.md) ## 链表 @@ -169,10 +185,9 @@ 6. [链表:删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md) 7. [链表:链表相交](./problems/面试题02.07.链表相交.md) 8. [链表:环找到了,那入口呢?](./problems/0142.环形链表II.md) -9. [链表:删除链表的倒数第 N 个结点](./problems/0019.删除链表的倒数第N个节点.md) -10. [哈希表:解决了两数之和,那么能解决三数之和么?](./problems/0015.三数之和.md) -11. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md) -12. [双指针法:总结篇!](./problems/双指针总结.md) +9. [哈希表:解决了两数之和,那么能解决三数之和么?](./problems/0015.三数之和.md) +10. [双指针法:一样的道理,能解决四数之和](./problems/0018.四数之和.md) +11. [双指针法:总结篇!](./problems/双指针总结.md) ## 栈与队列 @@ -292,6 +307,7 @@ 动态规划专题已经开始啦,来不及解释了,小伙伴们上车别掉队! + 1. [关于动态规划,你该了解这些!](./problems/动态规划理论基础.md) 2. [动态规划:斐波那契数](./problems/0509.斐波那契数.md) 3. [动态规划:爬楼梯](./problems/0070.爬楼梯.md) @@ -305,7 +321,8 @@ 背包问题系列: -背包问题大纲 +背包问题大纲 + 11. [动态规划:关于01背包问题,你该了解这些!](./problems/背包理论基础01背包-1.md) 12. [动态规划:关于01背包问题,你该了解这些!(滚动数组)](./problems/背包理论基础01背包-2.md) @@ -334,7 +351,8 @@ 股票系列: -股票问题总结 +股票问题总结 + 32. [动态规划:买卖股票的最佳时机](./problems/0121.买卖股票的最佳时机.md) 33. [动态规划:本周我们都讲了这些(系列六)](./problems/周总结/20210225动规周末总结.md) @@ -348,6 +366,9 @@ 子序列系列: + + + 40. [动态规划:最长递增子序列](./problems/0300.最长上升子序列.md) 41. [动态规划:最长连续递增序列](./problems/0674.最长连续递增序列.md) 42. [动态规划:最长重复子数组](./problems/0718.最长重复子数组.md) @@ -361,10 +382,14 @@ 52. [为了绝杀编辑距离,Carl做了三步铺垫,你都知道么?](./problems/为了绝杀编辑距离,卡尔做了三步铺垫.md) 53. [动态规划:回文子串](./problems/0647.回文子串.md) 54. [动态规划:最长回文子序列](./problems/0516.最长回文子序列.md) - +55. [动态规划总结篇](./problems/动态规划总结篇.md) (持续更新中....) +## 单调栈 + +1. [每日温度](./problems/0739.每日温度.md) + ## 图论 ## 十大排序 @@ -385,12 +410,7 @@ [各类基础算法模板](https://github.com/youngyangyang04/leetcode/blob/master/problems/算法模板.md) -# 备战秋招 -1. [技术比较弱,也对技术不感兴趣,如何选择方向?](https://mp.weixin.qq.com/s/ZCzFiAHZHLqHPLJQXNm75g) -2. [刷题就用库函数了,怎么了?](https://mp.weixin.qq.com/s/6K3_OSaudnHGq2Ey8vqYfg) -3. [关于实习,大家可能有点迷茫!](https://mp.weixin.qq.com/s/xcxzi7c78kQGjvZ8hh7taA) -4. [马上秋招了,慌得很!](https://mp.weixin.qq.com/s/7q7W8Cb2-a5U5atZdOnOFA) # B站算法视频讲解 @@ -409,6 +429,10 @@ (持续更新中....) +# 贡献者 + +你可以[点此链接](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)查看LeetCode-Master的所有贡献者。感谢你们补充了LeetCode-Master的其他语言版本,让更多的读者收益于此项目。 + # 关于作者 大家好,我是程序员Carl,哈工大师兄,ACM 校赛、黑龙江省赛、东北四省赛金牌、亚洲区域赛铜牌获得者,先后在腾讯和百度从事后端技术研发,CSDN博客专家。对算法和C++后端技术有一定的见解,利用工作之余重新刷leetcode。 @@ -420,7 +444,7 @@ -# 我的公众号 +# 公众号 更多精彩文章持续更新,微信搜索:「代码随想录」第一时间围观,关注后回复:「666」可以获得所有算法专题原创PDF。 diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md index 21f798a9..02e9996f 100644 --- a/problems/0001.两数之和.md +++ b/problems/0001.两数之和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 1. 两数之和 @@ -29,10 +29,10 @@ https://leetcode-cn.com/problems/two-sum/ 很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。 建议大家做这道题目之前,先做一下这两道 -* [242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig) -* [349. 两个数组的交集](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA) +* [242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) +* [349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q) -[242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA)这道题目是通过set作为哈希表来解决哈希问题。 +[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)这道题目是通过set作为哈希表来解决哈希问题。 本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限。 @@ -51,13 +51,14 @@ C++中map,有三种类型: std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。 -同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)。 +同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)。 **这道题目中并不需要key有序,选择std::unordered_map 效率更高!** 解题思路动画如下: - +![](https://code-thinking.cdn.bcebos.com/gifs/1.两数之和.gif) + C++代码: @@ -134,6 +135,21 @@ func twoSum(nums []int, target int) []int { } ``` +```go +// 使用map方式解题,降低时间复杂度 +func twoSum(nums []int, target int) []int { + m := make(map[int]int) + for index, val := range nums { + if preIndex, ok := m[target-val]; ok { + return []int{preIndex, index} + } else { + m[val] = index + } + } + return []int{} +} +``` + Rust ```rust @@ -156,6 +172,21 @@ impl Solution { } ``` +Javascript + +```javascript +var twoSum = function (nums, target) { + let hash = {}; + for (let i = 0; i < nums.length; i++) { + if (hash[target - nums[i]] !== undefined) { + return [i, hash[target - nums[i]]]; + } + hash[nums[i]] = i; + } + return []; +}; +``` + @@ -163,4 +194,4 @@ impl Solution { * 作者微信:[程序员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/0015.三数之和.md b/problems/0015.三数之和.md index 96dc1ac3..5b77a170 100644 --- a/problems/0015.三数之和.md +++ b/problems/0015.三数之和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -105,8 +105,7 @@ public: 时间复杂度:O(n^2)。 - -## 双指针法C++代码 +C++代码代码如下: ```C++ class Solution { @@ -163,13 +162,14 @@ public: # 思考题 -既然三数之和可以使用双指针法,我们之前讲过的[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ),可不可以使用双指针法呢? + +既然三数之和可以使用双指针法,我们之前讲过的[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ),可不可以使用双指针法呢? 如果不能,题意如何更改就可以使用双指针法呢? **大家留言说出自己的想法吧!** -两数之和 就不能使用双指针法,因为[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)要求返回的是索引下表, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。 +两数之和 就不能使用双指针法,因为[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)要求返回的是索引下表, 而双指针法一定要排序,一旦排序之后原数组的索引就被改变了。 -如果[两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)要求返回的是数值的话,就可以使用双指针法了。 +如果[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)要求返回的是数值的话,就可以使用双指针法了。 @@ -218,8 +218,33 @@ class Solution { ``` Python: - - +```Python +class Solution: + def threeSum(self, nums): + ans = [] + n = len(nums) + nums.sort() + for i in range(n): + left = i + 1 + right = n - 1 + if nums[i] > 0: + break + if i >= 1 and nums[i] == nums[i - 1]: + continue + while left < right: + total = nums[i] + nums[left] + nums[right] + if total > 0: + right -= 1 + elif total < 0: + left += 1 + else: + ans.append([nums[i], nums[left], nums[right]]) + while left != right and nums[left] == nums[left + 1]: left += 1 + while left != right and nums[right] == nums[right - 1]: right -= 1 + left += 1 + right -= 1 + return ans +``` Go: ```Go func threeSum(nums []int)[][]int{ @@ -256,8 +281,78 @@ func threeSum(nums []int)[][]int{ } ``` +javaScript: + +```js +/** + * @param {number[]} nums + * @return {number[][]} + */ + +// 循环内不考虑去重 +var threeSum = function(nums) { + const len = nums.length; + if(len < 3) return []; + nums.sort((a, b) => a - b); + const resSet = new Set(); + for(let i = 0; i < len - 2; i++) { + if(nums[i] > 0) break; + let l = i + 1, r = len - 1; + while(l < r) { + const sum = nums[i] + nums[l] + nums[r]; + if(sum < 0) { l++; continue }; + if(sum > 0) { r--; continue }; + resSet.add(`${nums[i]},${nums[l]},${nums[r]}`); + l++; + r--; + } + } + return Array.from(resSet).map(i => i.split(",")); +}; + +// 去重优化 +var threeSum = function(nums) { + const len = nums.length; + if(len < 3) return []; + nums.sort((a, b) => a - b); + const res = []; + for(let i = 0; i < len - 2; i++) { + if(nums[i] > 0) break; + // a去重 + if(i > 0 && nums[i] === nums[i - 1]) continue; + let l = i + 1, r = len - 1; + while(l < r) { + const sum = nums[i] + nums[l] + nums[r]; + if(sum < 0) { l++; continue }; + if(sum > 0) { r--; continue }; + res.push([nums[i], nums[l], nums[r]]) + // b c 去重 + while(l < r && nums[l] === nums[++l]); + while(l < r && nums[r] === nums[--r]); + } + } + return res; +}; +``` +ruby: +```ruby +def is_valid(strs) + symbol_map = {')' => '(', '}' => '{', ']' => '['} + stack = [] + strs.size.times {|i| + c = strs[i] + if symbol_map.has_key?(c) + top_e = stack.shift + return false if symbol_map[c] != top_e + else + stack.unshift(c) + end + } + stack.empty? +end +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index f06ed80a..aefee698 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 17.电话号码的字母组合 @@ -137,7 +137,7 @@ for (int i = 0; i < letters.size(); i++) { 关键地方都讲完了,按照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的回溯法模板,不难写出如下C++代码: -``` +```c++ // 版本一 class Solution { private: @@ -183,7 +183,7 @@ public: 一些写法,是把回溯的过程放在递归函数里了,例如如下代码,我可以写成这样:(注意注释中不一样的地方) -``` +```c++ // 版本二 class Solution { private: @@ -272,7 +272,7 @@ class Solution { String str = numString[digits.charAt(num) - '0']; for (int i = 0; i < str.length(); i++) { temp.append(str.charAt(i)); - //回溯 + //c backTracking(digits, numString, num + 1); //剔除末尾的继续尝试 temp.deleteCharAt(temp.length() - 1); @@ -283,9 +283,92 @@ class Solution { Python: +```Python +class Solution: + ans = [] + s = '' + letterMap = { + '2': 'abc', + '3': 'def', + '4': 'ghi', + '5': 'jkl', + '6': 'mno', + '7': 'pqrs', + '8': 'tuv', + '9': 'wxyz' + } + + def letterCombinations(self, digits): + self.ans.clear() + if digits == '': + return self.ans + self.backtracking(digits, 0) + return self.ans + + def backtracking(self, digits, index): + if index == len(digits): + self.ans.append(self.s) + return + else: + letters = self.letterMap[digits[index]] # 取出数字对应的字符集 + for letter in letters: + self.s = self.s + letter # 处理 + self.backtracking(digits, index + 1) + self.s = self.s[:-1] # 回溯 +``` + +python3: + +```py +class Solution: + def letterCombinations(self, digits: str) -> List[str]: + self.s = "" + res = [] + letterMap = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"] + if len(digits) == 0: return res + def backtrack(digits,index): + if index == len(digits): + return res.append(self.s) + digit = int(digits[index]) #将index指向的数字转为int + letters = letterMap[digit] #取数字对应的字符集 + for i in range(len(letters)): + self.s += letters[i] + backtrack(digits,index + 1) #递归,注意index+1,一下层要处理下一个数字 + self.s = self.s[:-1] #回溯 + backtrack(digits,0) + return res +``` + Go: +javaScript: + +```js +var letterCombinations = function(digits) { + const k = digits.length; + const map = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]; + if(!k) return []; + if(k === 1) return map[digits].split(""); + + const res = [], path = []; + backtracking(digits, k, 0); + return res; + + function backtracking(n, k, a) { + if(path.length === k) { + res.push(path.join("")); + return; + } + for(const v of map[n[a]]) { + path.push(v); + backtracking(n, k, a + 1); + path.pop(); + } + + } +}; +``` diff --git a/problems/0018.四数之和.md b/problems/0018.四数之和.md index ff441bf7..0caf12be 100644 --- a/problems/0018.四数之和.md +++ b/problems/0018.四数之和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 一样的道理,能解决四数之和 @@ -31,38 +31,39 @@ https://leetcode-cn.com/problems/4sum/ # 思路 -四数之和,和[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)是一个思路,都是使用双指针法, 基本解法就是在[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) 的基础上再套一层for循环。 +四数之和,和[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)是一个思路,都是使用双指针法, 基本解法就是在[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) 的基础上再套一层for循环。 但是有一些细节需要注意,例如: 不要判断`nums[k] > target` 就返回了,三数之和 可以通过 `nums[i] > 0` 就返回了,因为 0 已经是确定的数了,四数之和这道题目 target是任意值。(大家亲自写代码就能感受出来) -[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下表作为双指针,找到nums[i] + nums[left] + nums[right] == 0。 +[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下表作为双指针,找到nums[i] + nums[left] + nums[right] == 0。 四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下表作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。 那么一样的道理,五数之和、六数之和等等都采用这种解法。 -对于[三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 +对于[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 -之前我们讲过哈希表的经典题目:[四数相加II](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。 +之前我们讲过哈希表的经典题目:[454.四数相加II](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。 -而[四数相加II](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少! +而[454.四数相加II](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于本题还是简单了不少! 我们来回顾一下,几道题目使用了双指针法。 双指针法将时间复杂度O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下: -* [0027.移除元素](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) -* [15.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) + +* [27.移除元素](https://mp.weixin.qq.com/s/RMkulE4NIb6XsSX83ra-Ww) +* [15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg) * [18.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) -双指针来记录前后指针实现链表反转: -* [206.反转链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) +操作链表: -使用双指针来确定有环: +* [206.反转链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A) +* [19.删除链表的倒数第N个节点](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg) +* [面试题 02.07. 链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw) +* [142题.环形链表II](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) -* [142题.环形链表II](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) - -双指针法在数组和链表中还有很多应用,后面还会介绍到。 +双指针法在字符串题目中还有很多应用,后面还会介绍到。 C++代码 @@ -165,11 +166,75 @@ class Solution { ``` Python: +```python +class Solution(object): + def fourSum(self, nums, target): + """ + :type nums: List[int] + :type target: int + :rtype: List[List[int]] + """ + # use a dict to store value:showtimes + hashmap = dict() + for n in nums: + if n in hashmap: + hashmap[n] += 1 + else: + hashmap[n] = 1 + + # good thing about using python is you can use set to drop duplicates. + ans = set() + for i in range(len(nums)): + for j in range(i + 1, len(nums)): + for k in range(j + 1, len(nums)): + val = target - (nums[i] + nums[j] + nums[k]) + if val in hashmap: + # make sure no duplicates. + count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val) + if hashmap[val] > count: + ans.add(tuple(sorted([nums[i], nums[j], nums[k], val]))) + else: + continue + return ans + +``` Go: +javaScript: +```js +/** + * @param {number[]} nums + * @param {number} target + * @return {number[][]} + */ +var fourSum = function(nums, target) { + const len = nums.length; + if(len < 4) return []; + nums.sort((a, b) => a - b); + const res = []; + for(let i = 0; i < len - 3; i++) { + // 去重i + if(i > 0 && nums[i] === nums[i - 1]) continue; + for(let j = i + 1; j < len - 2; j++) { + // 去重j + if(j > i + 1 && nums[j] === nums[j - 1]) continue; + let l = j + 1, r = len - 1; + while(l < r) { + const sum = nums[i] + nums[j] + nums[l] + nums[r]; + if(sum < target) { l++; continue} + if(sum > target) { r--; continue} + res.push([nums[i], nums[j], nums[l], nums[r]]); + while(l < r && nums[l] === nums[++l]); + while(l < r && nums[r] === nums[--r]); + } + } + } + return res; +}; +``` ----------------------- diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index 3b89dabd..52735794 100644 --- a/problems/0019.删除链表的倒数第N个节点.md +++ b/problems/0019.删除链表的倒数第N个节点.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -112,29 +112,79 @@ class Solution { } } ``` + +Python: +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: + head_dummy = ListNode() + head_dummy.next = head + + slow, fast = head_dummy, head_dummy + while(n!=0): #fast先往前走n步 + fast = fast.next + n -= 1 + while(fast.next!=None): + slow = slow.next + fast = fast.next + #fast 走到结尾后,slow的下一个节点为倒数第N个节点 + slow.next = slow.next.next #删除 + return head_dummy.next +``` Go: ```Go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ func removeNthFromEnd(head *ListNode, n int) *ListNode { - result:=&ListNode{} - result.Next=head - var pre *ListNode - cur:=result - - i:=1 - for head!=nil{ - if i>=n{ - pre=cur - cur=cur.Next + dummyHead := &ListNode{} + dummyHead.Next = head + cur := head + prev := dummyHead + i := 1 + for cur != nil { + cur = cur.Next + if i > n { + prev = prev.Next } - head=head.Next i++ } - pre.Next=pre.Next.Next - return result.Next - + prev.Next = prev.Next.Next + return dummyHead.Next } ``` +JavaScript: + +```js +/** + * @param {ListNode} head + * @param {number} n + * @return {ListNode} + */ +var removeNthFromEnd = function(head, n) { + let ret = new ListNode(0, head), + slow = fast = ret; + while(n--) fast = fast.next; + if(!fast) return ret.next; + while (fast.next) { + fast = fast.next; + slow = slow.next + }; + slow.next = slow.next.next; + return ret.next; +}; +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index 77c6e10a..dae84354 100644 --- a/problems/0020.有效的括号.md +++ b/problems/0020.有效的括号.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -162,6 +162,33 @@ class Solution { return deque.isEmpty(); } } +// 方法2 +class Solution { + public boolean isValid(String s) { + + Stack 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: @@ -220,6 +247,49 @@ def is_valid(strs) end ``` +Javascript: +```javascript +var isValid = function (s) { + const stack = []; + for (let i = 0; i < s.length; i++) { + let c = s[i]; + switch (c) { + case '(': + stack.push(')'); + break; + case '[': + stack.push(']'); + break; + case '{': + stack.push('}'); + break; + default: + if (c !== stack.pop()) { + return false; + } + } + } + return stack.length === 0; +}; +// 简化版本 +var isValid = function(s) { + const stack = [], + map = { + "(":")", + "{":"}", + "[":"]" + }; + for(const x of s) { + if(x in map) { + stack.push(x); + continue; + }; + if(map[stack.pop()] !== x) return false; + } + return !stack.length; +}; +``` + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md index 9f07b4f7..59ded523 100644 --- a/problems/0024.两两交换链表中的节点.md +++ b/problems/0024.两两交换链表中的节点.md @@ -1,11 +1,11 @@ -

- + - +

-

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

+

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

+ ## 24. 两两交换链表中的节点 @@ -87,10 +87,116 @@ public: Java: +```Java +// 递归版本 +class Solution { + public ListNode swapPairs(ListNode head) { + // base case 退出提交 + if(head == null || head.next == null) return head; + // 获取当前节点的下一个节点 + ListNode next = head.next; + // 进行递归 + ListNode newNode = swapPairs(next.next); + // 这里进行交换 + next.next = head; + head.next = newNode; + + return next; + } +} +``` + +```java +// 虚拟头结点 +class Solution { + public ListNode swapPairs(ListNode head) { + + ListNode dummyNode = new ListNode(0); + dummyNode.next = head; + ListNode prev = dummyNode; + + while (prev.next != null && prev.next.next != null) { + ListNode temp = head.next.next; // 缓存 next + prev.next = head.next; // 将 prev 的 next 改为 head 的 next + head.next.next = head; // 将 head.next(prev.next) 的next,指向 head + head.next = temp; // 将head 的 next 接上缓存的temp + prev = head; // 步进1位 + head = head.next; // 步进1位 + } + return dummyNode.next; + } +} +``` Python: +```python +class Solution: + def swapPairs(self, head: ListNode) -> ListNode: + dummy = ListNode(0) #设置一个虚拟头结点 + dummy.next = head + cur = dummy + while cur.next and cur.next.next: + tmp = cur.next #记录临时节点 + tmp1 = cur.next.next.next #记录临时节点 + + cur.next = cur.next.next #步骤一 + cur.next.next = tmp #步骤二 + cur.next.next.next = tmp1 #步骤三 + + cur = cur.next.next #cur移动两位,准备下一轮交换 + return dummy.next +``` Go: +```go +func swapPairs(head *ListNode) *ListNode { + dummy := &ListNode{ + Next: head, + } + //head=list[i] + //pre=list[i-1] + pre := dummy + for head != nil && head.Next != nil { + pre.Next = head.Next + next := head.Next.Next + head.Next.Next = head + head.Next = next + //pre=list[(i+2)-1] + pre = head + //head=list[(i+2)] + head = next + } + return dummy.Next +} +``` + +```go +// 递归版本 +func swapPairs(head *ListNode) *ListNode { + if head == nil || head.Next == nil { + return head + } + next := head.Next + head.Next = swapPairs(next.Next) + next.Next = head + return next +} +``` + +Javascript: +```javascript +var swapPairs = function (head) { + let ret = new ListNode(0, head), temp = ret; + while (temp.next && temp.next.next) { + let cur = temp.next.next, pre = temp.next; + pre.next = cur.next; + cur.next = pre; + temp.next = cur; + temp = pre; + } + return ret.next; +}; +``` ----------------------- diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index 9481af1f..f1187db7 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 27. 移除元素 @@ -186,6 +186,36 @@ var removeElement = (nums, val) => { }; ``` +Ruby: +```ruby +def remove_element(nums, val) + i = 0 + nums.each_index do |j| + if nums[j] != val + nums[i] = nums[j] + i+=1 + end + end + i +end +``` +Rust: +```rust +pub fn remove_element(nums: &mut Vec, val: i32) -> &mut Vec { + let mut start: usize = 0; + while start < nums.len() { + if nums[start] == val { + nums.remove(start); + } + start += 1; + } + nums +} +fn main() { + let mut nums = vec![5,1,3,5,2,3,4,1]; + println!("{:?}",remove_element(&mut nums, 5)); +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) * B站视频:[代码随想录](https://space.bilibili.com/525438321) diff --git a/problems/0028.实现strStr.md b/problems/0028.实现strStr.md index c6463e8b..04508440 100644 --- a/problems/0028.实现strStr.md +++ b/problems/0028.实现strStr.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 在一个串中查找是否出现过另一个串,这是KMP的看家本领。 @@ -61,11 +61,6 @@ KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部 读完本篇可以顺便,把leetcode上28.实现strStr()题目做了。 -如果文字实在看不下去,就看我在B站上的视频吧,如下: - -* [帮你把KMP算法学个通透!(理论篇)B站](https://www.bilibili.com/video/BV1PD4y1o7nd/) -* [帮你把KMP算法学个通透!(求next数组代码篇)B站](https://www.bilibili.com/video/BV1M5411j7Xx/) - # 什么是KMP @@ -726,10 +721,98 @@ class Solution: Go: +```go +// 方法一:前缀表使用减1实现 + +// getNext 构造前缀表next +// params: +// next 前缀表数组 +// s 模式串 +func getNext(next []int, s string) { + j := -1 // j表示 最长相等前后缀长度 + next[0] = j + + for i := 1; i < len(s); i++ { + for j >= 0 && s[i] != s[j+1] { + j = next[j] // 回退前一位 + } + if s[i] == s[j+1] { + j++ + } + next[i] = j // next[i]是i(包括i)之前的最长相等前后缀长度 + } +} +func strStr(haystack string, needle string) int { + if len(needle) == 0 { + return 0 + } + next := make([]int, len(needle)) + getNext(next, needle) + j := -1 // 模式串的起始位置 next为-1 因此也为-1 + for i := 0; i < len(haystack); i++ { + for j >= 0 && haystack[i] != needle[j+1] { + j = next[j] // 寻找下一个匹配点 + } + if haystack[i] == needle[j+1] { + j++ + } + if j == len(needle)-1 { // j指向了模式串的末尾 + return i - len(needle) + 1 + } + } + return -1 +} +``` + +```go +// 方法二: 前缀表无减一或者右移 + +// getNext 构造前缀表next +// params: +// next 前缀表数组 +// s 模式串 +func getNext(next []int, s string) { + j := 0 + next[0] = j + for i := 1; i < len(s); i++ { + for j > 0 && s[i] != s[j] { + j = next[j-1] + } + if s[i] == s[j] { + j++ + } + next[i] = j + } +} +func strStr(haystack string, needle string) int { + n := len(needle) + if n == 0 { + return 0 + } + j := 0 + next := make([]int, n) + getNext(next, needle) + for i := 0; i < len(haystack); i++ { + for j > 0 && haystack[i] != needle[j] { + j = next[j-1] // 回退到j的前一位 + } + if haystack[i] == needle[j] { + j++ + } + if j == n { + return i - n + 1 + } + } + return -1 +} +``` + + + ----------------------- * 作者微信:[程序员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/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md index a8da3045..0f826412 100644 --- a/problems/0035.搜索插入位置.md +++ b/problems/0035.搜索插入位置.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -116,7 +116,7 @@ public: **大家要仔细看注释,思考为什么要写while(left <= right), 为什么要写right = middle - 1**。 -``` +```C++ class Solution { public: int searchInsert(vector& nums, int target) { @@ -240,16 +240,16 @@ Python: ```python3 class Solution: def searchInsert(self, nums: List[int], target: int) -> int: - left, right = 0, len(nums) - 1 - + left, right = 0, len(nums) - 1 + while left <= right: middle = (left + right) // 2 - + if nums[middle] < target: left = middle + 1 elif nums[middle] > target: right = middle - 1 - else: + else: return middle return right + 1 ``` @@ -257,7 +257,25 @@ class Solution: Go: +JavaScript: +```js +var searchInsert = function (nums, target) { + let l = 0, r = nums.length - 1, ans = nums.length; + while (l <= r) { + const mid = l + Math.floor((r - l) >> 1); + + if (target > nums[mid]) { + l = mid + 1; + } else { + ans = mid; + r = mid - 1; + } + } + + return ans; +}; +``` ----------------------- diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md index 7b59fad9..4eb60704 100644 --- a/problems/0037.解数独.md +++ b/problems/0037.解数独.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

如果对回溯法理论还不清楚的同学,可以先看这个视频[视频来了!!带你学透回溯算法(理论篇)](https://mp.weixin.qq.com/s/wDd5azGIYWjbU0fdua_qBg) @@ -287,11 +287,97 @@ class Solution { ``` Python: - +```python3 +class Solution: + def solveSudoku(self, board: List[List[str]]) -> None: + """ + Do not return anything, modify board in-place instead. + """ + def backtrack(board): + for i in range(len(board)): #遍历行 + for j in range(len(board[0])): #遍历列 + if board[i][j] != ".": continue + for k in range(1,10): #(i, j) 这个位置放k是否合适 + if isValid(i,j,k,board): + board[i][j] = str(k) #放置k + if backtrack(board): return True #如果找到合适一组立刻返回 + board[i][j] = "." #回溯,撤销k + return False #9个数都试完了,都不行,那么就返回false + return True #遍历完没有返回false,说明找到了合适棋盘位置了 + def isValid(row,col,val,board): + for i in range(9): #判断行里是否重复 + if board[row][i] == str(val): + return False + for j in range(9): #判断列里是否重复 + if board[j][col] == str(val): + return False + startRow = (row // 3) * 3 + startcol = (col // 3) * 3 + for i in range(startRow,startRow + 3): #判断9方格里是否重复 + for j in range(startcol,startcol + 3): + if board[i][j] == str(val): + return False + return True + backtrack(board) +``` Go: +Javascript: +```Javascript +var solveSudoku = function(board) { + function isValid(row, col, val, board) { + let len = board.length + // 行不能重复 + for(let i = 0; i < len; i++) { + if(board[row][i] === val) { + return false + } + } + // 列不能重复 + for(let i = 0; i < len; i++) { + if(board[i][col] === val) { + return false + } + } + let startRow = Math.floor(row / 3) * 3 + let startCol = Math.floor(col / 3) * 3 + for(let i = startRow; i < startRow + 3; i++) { + for(let j = startCol; j < startCol + 3; j++) { + if(board[i][j] === val) { + return false + } + } + } + + return true + } + + function backTracking() { + for(let i = 0; i < board.length; i++) { + for(let j = 0; j < board[0].length; j++) { + if(board[i][j] !== '.') continue + for(let val = 1; val <= 9; val++) { + if(isValid(i, j, `${val}`, board)) { + board[i][j] = `${val}` + if (backTracking()) { + return true + } + + board[i][j] = `.` + } + } + return false + } + } + return true + } + backTracking(board) + return board + +}; +``` ----------------------- diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md index ab118ee0..e3e4a117 100644 --- a/problems/0039.组合总和.md +++ b/problems/0039.组合总和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 39. 组合总和 @@ -237,45 +237,81 @@ public: Java: ```Java +// 剪枝优化 class Solution { - List> lists = new ArrayList<>(); - Deque deque = new LinkedList<>(); - - public List> combinationSum3(int k, int n) { - int[] arr = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9}; - backTracking(arr, n, k, 0); - return lists; + public List> combinationSum(int[] candidates, int target) { + List> res = new ArrayList<>(); + Arrays.sort(candidates); // 先进行排序 + backtracking(res, new ArrayList<>(), candidates, target, 0, 0); + return res; } - public void backTracking(int[] arr, int n, int k, int startIndex) { - //如果 n 小于0,没必要继续本次递归,已经不符合要求了 - if (n < 0) { + public void backtracking(List> res, List path, int[] candidates, int target, int sum, int idx) { + // 找到了数字和为 target 的组合 + if (sum == target) { + res.add(new ArrayList<>(path)); return; } - if (deque.size() == k) { - if (n == 0) { - lists.add(new ArrayList(deque)); - } - return; - } - for (int i = startIndex; i < arr.length - (k - deque.size()) + 1; i++) { - deque.push(arr[i]); - //减去当前元素 - n -= arr[i]; - backTracking(arr, n, k, i + 1); - //恢复n - n += deque.pop(); + + for (int i = idx; i < candidates.length; i++) { + // 如果 sum + candidates[i] > target 就终止遍历 + if (sum + candidates[i] > target) break; + path.add(candidates[i]); + backtracking(res, path, candidates, target, sum + candidates[i], i); + path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素 } } } ``` Python: - - +```python3 +class Solution: + def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: + res = [] + path = [] + def backtrack(candidates,target,sum,startIndex): + if sum > target: return + if sum == target: return res.append(path[:]) + for i in range(startIndex,len(candidates)): + if sum + candidates[i] >target: return #如果 sum + candidates[i] > target 就终止遍历 + sum += candidates[i] + path.append(candidates[i]) + backtrack(candidates,target,sum,i) #startIndex = i:表示可以重复读取当前的数 + sum -= candidates[i] #回溯 + path.pop() #回溯 + candidates = sorted(candidates) #需要排序 + backtrack(candidates,target,0,0) + return res +``` Go: +JavaScript: +```js +var combinationSum = function(candidates, target) { + const res = [], path = []; + candidates.sort(); // 排序 + backtracking(0, 0); + return res; + function backtracking(j, sum) { + if (sum > target) return; + if (sum === target) { + res.push(Array.from(path)); + return; + } + for(let i = j; i < candidates.length; i++ ) { + const n = candidates[i]; + if(n > target - sum) continue; + path.push(n); + sum += n; + backtracking(i, sum); + path.pop(); + sum -= n; + } + } +}; +``` ----------------------- diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md index 50898016..a462b524 100644 --- a/problems/0040.组合总和II.md +++ b/problems/0040.组合总和II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 这篇可以说是全网把组合问题如何去重,讲的最清晰的了! @@ -292,13 +292,61 @@ class Solution { } } ``` - Python: - - +```py +class Solution: + def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]: + res = [] + path = [] + def backtrack(candidates,target,sum,startIndex): + if sum == target: res.append(path[:]) + for i in range(startIndex,len(candidates)): #要对同一树层使用过的元素进行跳过 + if sum + candidates[i] > target: return + if i > startIndex and candidates[i] == candidates[i-1]: continue #直接用startIndex来去重,要对同一树层使用过的元素进行跳过 + sum += candidates[i] + path.append(candidates[i]) + backtrack(candidates,target,sum,i+1) #i+1:每个数字在每个组合中只能使用一次 + sum -= candidates[i] #回溯 + path.pop() #回溯 + candidates = sorted(candidates) #首先把给candidates排序,让其相同的元素都挨在一起。 + backtrack(candidates,target,0,0) + return res +``` Go: +javaScript: +```js +/** + * @param {number[]} candidates + * @param {number} target + * @return {number[][]} + */ +var combinationSum2 = function(candidates, target) { + const res = []; path = [], len = candidates.length; + candidates.sort(); + backtracking(0, 0); + return res; + function backtracking(sum, i) { + if (sum > target) return; + if (sum === target) { + res.push(Array.from(path)); + return; + } + let f = -1; + for(let j = i; j < len; j++) { + const n = candidates[j]; + if(n > target - sum || n === f) continue; + path.push(n); + sum += n; + f = n; + backtracking(sum, j + 1); + path.pop(); + sum -= n; + } + } +}; +``` ----------------------- diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index b8e369e6..4128da4c 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不少,做好心里准备! @@ -175,9 +175,64 @@ class Solution { ``` Python: - +```python +class Solution: + def jump(self, nums: List[int]) -> int: + if len(nums) == 1: return 0 + ans = 0 + curDistance = 0 + nextDistance = 0 + for i in range(len(nums)): + nextDistance = max(i + nums[i], nextDistance) + if i == curDistance: + if curDistance != len(nums) - 1: + ans += 1 + curDistance = nextDistance + if nextDistance >= len(nums) - 1: break + return ans +``` Go: +```Go +func jump(nums []int) int { + dp:=make([]int ,len(nums)) + dp[0]=0 + + for i:=1;ii{ + dp[i]=min(dp[j]+1,dp[i]) + } + } + } + return dp[len(nums)-1] +} +``` + +Javascript: +```Javascript +var jump = function(nums) { + let curIndex = 0 + let nextIndex = 0 + let steps = 0 + for(let i = 0; i < nums.length - 1; i++) { + nextIndex = Math.max(nums[i] + i, nextIndex) + if(i === curIndex) { + curIndex = nextIndex + steps++ + } + } + + return steps +}; +``` + +/* +dp[i]表示从起点到当前位置的最小跳跃次数 +dp[i]=min(dp[j]+1,dp[i]) 表示从j位置用一步跳跃到当前位置,这个j位置可能有很多个,却最小一个就可以 +*/ +``` diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index d177e391..06367851 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 46.全排列 @@ -149,6 +149,7 @@ public: Java: ```java class Solution { + List> result = new ArrayList<>();// 存放符合条件结果的集合 LinkedList path = new LinkedList<>();// 用来存放符合条件结果 boolean[] used; @@ -167,9 +168,6 @@ class Solution { return; } for (int i = 0; i < nums.length; i++){ - // if (path.contains(nums[i])){ - // continue; - // } if (used[i]){ continue; } @@ -184,7 +182,45 @@ class Solution { ``` Python: +```python3 +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + res = [] #存放符合条件结果的集合 + path = [] #用来存放符合条件的结果 + used = [] #用来存放已经用过的数字 + def backtrack(nums,used): + if len(path) == len(nums): + return res.append(path[:]) #此时说明找到了一组 + for i in range(0,len(nums)): + if nums[i] in used: + continue #used里已经收录的元素,直接跳过 + path.append(nums[i]) + used.append(nums[i]) + backtrack(nums,used) + used.pop() + path.pop() + backtrack(nums,used) + return res +``` +Python(优化,不用used数组): +```python3 +class Solution: + def permute(self, nums: List[int]) -> List[List[int]]: + res = [] #存放符合条件结果的集合 + path = [] #用来存放符合条件的结果 + def backtrack(nums): + if len(path) == len(nums): + return res.append(path[:]) #此时说明找到了一组 + for i in range(0,len(nums)): + if nums[i] in path: #path里已经收录的元素,直接跳过 + continue + path.append(nums[i]) + backtrack(nums) #递归 + path.pop() #回溯 + backtrack(nums) + return res +``` Go: ```Go @@ -208,14 +244,33 @@ func backtrack(nums,pathNums []int,used []bool){ } } -func permute(nums []int) [][]int { - //var pathNums []int - pathNums:=make([]int,0) - var used=make([]bool,len(nums)) - result=[][]int{} - backtrack(nums,pathNums,used) +Javascript: + +```javascript + +var permute = function(nums) { + let result = [] + let path = [] + function backtracing(used) { + if(path.length === nums.length) { + result.push(path.slice(0)) + 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 + } + } + backtracing([]) return result -} +}; + ``` diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index 94bb4df1..b4fb3470 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 排列问题(二) ## 47.全排列 II @@ -85,7 +85,7 @@ public: path.clear(); sort(nums.begin(), nums.end()); // 排序 vector used(nums.size(), false); - backtracking(nums, vec, used); + backtracking(nums, used); return result; } }; @@ -220,6 +220,43 @@ class Solution: return res ``` +Javascript: + +```javascript + +var permuteUnique = function (nums) { + nums.sort((a, b) => { + return a - b + }) + let result = [] + let path = [] + + function backtracing( used) { + if (path.length === nums.length) { + result.push(path.slice()) + return + } + for (let i = 0; i < nums.length; i++) { + if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) { + continue + } + if (!used[i]) { + used[i] = true + path.push(nums[i]) + backtracing(used) + path.pop() + used[i] = false + } + + + } + } + backtracing([]) + return result +}; + +``` + diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index 935ea89c..5242fce2 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 第51题. N皇后 @@ -430,6 +430,65 @@ func solveNQueens(n int) [][]string { } ``` +Javascript: +```Javascript +var solveNQueens = function(n) { + function isValid(row, col, chessBoard, n) { + + for(let i = 0; i < row; i++) { + if(chessBoard[i][col] === 'Q') { + return false + } + } + + for(let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if(chessBoard[i][j] === 'Q') { + return false + } + } + + for(let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if(chessBoard[i][j] === 'Q') { + return false + } + } + return true + } + + function transformChessBoard(chessBoard) { + let chessBoardBack = [] + chessBoard.forEach(row => { + let rowStr = '' + row.forEach(value => { + rowStr += value + }) + chessBoardBack.push(rowStr) + }) + + return chessBoardBack + } + + let result = [] + function backtracing(row,chessBoard) { + if(row === n) { + result.push(transformChessBoard(chessBoard)) + return + } + for(let col = 0; col < n; col++) { + if(isValid(row, col, chessBoard, n)) { + chessBoard[row][col] = 'Q' + backtracing(row + 1,chessBoard) + chessBoard[row][col] = '.' + } + } + } + let chessBoard = new Array(n).fill([]).map(() => new Array(n).fill('.')) + backtracing(0,chessBoard) + return result + +}; +``` + ----------------------- diff --git a/problems/0053.最大子序和.md b/problems/0053.最大子序和.md index 6474d01f..81e0b35a 100644 --- a/problems/0053.最大子序和.md +++ b/problems/0053.最大子序和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 53. 最大子序和 @@ -175,29 +175,39 @@ class Solution: ``` Go: -```Go -func maxSubArray(nums []int) int { - if len(nums)<1{ - return 0 - } - dp:=make([]int,len(nums)) - result:=nums[0] - dp[0]=nums[0] - for i:=1;ib{ - return a - }else{ - return b +```go +func maxSubArray(nums []int) int { + maxSum := nums[0] + for i := 1; i < len(nums); i++ { + if nums[i] + nums[i-1] > nums[i] { + nums[i] += nums[i-1] + } + if nums[i] > maxSum { + maxSum = nums[i] + } } + return maxSum } ``` +Javascript: +```Javascript +var maxSubArray = function(nums) { + let result = -Infinity + let count = 0 + for(let i = 0; i < nums.length; i++) { + count += nums[i] + if(count > result) { + result = count + } + if(count < 0) { + count = 0 + } + } + return result +}; +``` + ----------------------- diff --git a/problems/0053.最大子序和(动态规划).md b/problems/0053.最大子序和(动态规划).md index 957d8b6c..dd0e513b 100644 --- a/problems/0053.最大子序和(动态规划).md +++ b/problems/0053.最大子序和(动态规划).md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 53. 最大子序和 @@ -95,10 +95,47 @@ public: Java: +```java + /** + * 1.dp[i]代表当前下标对应的最大值 + * 2.递推公式 dp[i] = max (dp[i-1]+nums[i],nums[i]) res = max(res,dp[i]) + * 3.初始化 都为 0 + * 4.遍历方向,从前往后 + * 5.举例推导结果。。。 + * + * @param nums + * @return + */ + public static int maxSubArray(int[] nums) { + if (nums.length == 0) { + return 0; + } + int res = nums[0]; + int[] dp = new int[nums.length]; + dp[0] = nums[0]; + for (int i = 1; i < nums.length; i++) { + dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]); + res = res > dp[i] ? res : dp[i]; + } + return res; + } +``` Python: - +```python +class Solution: + def maxSubArray(self, nums: List[int]) -> int: + if len(nums) == 0: + return 0 + dp = [0] * len(nums) + dp[0] = nums[0] + result = dp[0] + for i in range(1, len(nums)): + dp[i] = max(dp[i-1] + nums[i], nums[i]) #状态转移公式 + result = max(result, dp[i]) #result 保存dp[i]的最大值 + return result +``` Go: diff --git a/problems/0055.跳跃游戏.md b/problems/0055.跳跃游戏.md index a25c831a..8618515e 100644 --- a/problems/0055.跳跃游戏.md +++ b/problems/0055.跳跃游戏.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 55. 跳跃游戏 @@ -122,8 +122,39 @@ class Solution: ``` Go: +```Go +func canJUmp(nums []int) bool { + if len(nums)<=1{ + return true + } + dp:=make([]bool,len(nums)) + dp[0]=true + for i:=1;i=0;j--{ + if dp[j]&&nums[j]+j>=i{ + dp[i]=true + break + } + } + } + return dp[len(nums)-1] +} +``` - +Javascript: +```Javascript +var canJump = function(nums) { + if(nums.length === 1) return true + let cover = 0 + for(let i = 0; i <= cover; i++) { + cover = Math.max(cover, i + nums[i]) + if(cover >= nums.length - 1) { + return true + } + } + return false +}; +``` ----------------------- diff --git a/problems/0056.合并区间.md b/problems/0056.合并区间.md index e84a1634..d4ffc554 100644 --- a/problems/0056.合并区间.md +++ b/problems/0056.合并区间.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 56. 合并区间 @@ -141,16 +141,7 @@ Java: class Solution { public int[][] merge(int[][] intervals) { List res = new LinkedList<>(); - Arrays.sort(intervals, new Comparator() { - @Override - public int compare(int[] o1, int[] o2) { - if (o1[0] != o2[0]) { - return Integer.compare(o1[0],o2[0]); - } else { - return Integer.compare(o1[1],o2[1]); - } - } - }); + Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0])); int start = intervals[0][0]; for (int i = 1; i < intervals.length; i++) { @@ -168,10 +159,69 @@ class Solution { ``` Python: - +```python +class Solution: + def merge(self, intervals: List[List[int]]) -> List[List[int]]: + if len(intervals) == 0: return intervals + intervals.sort(key=lambda x: x[0]) + result = [] + result.append(intervals[0]) + for i in range(1, len(intervals)): + last = result[-1] + if last[1] >= intervals[i][0]: + result[-1] = [last[0], max(last[1], intervals[i][1])] + else: + result.append(intervals[i]) + return result +``` Go: +```Go +func merge(intervals [][]int) [][]int { + sort.Slice(intervals, func(i, j int) bool { + return intervals[i][0] b { return a } + return b +} +``` + +Javascript: +```javascript +var merge = function (intervals) { + intervals.sort((a, b) => a[0] - b[0]); + let prev = intervals[0] + let result = [] + for(let i =0; i prev[1]){ + result.push(prev) + prev = cur + }else{ + prev[1] = Math.max(cur[1],prev[1]) + } + } + result.push(prev) + return result +}; +``` @@ -179,4 +229,4 @@ Go: * 作者微信:[程序员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/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md index 5e2d48d8..6d8ec99c 100644 --- a/problems/0059.螺旋矩阵II.md +++ b/problems/0059.螺旋矩阵II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -224,6 +224,88 @@ class Solution: return matrix ``` +javaScript + +```js + +/** + * @param {number} n + * @return {number[][]} + */ +var generateMatrix = function(n) { + // new Array(n).fill(new Array(n)) + // 使用fill --> 填充的是同一个数组地址 + const res = Array.from({length: n}).map(() => new Array(n)); + let loop = n >> 1, i = 0, //循环次数 + count = 1, + startX = startY = 0; // 起始位置 + while(++i <= loop) { + // 定义行列 + let row = startX, column = startY; + // [ startY, n - i) + while(column < n - i) { + res[row][column++] = count++; + } + // [ startX, n - i) + while(row < n - i) { + res[row++][column] = count++; + } + // [n - i , startY) + while(column > startY) { + res[row][column--] = count++; + } + // [n - i , startX) + while(row > startX) { + res[row--][column] = count++; + } + startX = ++startY; + } + if(n & 1) { + res[startX][startY] = count; + } + return res; +}; +``` + +Go: + +```go +func generateMatrix(n int) [][]int { + top, bottom := 0, n-1 + left, right := 0, n-1 + num := 1 + tar := n * n + matrix := make([][]int, n) + for i := 0; i < n; i++ { + matrix[i] = make([]int, n) + } + for num <= tar { + for i := left; i <= right; i++ { + matrix[top][i] = num + num++ + } + top++ + for i := top; i <= bottom; i++ { + matrix[i][right] = num + num++ + } + right-- + for i := right; i >= left; i-- { + matrix[bottom][i] = num + num++ + } + bottom-- + for i := bottom; i >= top; i-- { + matrix[i][left] = num + num++ + } + left++ + } + return matrix +} +``` + + ----------------------- diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index e3a6da8c..47cb41af 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 62.不同路径 @@ -86,7 +86,7 @@ public: ### 动态规划 -机器人从(0 , 0) 位置触发,到(m - 1, n - 1)终点。 +机器人从(0 , 0) 位置出发,到(m - 1, n - 1)终点。 按照动规五部曲来分析: @@ -243,14 +243,92 @@ public: Java: +```java + /** + * 1. 确定dp数组下表含义 dp[i][j] 到每一个坐标可能的路径种类 + * 2. 递推公式 dp[i][j] = dp[i-1][j] dp[i][j-1] + * 3. 初始化 dp[i][0]=1 dp[0][i]=1 初始化横竖就可 + * 4. 遍历顺序 一行一行遍历 + * 5. 推导结果 。。。。。。。。 + * + * @param m + * @param n + * @return + */ + public static int uniquePaths(int m, int n) { + int[][] dp = new int[m][n]; + //初始化 + for (int i = 0; i < m; i++) { + dp[i][0] = 1; + } + for (int i = 0; i < n; i++) { + dp[0][i] = 1; + } + for (int i = 1; i < m; i++) { + for (int j = 1; j < n; j++) { + dp[i][j] = dp[i-1][j]+dp[i][j-1]; + } + } + return dp[m-1][n-1]; + } + +``` 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 + for i in range(1, m): + for j in range(1, n): + dp[i][j] = dp[i][j - 1] + dp[i - 1][j] + return dp[m - 1][n - 1] +``` Go: +```Go +func uniquePaths(m int, n int) int { + dp := make([][]int, m) + for i := range dp { + dp[i] = make([]int, n) + dp[i][0] = 1 + } + for j := 0; j < n; j++ { + dp[0][j] = 1 + } + for i := 1; i < m; i++ { + for j := 1; j < n; j++ { + dp[i][j] = dp[i-1][j] + dp[i][j-1] + } + } + return dp[m-1][n-1] +} +``` - +Javascript: +```Javascript +var uniquePaths = function(m, n) { + const dp = Array(m).fill().map(item => Array(n)) + + for (let i = 0; i < m; ++i) { + dp[i][0] = 1 + } + + for (let i = 0; i < n; ++i) { + dp[0][i] = 1 + } + + for (let i = 1; i < m; ++i) { + for (let j = 1; j < n; ++j) { + dp[i][j] = dp[i - 1][j] + dp[i][j - 1] + } + } + return dp[m - 1][n - 1] +}; +``` ----------------------- diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index 311f712e..52f00322 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 63. 不同路径 II @@ -157,8 +157,6 @@ public: * 时间复杂度O(n * m) n m 分别为obstacleGrid 长度和宽度 * 空间复杂度O(n * m) -至于能不能优化空间降为一维dp数组,我感觉不太行,因为要考虑障碍,如果把这些障碍压缩到一行,结果一定就不一样了。 - ## 总结 本题是[62.不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A)的障碍版,整体思路大体一致。 @@ -237,7 +235,74 @@ class Solution: Go: +```go +func uniquePathsWithObstacles(obstacleGrid [][]int) int { + m,n:= len(obstacleGrid),len(obstacleGrid[0]) + // 定义一个dp数组 + dp := make([][]int,m) + for i,_ := range dp { + dp[i] = make([]int,n) + } + // 初始化 + for i:=0;i Array(n).fill(0)) + + for (let i = 0; i < m && obstacleGrid[i][0] === 0; ++i) { + dp[i][0] = 1 + } + + for (let i = 0; i < n && obstacleGrid[0][i] === 0; ++i) { + dp[0][i] = 1 + } + + for (let i = 1; i < m; ++i) { + for (let j = 1; j < n; ++j) { + dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i - 1][j] + dp[i][j - 1] + } + } + + return dp[m - 1][n - 1] +}; +``` ----------------------- diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index f0a08f99..680af587 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -1,14 +1,15 @@

- + - +

-

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

+

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

## 70. 爬楼梯 +题目地址:https://leetcode-cn.com/problems/climbing-stairs/ -假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 +假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? @@ -212,7 +213,45 @@ public: Java: +```Java +class Solution { + public int climbStairs(int n) { + // 跟斐波那契数列一样 + if(n <= 2) return n; + int a = 1, b = 2, sum = 0; + + for(int i = 3; i <= n; i++){ + sum = a + b; + a = b; + b = sum; + } + return b; + } +} +``` +```java +// 常规方式 +public int climbStairs(int n) { + int[] dp = new int[n + 1]; + dp[0] = 1; + dp[1] = 1; + for (int i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +} +// 用变量记录代替数组 +public int climbStairs(int n) { + int a = 0, b = 1, c = 0; // 默认需要1次 + for (int i = 1; i <= n; i++) { + c = a + b; // f(i - 1) + f(n - 2) + a = b; // 记录上一轮的值 + b = c; // 向后步进1个数 + } + return c; +} +``` Python: diff --git a/problems/0070.爬楼梯完全背包版本.md b/problems/0070.爬楼梯完全背包版本.md index f92e6716..b3e7eb73 100644 --- a/problems/0070.爬楼梯完全背包版本.md +++ b/problems/0070.爬楼梯完全背包版本.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:以前我没得选,现在我选择再爬一次! 之前讲这道题目的时候,因为还没有讲背包问题,所以就只是讲了一下爬楼梯最直接的动规方法(斐波那契)。 @@ -149,13 +149,48 @@ class Solution { Python: -Go: +```python3 +class Solution: + def climbStairs(self, n: int) -> int: + dp = [0]*(n + 1) + dp[0] = 1 + m = 2 + # 遍历背包 + for j in range(n + 1): + # 遍历物品 + for step in range(1, m + 1): + if j >= step: + dp[j] += dp[j - step] + return dp[n] +``` +Go: +```go +func climbStairs(n int) int { + //定义 + dp := make([]int, n+1) + //初始化 + dp[0] = 1 + // 本题物品只有两个1,2 + m := 2 + // 遍历顺序 + for j := 1; j <= n; j++ { //先遍历背包 + for i := 1; i <= m; i++ { //再遍历物品 + if j >= i { + dp[j] += dp[j-i] + } + //fmt.Println(dp) + } + } + return dp[n] +} +``` + ----------------------- * 作者微信:[程序员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/0072.编辑距离.md b/problems/0072.编辑距离.md index 7e27a3d5..2044ac25 100644 --- a/problems/0072.编辑距离.md +++ b/problems/0072.编辑距离.md @@ -1,13 +1,15 @@

- + - +

-

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

+

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

## 72. 编辑距离 +https://leetcode-cn.com/problems/edit-distance/ + 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作: @@ -16,23 +18,23 @@ * 删除一个字符 * 替换一个字符 -示例 1: -输入:word1 = "horse", word2 = "ros" -输出:3 -解释: -horse -> rorse (将 'h' 替换为 'r') -rorse -> rose (删除 'r') -rose -> ros (删除 'e') +示例 1: +输入:word1 = "horse", word2 = "ros" +输出:3 +解释: +horse -> rorse (将 'h' 替换为 'r') +rorse -> rose (删除 'r') +rose -> ros (删除 'e') -示例 2: -输入:word1 = "intention", word2 = "execution" -输出:5 -解释: -intention -> inention (删除 't') -inention -> enention (将 'i' 替换为 'e') -enention -> exention (将 'n' 替换为 'x') -exention -> exection (将 'n' 替换为 'c') -exection -> execution (插入 'u') +示例 2: +输入:word1 = "intention", word2 = "execution" +输出:5 +解释: +intention -> inention (删除 't') +inention -> enention (将 'i' 替换为 'e') +enention -> exention (将 'n' 替换为 'x') +exention -> exection (将 'n' 替换为 'c') +exection -> execution (插入 'u')   提示: @@ -198,10 +200,50 @@ public: Java: - +```java +public int minDistance(String word1, String word2) { + int m = word1.length(); + int n = word2.length(); + int[][] dp = new int[m + 1][n + 1]; + // 初始化 + for (int i = 1; i <= m; i++) { + dp[i][0] = i; + } + for (int j = 1; j <= n; j++) { + dp[0][j] = j; + } + for (int i = 1; i <= m; i++) { + for (int j = 1; j <= n; j++) { + // 因为dp数组有效位从1开始 + // 所以当前遍历到的字符串的位置为i-1 | j-1 + if (word1.charAt(i - 1) == word2.charAt(j - 1)) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1; + } + } + } + return dp[m][n]; +} +``` Python: - +```python +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + dp = [[0] * (len(word2)+1) for _ in range(len(word1)+1)] + for i in range(len(word1)+1): + dp[i][0] = i + for j in range(len(word2)+1): + dp[0][j] = j + for i in range(1, len(word1)+1): + for j in range(1, len(word2)+1): + if word1[i-1] == word2[j-1]: + dp[i][j] = dp[i-1][j-1] + else: + dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1 + return dp[-1][-1] +``` Go: ```Go diff --git a/problems/0077.组合.md b/problems/0077.组合.md index f31766e0..0b289a40 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -370,11 +370,70 @@ class Solution { Python: - - +```python3 +class Solution: + def combine(self, n: int, k: int) -> List[List[int]]: + res=[] #存放符合条件结果的集合 + path=[] #用来存放符合条件结果 + def backtrack(n,k,startIndex): + if len(path) == k: + res.append(path[:]) + return + for i in range(startIndex,n+1): + path.append(i) #处理节点 + backtrack(n,k,i+1) #递归 + path.pop() #回溯,撤销处理的节点 + backtrack(n,k,1) + return res +``` +javascript +```javascript +let result = [] +let path = [] +var combine = function(n, k) { + result = [] + combineHelper(n, k, 1) + return result +}; +const combineHelper = (n, k, startIndex) => { + if (path.length === k) { + result.push([...path]) + return + } + for (let i = startIndex; i <= n - (k - path.length) + 1; ++i) { + path.push(i) + combineHelper(n, k, i + 1) + path.pop() + } +} +``` Go: - - +```Go +var res [][]int +func combine(n int, k int) [][]int { + res=[][]int{} + if n <= 0 || k <= 0 || k > n { + return res + } + backtrack(n, k, 1, []int{}) + return res +} +func backtrack(n,k,start int,track []int){ + if len(track)==k{ + temp:=make([]int,k) + copy(temp,track) + res=append(res,temp) + } + if len(track)+n-start+1 < k { + return + } + for i:=start;i<=n;i++{ + track=append(track,i) + backtrack(n,k,i+1,track) + track=track[:len(track)-1] + } +} +``` ----------------------- diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md index a8a17858..d3e82f09 100644 --- a/problems/0077.组合优化.md +++ b/problems/0077.组合优化.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -147,7 +147,7 @@ public: Java: -``` +```java class Solution { List> result = new ArrayList<>(); LinkedList path = new LinkedList<>(); @@ -176,9 +176,71 @@ class Solution { ``` Python: - - +```python3 +class Solution: + def combine(self, n: int, k: int) -> List[List[int]]: + res=[] #存放符合条件结果的集合 + path=[] #用来存放符合条件结果 + def backtrack(n,k,startIndex): + if len(path) == k: + res.append(path[:]) + return + for i in range(startIndex,n-(k-len(path))+2): #优化的地方 + path.append(i) #处理节点 + backtrack(n,k,i+1) #递归 + path.pop() #回溯,撤销处理的节点 + backtrack(n,k,1) + return res +``` Go: +```Go +var res [][]int +func combine(n int, k int) [][]int { + res=[][]int{} + if n <= 0 || k <= 0 || k > n { + return res + } + backtrack(n, k, 1, []int{}) + return res +} +func backtrack(n,k,start int,track []int){ + if len(track)==k{ + temp:=make([]int,k) + copy(temp,track) + res=append(res,temp) + } + if len(track)+n-start+1 < k { + return + } + for i:=start;i<=n;i++{ + track=append(track,i) + backtrack(n,k,i+1,track) + track=track[:len(track)-1] + } +} +``` + +javaScript: + +```js +var combine = function(n, k) { + const res = [], path = []; + backtracking(n, k, 1); + return res; + function backtracking (n, k, i){ + const len = path.length; + if(len === k) { + res.push(Array.from(path)); + return; + } + for(let a = i; a <= n + len - k + 1; a++) { + path.push(a); + backtracking(n, k, a + 1); + path.pop(); + } + } +}; +``` diff --git a/problems/0078.子集.md b/problems/0078.子集.md index 7166f7fa..0b2f3c09 100644 --- a/problems/0078.子集.md +++ b/problems/0078.子集.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 第78题. 子集 @@ -186,7 +186,6 @@ class Solution { result.add(new ArrayList<>()); return result; } - Arrays.sort(nums); subsetsHelper(nums, 0); return result; } @@ -206,7 +205,20 @@ class Solution { ``` Python: - +```python3 +class Solution: + def subsets(self, nums: List[int]) -> List[List[int]]: + res = [] + path = [] + def backtrack(nums,startIndex): + res.append(path[:]) #收集子集,要放在终止添加的上面,否则会漏掉自己 + for i in range(startIndex,len(nums)): #当startIndex已经大于数组的长度了,就终止了,for循环本来也结束了,所以不需要终止条件 + path.append(nums[i]) + backtrack(nums,i+1) #递归 + path.pop() #回溯 + backtrack(nums,0) + return res +``` Go: ```Go diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md index 941e3eca..71aef5c7 100644 --- a/problems/0090.子集II.md +++ b/problems/0090.子集II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 第90题.子集II @@ -208,7 +208,23 @@ class Solution { ``` Python: - +```python3 +class Solution: + def subsetsWithDup(self, nums: List[int]) -> List[List[int]]: + res = [] #存放符合条件结果的集合 + path = [] #用来存放符合条件结果 + def backtrack(nums,startIndex): + res.append(path[:]) + for i in range(startIndex,len(nums)): + if i > startIndex and nums[i] == nums[i - 1]: #我们要对同一树层使用过的元素进行跳过 + continue + path.append(nums[i]) + backtrack(nums,i+1) #递归 + path.pop() #回溯 + nums = sorted(nums) #去重需要排序 + backtrack(nums,0) + return res +``` Go: ```Go diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index 4cea7a3a..a8b9a215 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -338,6 +338,35 @@ class Solution(object): return ans``` ``` +JavaScript: + +```js +/** + * @param {string} s + * @return {string[]} + */ +var restoreIpAddresses = function(s) { + const res = [], path = []; + backtracking(0, 0) + return res; + function backtracking(i) { + const len = path.length; + if(len > 4) return; + if(len === 4 && i === s.length) { + res.push(path.join(".")); + return; + } + for(let j = i; j < s.length; j++) { + const str = s.substr(i, j - i + 1); + if(str.length > 3 || +str > 255) break; + if(str.length > 1 && str[0] === "0") break; + path.push(str); + backtracking(j + 1); + path.pop() + } + } +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index 7dea8fb0..a9631315 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 96.不同的二叉搜索树 @@ -186,10 +186,30 @@ class Solution { ``` Python: - +```python +class Solution: + def numTrees(self, n: int) -> int: + dp = [0] * (n + 1) + dp[0], dp[1] = 1, 1 + for i in range(2, n + 1): + for j in range(1, i + 1): + dp[i] += dp[j - 1] * dp[i - j] + return dp[-1] +``` Go: - +```Go +func numTrees(n int)int{ + dp:=make([]int,n+1) + dp[0]=1 + for i:=1;i<=n;i++{ + for 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 f246c21a..eb877abb 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 98.验证二叉搜索树 @@ -304,11 +304,58 @@ class Solution { return true; } } + +// 简洁实现·递归解法 +class Solution { + public boolean isValidBST(TreeNode root) { + return validBST(Long.MIN_VALUE, Long.MAX_VALUE, root); + } + boolean validBST(long lower, long upper, TreeNode root) { + if (root == null) return true; + if (root.val <= lower || root.val >= upper) return false; + return validBST(lower, root.val, root.left) && validBST(root.val, upper, root.right); + } +} +// 简洁实现·中序遍历 +class Solution { + private long prev = Long.MIN_VALUE; + public boolean isValidBST(TreeNode root) { + if (root == null) { + return true; + } + if (!isValidBST(root.left)) { + return false; + } + if (root.val <= prev) { // 不满足二叉搜索树条件 + return false; + } + prev = root.val; + return isValidBST(root.right); + } +} ``` 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 isValidBST(self, root: TreeNode) -> bool: + res = [] //把二叉搜索树按中序遍历写成list + def buildalist(root): + if not root: return + buildalist(root.left) //左 + res.append(root.val) //中 + buildalist(root.right) //右 + return res + buildalist(root) + return res == sorted(res) and len(set(res)) == len(res) //检查list里的数有没有重复元素,以及是否按从小到大排列 +``` Go: ```Go import "math" @@ -336,4 +383,4 @@ func isBST(root *TreeNode, min, max int) 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/0101.对称二叉树.md b/problems/0101.对称二叉树.md index b7d41557..9717588a 100644 --- a/problems/0101.对称二叉树.md +++ b/problems/0101.对称二叉树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 101. 对称二叉树 @@ -360,9 +360,129 @@ Java: Python: +> 递归法 +```python +class Solution: + def isSymmetric(self, root: TreeNode) -> bool: + if not root: + return True + return self.compare(root.left, root.right) + + def compare(self, left, right): + #首先排除空节点的情况 + if left == None and right != None: return False + elif left != None and right == None: return False + elif left == None and right == None: return True + #排除了空节点,再排除数值不相同的情况 + elif left.val != right.val: return False + + #此时就是:左右节点都不为空,且数值相同的情况 + #此时才做递归,做下一层的判断 + outside = self.compare(left.left, right.right) #左子树:左、 右子树:右 + inside = self.compare(left.right, right.left) #左子树:右、 右子树:左 + isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理) + return isSame +``` + +> 迭代法: 使用队列 +```python +import collections +class Solution: + def isSymmetric(self, root: TreeNode) -> bool: + if not root: + return True + queue = collections.deque() + queue.append(root.left) #将左子树头结点加入队列 + queue.append(root.right) #将右子树头结点加入队列 + while queue: #接下来就要判断这这两个树是否相互翻转 + leftNode = queue.popleft() + rightNode = queue.popleft() + if not leftNode and not rightNode: #左节点为空、右节点为空,此时说明是对称的 + continue + + #左右一个节点不为空,或者都不为空但数值不相同,返回false + if not leftNode or not rightNode or leftNode.val != rightNode.val: + return False + queue.append(leftNode.left) #加入左节点左孩子 + queue.append(rightNode.right) #加入右节点右孩子 + queue.append(leftNode.right) #加入左节点右孩子 + queue.append(rightNode.left) #加入右节点左孩子 + return True +``` + +> 迭代法:使用栈 +```python +class Solution: + def isSymmetric(self, root: TreeNode) -> bool: + if not root: + return True + st = [] #这里改成了栈 + st.append(root.left) + st.append(root.right) + while st: + leftNode = st.pop() + rightNode = st.pop() + if not leftNode and not rightNode: + continue + if not leftNode or not rightNode or leftNode.val != rightNode.val: + return False + st.append(leftNode.left) + st.append(rightNode.right) + st.append(leftNode.right) + st.append(rightNode.left) + return True +``` Go: +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +// 递归 +func defs(left *TreeNode, right *TreeNode) bool { + if left == nil && right == nil { + return true; + }; + if left == nil || right == nil { + return false; + }; + if left.Val != right.Val { + return false; + } + return defs(left.Left, right.Right) && defs(right.Left, left.Right); +} +func isSymmetric(root *TreeNode) bool { + return defs(root.Left, root.Right); +} + +// 迭代 +func isSymmetric(root *TreeNode) bool { + var queue []*TreeNode; + if root != nil { + queue = append(queue, root.Left, root.Right); + } + for len(queue) > 0 { + left := queue[0]; + right := queue[1]; + queue = queue[2:]; + if left == nil && right == nil { + continue; + } + if left == nil || right == nil || left.Val != right.Val { + return false; + }; + queue = append(queue, left.Left, right.Right, right.Left, left.Right); + } + return true; +} +``` + JavaScript ```javascript @@ -379,6 +499,90 @@ const check = (leftPtr, rightPtr) => { return leftPtr.val === rightPtr.val && check(leftPtr.left, rightPtr.right) && check(leftPtr.right, rightPtr.left) } ``` +JavaScript: + +递归判断是否为对称二叉树: +```javascript +var isSymmetric = function(root) { + //使用递归遍历左右子树 递归三部曲 + // 1. 确定递归的参数 root.left root.right和返回值true false + const compareNode=function(left,right){ + //2. 确定终止条件 空的情况 + if(left===null&&right!==null||left!==null&&right===null){ + return false; + }else if(left===null&&right===null){ + return true; + }else if(left.val!==right.val){ + return false; + } + //3. 确定单层递归逻辑 + let outSide=compareNode(left.left,right.right); + let inSide=compareNode(left.right,right.left); + return outSide&&inSide; + } + if(root===null){ + return true; + } + return compareNode(root.left,root.right); +}; +``` +队列实现迭代判断是否为对称二叉树: +```javascript +var isSymmetric = function(root) { + //迭代方法判断是否是对称二叉树 + //首先判断root是否为空 + if(root===null){ + return true; + } + let queue=[]; + queue.push(root.left); + queue.push(root.right); + while(queue.length){ + let leftNode=queue.shift();//左节点 + let rightNode=queue.shift();//右节点 + if(leftNode===null&&rightNode===null){ + continue; + } + if(leftNode===null||rightNode===null||leftNode.val!==rightNode.val){ + return false; + } + queue.push(leftNode.left);//左节点左孩子入队 + queue.push(rightNode.right);//右节点右孩子入队 + queue.push(leftNode.right);//左节点右孩子入队 + queue.push(rightNode.left);//右节点左孩子入队 + } + return true; +}; +``` +栈实现迭代判断是否为对称二叉树: +```javascript +var isSymmetric = function(root) { + //迭代方法判断是否是对称二叉树 + //首先判断root是否为空 + if(root===null){ + return true; + } + let stack=[]; + stack.push(root.left); + stack.push(root.right); + while(stack.length){ + let rightNode=stack.pop();//左节点 + let leftNode=stack.pop();//右节点 + if(leftNode===null&&rightNode===null){ + continue; + } + if(leftNode===null||rightNode===null||leftNode.val!==rightNode.val){ + return false; + } + stack.push(leftNode.left);//左节点左孩子入队 + stack.push(rightNode.right);//右节点右孩子入队 + stack.push(leftNode.right);//左节点右孩子入队 + stack.push(rightNode.left);//右节点左孩子入队 + } + return true; +}; +``` + ----------------------- diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 9b5f9ed5..89d0dda7 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 二叉树的层序遍历 @@ -79,6 +79,67 @@ public: return result; } }; +``` +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: + return [] + + quene = [root] + out_list = [] + + while quene: + length = len(queue) # 这里一定要先求出队列的长度,不能用range(len(queue)),因为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(in_list) + + return out_list +``` + + + +javascript代码: + +```javascript +var levelOrder = function(root) { + //二叉树的层序遍历 + let res=[],queue=[]; + queue.push(root); + if(root===null){ + return res; + } + while(queue.length!==0){ + // 记录当前层级节点数 + let length=queue.length; + //存放每一层的节点 + let curLevel=[]; + for(let i=0;i List[List[int]]: + if not root: + return [] + quene = [root] + out_list = [] + + while quene: + in_list = [] + for _ in range(len(quene)): + node = quene.pop(0) + in_list.append(node.val) + if node.left: + quene.append(node.left) + if node.right: + quene.append(node.right) + + out_list.append(in_list) + + out_list.reverse() + return out_list + +# 执行用时:36 ms, 在所有 Python3 提交中击败了92.00%的用户 +# 内存消耗:15.2 MB, 在所有 Python3 提交中击败了63.76%的用户 +``` + + + +javascript代码 + +```javascript +var levelOrderBottom = function(root) { + let res=[],queue=[]; + queue.push(root); + while(queue.length&&root!==null){ + // 存放当前层级节点数组 + let curLevel=[]; + // 计算当前层级节点数量 + let length=queue.length; + while(length--){ + let node=queue.shift(); + // 把当前层节点存入curLevel数组 + curLevel.push(node.val); + // 把下一层级的左右节点存入queue队列 + node.left&&queue.push(node.left); + node.right&&queue.push(node.right); + } + res.push(curLevel); + } + return res.reverse(); +}; +``` ## 199.二叉树的右视图 @@ -159,6 +281,73 @@ 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 rightSideView(self, root: TreeNode) -> List[int]: + if not root: + return [] + + # deque来自collections模块,不在力扣平台时,需要手动写入 + # 'from collections import deque' 导入 + # deque相比list的好处是,list的pop(0)是O(n)复杂度,deque的popleft()是O(1)复杂度 + + quene = deque([root]) + out_list = [] + + while quene: + # 每次都取最后一个node就可以了 + node = quene[-1] + out_list.append(node.val) + + # 执行这个遍历的目的是获取下一层所有的node + for _ in range(len(quene)): + node = quene.popleft() + if node.left: + quene.append(node.left) + if node.right: + quene.append(node.right) + + return out_list + +# 执行用时:36 ms, 在所有 Python3 提交中击败了89.47%的用户 +# 内存消耗:14.6 MB, 在所有 Python3 提交中击败了96.65%的用户 +``` + + + + + +javascript代码: + +```javascript +var rightSideView = function(root) { + //二叉树右视图 只需要把每一层最后一个节点存储到res数组 + let res=[],queue=[]; + queue.push(root); + while(queue.length&&root!==null){ + // 记录当前层级节点个数 + let length=queue.length; + while(length--){ + let node=queue.shift(); + //length长度为0的时候表明到了层级最后一个节点 + if(!length){ + res.push(node.val); + } + node.left&&queue.push(node.left); + node.right&&queue.push(node.right); + } + } + return res; +}; +``` ## 637.二叉树的层平均值 @@ -199,6 +388,71 @@ 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 averageOfLevels(self, root: TreeNode) -> List[float]: + if not root: + return [] + + quene = deque([root]) + out_list = [] + + while quene: + in_list = [] + + for _ in range(len(quene)): + node = quene.popleft() + in_list.append(node.val) + if node.left: + quene.append(node.left) + if node.right: + quene.append(node.right) + + out_list.append(in_list) + + out_list = map(lambda x: sum(x) / len(x), out_list) + + return out_list + +# 执行用时:56 ms, 在所有 Python3 提交中击败了81.48%的用户 +# 内存消耗:17 MB, 在所有 Python3 提交中击败了89.68%的用户 +``` + + + +javascript代码: + +```javascript +var averageOfLevels = function(root) { + //层级平均值 + let res=[],queue=[]; + queue.push(root); + while(queue.length&&root!==null){ + //每一层节点个数 + let length=queue.length; + //sum记录每一层的和 + let sum=0; + for(let i=0;i List[List[int]]: + if not root: + return [] + + quene = deque([root]) + out_list = [] + + while quene: + in_list = [] + + for _ in range(len(quene)): + node = quene.popleft() + in_list.append(node.val) + if node.children: + # 这个地方要用extend而不是append,我们看下面的例子: + # In [18]: alist=[] + # In [19]: alist.append([1,2,3]) + # In [20]: alist + # Out[20]: [[1, 2, 3]] + # In [21]: alist.extend([4,5,6]) + # In [22]: alist + # Out[22]: [[1, 2, 3], 4, 5, 6] + # 可以看到extend对要添加的list进行了一个解包操作 + # print(root.children),可以得到children是一个包含 + # 孩子节点地址的list,我们使用for遍历quene的时候, + # 希望quene是一个单层list,所以要用extend + # 使用extend的情况,如果print(quene),结果是 + # deque([<__main__.Node object at 0x7f60763ae0a0>]) + # deque([<__main__.Node object at 0x7f607636e6d0>, <__main__.Node object at 0x7f607636e130>, <__main__.Node object at 0x7f607636e310>]) + # deque([<__main__.Node object at 0x7f607636e880>, <__main__.Node object at 0x7f607636ef10>]) + # 可以看到是单层list + # 如果使用append,print(quene)的结果是 + # deque([<__main__.Node object at 0x7f18907530a0>]) + # deque([[<__main__.Node object at 0x7f18907136d0>, <__main__.Node object at 0x7f1890713130>, <__main__.Node object at 0x7f1890713310>]]) + # 可以看到是两层list,这样for的遍历就会报错 + + quene.extend(node.children) + + out_list.append(in_list) + + return out_list + +# 执行用时:60 ms, 在所有 Python3 提交中击败了76.99%的用户 +# 内存消耗:16.5 MB, 在所有 Python3 提交中击败了89.19%的用户 +``` + + + +JavaScript代码: + +```JavaScript +var levelOrder = function(root) { + //每一层可能有2个以上,所以不再使用node.left node.right + let res=[],queue=[]; + queue.push(root); + while(queue.length&&root!==null){ + //记录每一层节点个数还是和二叉树一致 + let length=queue.length; + //存放每层节点 也和二叉树一致 + let curLevel=[]; + while(length--){ + let node = queue.shift(); + curLevel.push(node.val); + //这里不再是 ndoe.left node.right 而是循坏node.children + for(let item of node.children){ + item&&queue.push(item); + } + } + res.push(curLevel); + } + return res; +}; +``` + ## 515.在每个树行中找最大值 题目链接:https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/ @@ -287,6 +627,29 @@ public: } }; ``` +javascript代码: + +```javascript +var largestValues = function(root) { + //使用层序遍历 + let res=[],queue=[]; + queue.push(root); + while(root!==null&&queue.length){ + //设置max初始值就是队列的第一个元素 + let max=queue[0]; + let length=queue.length; + while(length--){ + let node = queue.shift(); + max=max>node.val?max:node.val; + node.left&&queue.push(node.left); + node.right&&queue.push(node.right); + } + //把每一层的最大值放到res数组 + res.push(max); + } + return res; +}; +``` ## 116.填充每个节点的下一个右侧节点指针 @@ -657,8 +1020,569 @@ 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 +} +/** +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 二叉树的层序遍历(Javascript语言完全版) (迭代 + 递归) + +```js +/** + * 102. 二叉树的层序遍历 + * @param {TreeNode} root + * @return {number[][]} + */ + +// 迭代 + +var levelOrder = function(root) { + const queue = [], res = []; + root && queue.push(root); + while(len = queue.length) { + const val = []; + while(len--) { + const node = queue.shift(); + val.push(node.val); + node.left && queue.push(node.left); + node.right && queue.push(node.right); + } + res.push(val); + } + return res; +}; + +// 递归 +var levelOrder = function(root) { + const res = []; + function defs (root, i) { + if(!root) return; + if(!res[i]) res[i] = []; + res[i].push(root.val) + root.left && defs(root.left, i + 1); + root.right && defs(root.right, i + 1); + } + defs(root, 0); + return res; +}; +/** + * 107. 二叉树的层序遍历 II + * @param {TreeNode} root + * @return {number[][]} + */ + +// 迭代 + +var levelOrderBottom = function(root) { + const queue = [], res = []; + root && queue.push(root); + while(len = queue.length) { + const val = []; + while(len--) { + const node = queue.shift(); + val.push(node.val); + node.left && queue.push(node.left); + node.right && queue.push(node.right); + } + res.push(val); + } + return res.reverse() +}; + +// 递归 + +var levelOrderBottom = function(root) { + const res = []; + function defs (root, i) { + if(!root) return; + if(!res[i]) res[i] = []; + res[i].push(root.val); + root.left && defs(root.left, i + 1); + root.right && defs(root.right, i + 1); + } + defs(root, 0); + return res.reverse(); +}; + +/** + * 199. 二叉树的右视图 + * @param {TreeNode} root + * @return {number[]} + */ + +// 迭代 + +var rightSideView = function(root) { + const res = [], queue = []; + root && queue.push(root); + while(l = queue.length) { + while (l--) { + const {val, left, right} = queue.shift(); + !l && res.push(val); + left && queue.push(left); + right && queue.push(right); + } + } + return res; +}; + +// 递归 +var rightSideView = function(root) { + const res = []; + function defs(root, i) { + if(!root) return; + res[i] = root.val; + root.left && defs(root.left, i + 1); + root.right && defs(root.right, i + 1); + } + defs(root, 0); + return res; +}; + +/** + * 637. 二叉树的层平均值 + * @param {TreeNode} root + * @return {number[]} + */ + +// 迭代 +var averageOfLevels = function(root) { + const queue = [], res = []; + root && queue.push(root); + while(len = queue.length) { + let sum = 0, l = len; + while(l--) { + const {val, left, right} = queue.shift(); + sum += val; + left && queue.push(left); + right && queue.push(right); + } + res.push(sum/len); + } + return res; +}; + +// 递归 +var averageOfLevels = function(root) { + const resCount = [], res = []; + function defs(root, i) { + if(!root) return; + if(isNaN(res[i])) resCount[i] = res[i] = 0; + res[i] += root.val; + resCount[i]++; + root.left && defs(root.left, i + 1); + root.right && defs(root.right, i + 1); + } + defs(root, 0); + return res.map((val, i) => val / resCount[i]); +}; + +/** + * 515. 在每个树行中找最大值 + * @param {TreeNode} root + * @return {number[]} + */ + +// 迭代 +const MIN_G = Number.MIN_SAFE_INTEGER; +var largestValues = function(root) { + const queue = [], res = []; + root && queue.push(root); + while(len = queue.length) { + let max = MIN_G; + while(len--) { + const {val, left, right} = queue.shift(); + max = max > val ? max : val; + left && queue.push(left); + right && queue.push(right); + } + res.push(max); + } + return res; +}; + +// 递归 +var largestValues = function(root) { + const res = []; + function defs (root, i) { + if(!root) return; + if(isNaN(res[i])) res[i] = root.val; + res[i] = res[i] > root.val ? res[i] : root.val; + root.left && defs(root.left, i + 1); + root.right && defs(root.right, i + 1); + } + defs(root, 0); + return res; +}; + +/** + * 429. N 叉树的层序遍历 + * @param {Node|null} root + * @return {number[][]} + */ + +// 迭代 +var levelOrder = function(root) { + const queue = [], res = []; + root && queue.push(root); + while(len = queue.length) { + const vals = []; + while(len--) { + const {val, children} = queue.shift(); + vals.push(val); + for(const e of children) { + queue.push(e); + } + } + res.push(vals); + } + return res; +}; + +// 递归 + +var levelOrder = function(root) { + const res = []; + function defs (root, i) { + if(!root) return; + if(!res[i]) res[i] = []; + res[i].push(root.val); + for(const e of root.children) { + defs(e, i + 1); + } + } + defs(root, 0); + return res; +}; + +/** + * 116. 填充每个节点的下一个右侧节点指针 + * 117. 填充每个节点的下一个右侧节点指针 II + * @param {Node} root + * @return {Node} + */ + +// 迭代 +var connect = function(root) { + const queue = []; + root && queue.push(root); + while(len = queue.length) { + while(len--) { + const node1 = queue.shift(), + node2 = len ? queue[0] : null; + node1.next = node2; + node1.left && queue.push(node1.left); + node1.right && queue.push(node1.right); + } + } + return root; +}; + +// 递归 +var connect = function(root) { + const res = []; + function defs (root, i) { + if(!root) return; + if(res[i]) res[i].next = root; + res[i] = root; + root.left && defs(root.left, i + 1); + root.right && defs(root.right, i + 1); + } + defs(root, 0); + return root; +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md index ccecea43..463b55d9 100644 --- a/problems/0104.二叉树的最大深度.md +++ b/problems/0104.二叉树的最大深度.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

看完本篇可以一起做了如下两道题目: @@ -193,40 +193,6 @@ public: }; ``` -使用栈来模拟后序遍历依然可以 - -```C++ -class Solution { -public: - int maxDepth(TreeNode* root) { - stack st; - if (root != NULL) st.push(root); - int depth = 0; - int result = 0; - while (!st.empty()) { - TreeNode* node = st.top(); - if (node != NULL) { - st.pop(); - st.push(node); // 中 - st.push(NULL); - depth++; - if (node->right) st.push(node->right); // 右 - if (node->left) st.push(node->left); // 左 - - } else { - st.pop(); - node = st.top(); - st.pop(); - depth--; - } - result = result > depth ? result : depth; - } - return result; - - } -}; -``` - ## 其他语言版本 @@ -281,9 +247,163 @@ class Solution { Python: +104.二叉树的最大深度 +> 递归法: +```python +class Solution: + def maxDepth(self, root: TreeNode) -> int: + return self.getDepth(root) + + def getDepth(self, node): + if not node: + return 0 + leftDepth = self.getDepth(node.left) #左 + rightDepth = self.getDepth(node.right) #右 + depth = 1 + max(leftDepth, rightDepth) #中 + return depth +``` +> 递归法;精简代码 +```python +class Solution: + def maxDepth(self, root: TreeNode) -> int: + if not root: + return 0 + return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right)) +``` + +> 迭代法: +```python +import collections +class Solution: + def maxDepth(self, root: TreeNode) -> int: + if not root: + return 0 + depth = 0 #记录深度 + queue = collections.deque() + queue.append(root) + while queue: + size = len(queue) + depth += 1 + for i in range(size): + node = queue.popleft() + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + return depth +``` + +559.N叉树的最大深度 +> 递归法: +```python +class Solution: + def maxDepth(self, root: 'Node') -> int: + if not root: + return 0 + depth = 0 + for i in range(len(root.children)): + depth = max(depth, self.maxDepth(root.children[i])) + return depth + 1 +``` + +> 迭代法: +```python +import collections +class Solution: + def maxDepth(self, root: 'Node') -> int: + queue = collections.deque() + if root: + queue.append(root) + depth = 0 #记录深度 + while queue: + size = len(queue) + depth += 1 + for i in range(size): + node = queue.popleft() + for j in range(len(node.children)): + if node.children[j]: + queue.append(node.children[j]) + return depth +``` + +> 使用栈来模拟后序遍历依然可以 +```python +class Solution: + def maxDepth(self, root: 'Node') -> int: + st = [] + if root: + st.append(root) + depth = 0 + result = 0 + while st: + node = st.pop() + if node != None: + st.append(node) #中 + st.append(None) + depth += 1 + for i in range(len(node.children)): #处理孩子 + if node.children[i]: + st.append(node.children[i]) + + else: + node = st.pop() + depth -= 1 + result = max(result, depth) + return result +``` + Go: +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func max (a, b int) int { + if a > b { + return a; + } + return b; +} +// 递归 +func maxDepth(root *TreeNode) int { + if root == nil { + return 0; + } + return max(maxDepth(root.Left), maxDepth(root.Right)) + 1; +} + +// 遍历 +func maxDepth(root *TreeNode) int { + levl := 0; + queue := make([]*TreeNode, 0); + if root != nil { + queue = append(queue, root); + } + for l := len(queue); l > 0; { + for ;l > 0;l-- { + node := queue[0]; + if node.Left != nil { + queue = append(queue, node.Left); + } + if node.Right != nil { + queue = append(queue, node.Right); + } + queue = queue[1:]; + } + levl++; + l = len(queue); + } + return levl; +} + +``` + JavaScript ```javascript @@ -292,9 +412,47 @@ var maxDepth = function(root) { return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)) }; ``` +二叉树最大深度递归遍历 +```javascript +var maxDepth = function(root) { + //使用递归的方法 递归三部曲 + //1. 确定递归函数的参数和返回值 + const getDepth=function(node){ + //2. 确定终止条件 + if(node===null){ + return 0; + } + //3. 确定单层逻辑 + let leftDepth=getDepth(node.left); + let rightDepth=getDepth(node.right); + let depth=1+Math.max(leftDepth,rightDepth); + return depth; + } + return getDepth(root); +}; +``` +二叉树最大深度层级遍历 +```javascript +var maxDepth = function(root) { + //使用递归的方法 递归三部曲 + //1. 确定递归函数的参数和返回值 + const getDepth=function(node){ + //2. 确定终止条件 + if(node===null){ + return 0; + } + //3. 确定单层逻辑 + let leftDepth=getDepth(node.left); + let rightDepth=getDepth(node.right); + let depth=1+Math.max(leftDepth,rightDepth); + return depth; + } + return getDepth(root); +}; +``` ----------------------- * 作者微信:[程序员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/0106.从中序与后序遍历序列构造二叉树.md b/problems/0106.从中序与后序遍历序列构造二叉树.md index 4cc2a88a..ba2d46a1 100644 --- a/problems/0106.从中序与后序遍历序列构造二叉树.md +++ b/problems/0106.从中序与后序遍历序列构造二叉树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

看完本文,可以一起解决如下两道题目 @@ -580,8 +580,10 @@ tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。 ## 其他语言版本 - Java: + +106.从中序与后序遍历序列构造二叉树 + ```java class Solution { public TreeNode buildTree(int[] inorder, int[] postorder) { @@ -617,9 +619,79 @@ class Solution { } ``` +105.从前序与中序遍历序列构造二叉树 + +```java +class Solution { + public TreeNode buildTree(int[] preorder, int[] inorder) { + return helper(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1); + } + + public TreeNode helper(int[] preorder, int preLeft, int preRight, + int[] inorder, int inLeft, int inRight) { + // 递归终止条件 + if (inLeft > inRight || preLeft > preRight) return null; + + // val 为前序遍历第一个的值,也即是根节点的值 + // idx 为根据根节点的值来找中序遍历的下标 + int idx = inLeft, val = preorder[preLeft]; + TreeNode root = new TreeNode(val); + for (int i = inLeft; i <= inRight; i++) { + if (inorder[i] == val) { + idx = i; + break; + } + } + + // 根据 idx 来递归找左右子树 + root.left = helper(preorder, preLeft + 1, preLeft + (idx - inLeft), + inorder, inLeft, idx - 1); + root.right = helper(preorder, preLeft + (idx - inLeft) + 1, preRight, + inorder, idx + 1, inRight); + return root; + } +} +``` + Python: +105.从前序与中序遍历序列构造二叉树 +```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 buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: + if not preorder: return None //特殊情况 + root = TreeNode(preorder[0]) //新建父节点 + p=inorder.index(preorder[0]) //找到父节点在中序遍历的位置(因为没有重复的元素,才可以这样找) + root.left = self.buildTree(preorder[1:p+1],inorder[:p]) //注意左节点时分割中序数组和前续数组的开闭环 + root.right = self.buildTree(preorder[p+1:],inorder[p+1:]) //分割中序数组和前续数组 + return root +``` +106.从中序与后序遍历序列构造二叉树 +```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 buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: + if not postorder: return None //特殊情况 + root = TreeNode(postorder[-1]) //新建父节点 + p=inorder.index(postorder[-1]) //找到父节点在中序遍历的位置*因为没有重复的元素,才可以这样找 + root.left = self.buildTree(inorder[:p],postorder[:p]) //分割中序数组和后续数组 + root.right = self.buildTree(inorder[p+1:],postorder[p:-1]) //注意右节点时分割中序数组和后续数组的开闭环 + return root +``` Go: @@ -643,4 +715,4 @@ var buildTree = function(inorder, postorder) { * 作者微信:[程序员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/0108.将有序数组转换为二叉搜索树.md b/problems/0108.将有序数组转换为二叉搜索树.md index 93dc5fd6..2692be47 100644 --- a/problems/0108.将有序数组转换为二叉搜索树.md +++ b/problems/0108.将有序数组转换为二叉搜索树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 构造二叉搜索树,一不小心就平衡了 @@ -233,7 +233,27 @@ class Solution { ``` 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 sortedArrayToBST(self, nums: List[int]) -> TreeNode: + def buildaTree(left,right): + if left > right: return None #左闭右闭的区间,当区间 left > right的时候,就是空节点,当left = right的时候,不为空 + mid = left + (right - left) // 2 #保证数据不会越界 + val = nums[mid] + root = TreeNode(val) + root.left = buildaTree(left,mid - 1) + root.right = buildaTree(mid + 1,right) + return root + root = buildaTree(0,len(nums) - 1) #左闭右闭区间 + return root +``` Go: @@ -244,4 +264,4 @@ Go: * 作者微信:[程序员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/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md index da71ccb2..b9d01503 100644 --- a/problems/0110.平衡二叉树.md +++ b/problems/0110.平衡二叉树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 求高度还是求深度,你搞懂了不? @@ -142,7 +142,7 @@ int getDepth(TreeNode* node) 2. 明确终止条件 -递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的书高度为0 +递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0 代码如下: @@ -498,6 +498,62 @@ class Solution { Python: +> 递归法: +```python +class Solution: + def isBalanced(self, root: TreeNode) -> bool: + return True if self.getDepth(root) != -1 else False + + #返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1 + def getDepth(self, node): + if not node: + return 0 + leftDepth = self.getDepth(node.left) + if leftDepth == -1: return -1 #说明左子树已经不是二叉平衡树 + rightDepth = self.getDepth(node.right) + if rightDepth == -1: return -1 #说明右子树已经不是二叉平衡树 + return -1 if abs(leftDepth - rightDepth)>1 else 1 + max(leftDepth, rightDepth) +``` + +> 迭代法: +```python +class Solution: + def isBalanced(self, root: TreeNode) -> bool: + st = [] + if not root: + return True + st.append(root) + while st: + node = st.pop() #中 + if abs(self.getDepth(node.left) - self.getDepth(node.right)) > 1: + return False + if node.right: + st.append(node.right) #右(空节点不入栈) + if node.left: + st.append(node.left) #左(空节点不入栈) + return True + + def getDepth(self, cur): + st = [] + if cur: + st.append(cur) + depth = 0 + result = 0 + while st: + node = st.pop() + if node: + st.append(node) #中 + st.append(None) + depth += 1 + if node.right: st.append(node.right) #右 + if node.left: st.append(node.left) #左 + else: + node = st.pop() + depth -= 1 + result = max(result, depth) + return result +``` + Go: ```Go @@ -534,7 +590,34 @@ func abs(a int)int{ return a } ``` - +JavaScript: +```javascript +var isBalanced = function(root) { + //还是用递归三部曲 + 后序遍历 左右中 当前左子树右子树高度相差大于1就返回-1 + // 1. 确定递归函数参数以及返回值 + const getDepth=function(node){ + // 2. 确定递归函数终止条件 + if(node===null){ + return 0; + } + // 3. 确定单层递归逻辑 + let leftDepth=getDepth(node.left);//左子树高度 + if(leftDepth===-1){ + return -1; + } + let rightDepth=getDepth(node.right);//右子树高度 + if(rightDepth===-1){ + return -1; + } + if(Math.abs(leftDepth-rightDepth)>1){ + return -1; + }else{ + return 1+Math.max(leftDepth,rightDepth); + } + } + return getDepth(root)===-1?false:true; +}; +``` ----------------------- diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index 01b6c89c..48795722 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 和求最大深度一个套路? @@ -301,6 +301,111 @@ class Solution: Go: +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func min(a, b int) int { + if a < b { + return a; + } + return b; +} +// 递归 +func minDepth(root *TreeNode) int { + if root == nil { + return 0; + } + if root.Left == nil && root.Right != nil { + return 1 + minDepth(root.Right); + } + if root.Right == nil && root.Left != nil { + return 1 + minDepth(root.Left); + } + return min(minDepth(root.Left), minDepth(root.Right)) + 1; +} + +// 迭代 + +func minDepth(root *TreeNode) int { + dep := 0; + queue := make([]*TreeNode, 0); + if root != nil { + queue = append(queue, root); + } + for l := len(queue); l > 0; { + dep++; + for ; l > 0; l-- { + node := queue[0]; + if node.Left == nil && node.Right == nil { + return dep; + } + if node.Left != nil { + queue = append(queue, node.Left); + } + if node.Right != nil { + queue = append(queue, node.Right); + } + queue = queue[1:]; + } + l = len(queue); + } + return dep; +} +``` + + +JavaScript: + +递归法: + +```javascript +/** + * @param {TreeNode} root + * @return {number} + */ +var minDepth1 = function(root) { + if(!root) return 0; + // 到叶子节点 返回 1 + if(!root.left && !root.right) return 1; + // 只有右节点时 递归右节点 + if(!root.left) return 1 + minDepth(root.right);、 + // 只有左节点时 递归左节点 + if(!root.right) return 1 + minDepth(root.left); + return Math.min(minDepth(root.left), minDepth(root.right)) + 1; +}; +``` + +迭代法: + +```javascript +/** +* @param {TreeNode} root +* @return {number} +*/ +var minDepth = function(root) { + if(!root) return 0; + const queue = [root]; + let dep = 0; + while(true) { + let size = queue.length; + dep++; + while(size--){ + const node = queue.shift(); + // 到第一个叶子节点 返回 当前深度 + if(!node.left && !node.right) return dep; + node.left && queue.push(node.left); + node.right && queue.push(node.right); + } + } +}; +``` + diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index 1b75113e..54f79d1d 100644 --- a/problems/0112.路径总和.md +++ b/problems/0112.路径总和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 递归函数什么时候需要返回值 @@ -332,13 +332,246 @@ class Solution { } } +// LC112 简洁方法 +class Solution { + public boolean hasPathSum(TreeNode root, int targetSum) { + + if (root == null) return false; // 为空退出 + + // 叶子节点判断是否符合 + if (root.left == null && root.right == null) return root.val == targetSum; + + // 求两侧分支的路径和 + return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); + } +} +``` +迭代 +```java +class Solution { + public boolean hasPathSum(TreeNode root, int targetSum) { + if(root==null)return false; + Stack stack1 = new Stack<>(); + Stack stack2 = new Stack<>(); + stack1.push(root);stack2.push(root.val); + while(!stack1.isEmpty()){ + int size = stack1.size(); + for(int i=0;i> pathSum(TreeNode root, int targetSum) { + List> res = new ArrayList<>(); + if (root == null) return res; // 非空判断 + + List path = new LinkedList<>(); + preorderDFS(root, targetSum, res, path); + return res; + } + + public void preorderDFS(TreeNode root, int targetSum, List> res, List path) { + path.add(root.val); + // 遇到了叶子节点 + if (root.left == null && root.right == null) { + // 找到了和为 targetSum 的路径 + if (targetSum - root.val == 0) { + res.add(new ArrayList<>(path)); + } + return; // 如果和不为 targetSum,返回 + } + + if (root.left != null) { + preorderDFS(root.left, targetSum - root.val, res, path); + path.remove(path.size() - 1); // 回溯 + } + if (root.right != null) { + preorderDFS(root.right, targetSum - root.val, res, path); + path.remove(path.size() - 1); // 回溯 + } + } +} ``` Python: +0112.路径总和 +```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 hasPathSum(self, root: TreeNode, targetSum: int) -> bool: + def isornot(root,targetSum)->bool: + if (not root.left) and (not root.right) and targetSum == 0:return True // 遇到叶子节点,并且计数为0 + if (not root.left) and (not root.right):return False //遇到叶子节点,计数不为0 + if root.left: + targetSum -= root.left.val //左节点 + if isornot(root.left,targetSum):return True //递归,处理左节点 + targetSum += root.left.val //回溯 + if root.right: + targetSum -= root.right.val //右节点 + if isornot(root.right,targetSum):return True //递归,处理右节点 + targetSum += root.right.val //回溯 + return False + + if root == None:return False //别忘记处理空TreeNode + else:return isornot(root,targetSum-root.val) +``` + +0113.路径总和-ii +```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 pathSum(self, root: TreeNode, targetSum: int) -> List[List[int]]: + path=[] + res=[] + def pathes(root,targetSum): + if (not root.left) and (not root.right) and targetSum == 0: // 遇到叶子节点,并且计数为0 + res.append(path[:]) //找到一种路径,记录到res中,注意必须是path[:]而不是path + return + if (not root.left) and (not root.right):return // 遇到叶子节点直接返回 + if root.left: //左 + targetSum -= root.left.val + path.append(root.left.val) //递归前记录节点 + pathes(root.left,targetSum) //递归 + targetSum += root.left.val //回溯 + path.pop() //回溯 + if root.right: //右 + targetSum -= root.right.val + path.append(root.right.val) //递归前记录节点 + pathes(root.right,targetSum) //递归 + targetSum += root.right.val //回溯 + path.pop() //回溯 + return + + if root == None:return [] //处理空TreeNode + else: + path.append(root.val) //首先处理根节点 + pathes(root,targetSum-root.val) + return res +``` Go: +> 112. 路径总和 + +```go +//递归法 +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func hasPathSum(root *TreeNode, targetSum int) bool { + var flage bool //找没找到的标志 + if root==nil{ + return flage + } + pathSum(root,0,targetSum,&flage) + return flage +} +func pathSum(root *TreeNode, sum int,targetSum int,flage *bool){ + sum+=root.Val + if root.Left==nil&&root.Right==nil&&sum==targetSum{ + *flage=true + return + } + if root.Left!=nil&&!(*flage){//左节点不为空且还没找到 + pathSum(root.Left,sum,targetSum,flage) + } + if root.Right!=nil&&!(*flage){//右节点不为空且没找到 + pathSum(root.Right,sum,targetSum,flage) + } +} +``` + + + +> 113 递归法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func pathSum(root *TreeNode, targetSum int) [][]int { + var result [][]int//最终结果 + if root==nil{ + return result + } + var sumNodes []int//经过路径的节点集合 + hasPathSum(root,&sumNodes,targetSum,&result) + return result +} +func hasPathSum(root *TreeNode,sumNodes *[]int,targetSum int,result *[][]int){ + *sumNodes=append(*sumNodes,root.Val) + if root.Left==nil&&root.Right==nil{//叶子节点 + fmt.Println(*sumNodes) + var sum int + var number int + for k,v:=range *sumNodes{//求该路径节点的和 + sum+=v + number=k + } + tempNodes:=make([]int,number+1)//新的nodes接受指针里的值,防止最终指针里的值发生变动,导致最后的结果都是最后一个sumNodes的值 + for k,v:=range *sumNodes{ + tempNodes[k]=v + } + if sum==targetSum{ + *result=append(*result,tempNodes) + } + } + if root.Left!=nil{ + hasPathSum(root.Left,sumNodes,targetSum,result) + *sumNodes=(*sumNodes)[:len(*sumNodes)-1]//回溯 + } + if root.Right!=nil{ + hasPathSum(root.Right,sumNodes,targetSum,result) + *sumNodes=(*sumNodes)[:len(*sumNodes)-1]//回溯 + } +} +``` + JavaScript: 0112.路径总和 @@ -407,6 +640,62 @@ let pathSum = function (root, targetSum) { }; ``` +0112 路径总和 +```javascript +var hasPathSum = function(root, targetSum) { + //递归方法 + // 1. 确定函数参数 + const traversal = function(node,count){ + // 2. 确定终止条件 + if(node.left===null&&node.right===null&&count===0){ + return true; + } + if(node.left===null&&node.right===null){ + return false; + } + //3. 单层递归逻辑 + if(node.left){ + if(traversal(node.left,count-node.left.val)){ + return true; + } + } + if(node.right){ + if(traversal(node.right,count-node.right.val)){ + return true; + } + } + return false; + } + if(root===null){ + return false; + } + return traversal(root,targetSum-root.val); +}; +``` +113 路径总和 +```javascript +var pathSum = function(root, targetSum) { + //递归方法 + let resPath = [],curPath = []; + // 1. 确定递归函数参数 + const travelTree = function(node,count){ + curPath.push(node.val); + count-=node.val; + if(node.left===null&&node.right===null&&count===0){ + resPath.push([...curPath]); + } + node.left&&travelTree(node.left,count); + node.right&&travelTree(node.right,count); + let cur = curPath.pop(); + count-=cur; + } + if(root===null){ + return resPath; + } + travelTree(root,targetSum); + return resPath; +}; +``` @@ -414,4 +703,4 @@ let pathSum = function (root, targetSum) { * 作者微信:[程序员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/0115.不同的子序列.md b/problems/0115.不同的子序列.md index d48f598b..e54389aa 100644 --- a/problems/0115.不同的子序列.md +++ b/problems/0115.不同的子序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 115.不同的子序列 @@ -16,7 +16,7 @@ 题目数据保证答案符合 32 位带符号整数范围。 -![115.不同的子序列示例](https://code-thinking.cdn.bcebos.com/pics/115.%E4%B8%8D%E5%90%8C%E7%9A%84%E5%AD%90%E5%BA%8F%E5%88%97%E7%A4%BA%E4%BE%8B.jpg) +![115.不同的子序列示例](https://code-thinking.cdn.bcebos.com/pics/115.不同的子序列示例.jpg) 提示: @@ -148,7 +148,22 @@ Java: Python: - +```python +class Solution: + def numDistinct(self, s: str, t: str) -> int: + dp = [[0] * (len(t)+1) for _ in range(len(s)+1)] + for i in range(len(s)): + dp[i][0] = 1 + for j in range(1, len(t)): + dp[0][j] = 0 + for i in range(1, len(s)+1): + for j in range(1, len(t)+1): + if s[i-1] == t[j-1]: + dp[i][j] = dp[i-1][j-1] + dp[i-1][j] + else: + dp[i][j] = dp[i-1][j] + return dp[-1][-1] +``` Go: diff --git a/problems/0121.买卖股票的最佳时机.md b/problems/0121.买卖股票的最佳时机.md index 3d564892..259fff34 100644 --- a/problems/0121.买卖股票的最佳时机.md +++ b/problems/0121.买卖股票的最佳时机.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 121. 买卖股票的最佳时机 @@ -16,14 +16,14 @@ 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。 -示例 1: -输入:[7,1,5,3,6,4] -输出:5 +示例 1: +输入:[7,1,5,3,6,4] +输出:5 解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。 -示例 2: -输入:prices = [7,6,4,3,1] -输出:0 +示例 2: +输入:prices = [7,6,4,3,1] +输出:0 解释:在这种情况下, 没有交易完成, 所以最大利润为 0。 @@ -33,7 +33,7 @@ 这道题目最直观的想法,就是暴力,找最优间距了。 -``` +```C++ class Solution { public: int maxProfit(vector& prices) { @@ -196,14 +196,122 @@ public: ## 其他语言版本 - Java: +```java +// 贪心思路 +class Solution { + public int maxProfit(int[] prices) { + int minprice = Integer.MAX_VALUE; + int maxprofit = 0; + for (int i = 0; i < prices.length; i++) { + if (prices[i] < minprice) { + minprice = prices[i]; + } else if (prices[i] - minprice > maxprofit) { + maxprofit = prices[i] - minprice; + } + } + return maxprofit; + } +} +``` +``` java +class Solution { // 动态规划解法 + public int maxProfit(int[] prices) { + // 可交易次数 + int k = 1; + // [天数][交易次数][是否持有股票] + int[][][] dp = new int[prices.length][k + 1][2]; + + // bad case + dp[0][0][0] = 0; + dp[0][0][1] = Integer.MIN_VALUE; + dp[0][1][0] = Integer.MIN_VALUE; + dp[0][1][1] = -prices[0]; + + for (int i = 1; i < prices.length; i++) { + for (int j = k; j >= 1; j--) { + // dp公式 + dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); + dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); + } + } + + return dp[prices.length - 1][k][0] > 0 ? dp[prices.length - 1][k][0] : 0; + } +} +``` Python: +> 贪心法: +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + low = float("inf") + result = 0 + for i in range(len(prices)): + low = min(low, prices[i]) #取最左最小价格 + result = max(result, prices[i] - low) #直接取最大区间利润 + return result +``` + +> 动态规划:版本一 +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + length = len(prices) + if len == 0: + return 0 + dp = [[0] * 2 for _ in range(length)] + dp[0][0] = -prices[0] + dp[0][1] = 0 + for i in range(1, length): + dp[i][0] = max(dp[i-1][0], -prices[i]) + dp[i][1] = max(dp[i-1][1], prices[i] + dp[i-1][0]) + return dp[-1][1] +``` + +> 动态规划:版本二 +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + length = len(prices) + dp = [[0] * 2 for _ in range(2)] #注意这里只开辟了一个2 * 2大小的二维数组 + dp[0][0] = -prices[0] + dp[0][1] = 0 + for i in range(1, length): + dp[i % 2][0] = max(dp[(i-1) % 2][0], -prices[i]) + dp[i % 2][1] = max(dp[(i-1) % 2][1], prices[i] + dp[(i-1) % 2][0]) + return dp[(length-1) % 2][1] +``` Go: +```Go +func maxProfit(prices []int) int { + length:=len(prices) + if length==0{return 0} + dp:=make([][]int,length) + for i:=0;ib{ + return a + } + return b +} +``` diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md index 249d44a7..4b878aa0 100644 --- a/problems/0122.买卖股票的最佳时机II.md +++ b/problems/0122.买卖股票的最佳时机II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 122.买卖股票的最佳时机II @@ -133,9 +133,10 @@ public: ## 其他语言版本 - Java: + ```java +// 贪心思路 class Solution { public int maxProfit(int[] prices) { int sum = 0; @@ -153,6 +154,29 @@ class Solution { } ``` +```java +class Solution { // 动态规划 + public int maxProfit(int[] prices) { + // [天数][是否持有股票] + int[][] dp = new int[prices.length][2]; + + // bad case + dp[0][0] = 0; + dp[0][1] = -prices[0]; + + for (int i = 1; i < prices.length; i++) { + // dp公式 + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + } + + return dp[prices.length - 1][0]; + } +} +``` + + + Python: ```python class Solution: @@ -166,10 +190,20 @@ class Solution: Go: - +Javascript: +```Javascript +// 贪心 +var maxProfit = function(prices) { + let result = 0 + for(let i = 1; i < prices.length; i++) { + result += Math.max(prices[i] - prices[i - 1], 0) + } + return result +}; +``` ----------------------- * 作者微信:[程序员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/0122.买卖股票的最佳时机II(动态规划).md b/problems/0122.买卖股票的最佳时机II(动态规划).md index 3444ca73..1215025e 100644 --- a/problems/0122.买卖股票的最佳时机II(动态规划).md +++ b/problems/0122.买卖股票的最佳时机II(动态规划).md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 122.买卖股票的最佳时机II @@ -133,10 +133,71 @@ public: Java: +```java +// 动态规划 +class Solution + // 实现1:二维数组存储 + // 可以将每天持有与否的情况分别用 dp[i][0] 和 dp[i][1] 来进行存储 + // 时间复杂度:O(n),空间复杂度O(n) + public int maxProfit(int[] prices) { + int n = prices.length; + int[][] dp = new int[n][2]; // 创建二维数组存储状态 + dp[0][0] = 0; // 初始状态 + dp[0][1] = -prices[0]; + for (int i = 1; i < n; ++i) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); // 第 i 天,没有股票 + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); // 第 i 天,持有股票 + } + return dp[n - 1][0]; // 卖出股票收益高于持有股票收益,因此取[0] + } + // 实现2:变量存储 + // 第一种方法需要用二维数组存储,有空间开销,其实关心的仅仅是前一天的状态,不关注更多的历史信息 + // 因此,可以仅保存前一天的信息存入 dp0、dp1 这 2 个变量即可 + // 时间复杂度:O(n),空间复杂度O(1) + public int maxProfit(int[] prices) { + int n = prices.length; + int dp0 = 0, dp1 = -prices[0]; // 定义变量,存储初始状态 + for (int i = 1; i < n; ++i) { + int newDp0 = Math.max(dp0, dp1 + prices[i]); // 第 i 天,没有股票 + int newDp1 = Math.max(dp1, dp0 - prices[i]); // 第 i 天,持有股票 + dp0 = newDp0; + dp1 = newDp1; + } + return dp0; + } +} +``` Python: +> 版本一: +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + length = len(prices) + dp = [[0] * 2 for _ in range(length)] + dp[0][0] = -prices[0] + dp[0][1] = 0 + for i in range(1, length): + dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]) #注意这里是和121. 买卖股票的最佳时机唯一不同的地方 + dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i]) + return dp[-1][1] +``` + +> 版本二: +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + length = len(prices) + dp = [[0] * 2 for _ in range(2)] #注意这里只开辟了一个2 * 2大小的二维数组 + dp[0][0] = -prices[0] + dp[0][1] = 0 + for i in range(1, length): + dp[i % 2][0] = max(dp[(i-1) % 2][0], dp[(i-1) % 2][1] - prices[i]) + dp[i % 2][1] = max(dp[(i-1) % 2][1], dp[(i-1) % 2][0] + prices[i]) + return dp[(length-1) % 2][1] +``` Go: diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md index 0e718cf1..fccb187d 100644 --- a/problems/0123.买卖股票的最佳时机III.md +++ b/problems/0123.买卖股票的最佳时机III.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 123.买卖股票的最佳时机III @@ -101,9 +101,9 @@ dp[i][4] = max(dp[i - 1][4], dp[i - 1][3] + prices[i]); 所以dp[0][2] = 0; -第0天第二次买入操作,初始值应该是多少呢? +第0天第二次买入操作,初始值应该是多少呢?应该不少同学疑惑,第一次还没买入呢,怎么初始化第二次买入呢? -不用管第几次,现在手头上没有现金,只要买入,现金就做相应的减少。 +第二次买入依赖于第一次卖出的状态,其实相当于第0天第一次买入了,第一次卖出了,然后在买入一次(第二次买入),那么现在手头上没有现金,只要买入,现金就做相应的减少。 所以第二次买入操作,初始化为:dp[0][3] = -prices[0]; @@ -190,12 +190,79 @@ dp[1] = max(dp[1], dp[0] - prices[i]); 如果dp[1]取dp[1],即保持买入股 ## 其他语言版本 - Java: +```java +class Solution { // 动态规划 + public int maxProfit(int[] prices) { + // 可交易次数 + int k = 2; + + // [天数][交易次数][是否持有股票] + int[][][] dp = new int[prices.length][k + 1][2]; + + // badcase + dp[0][0][0] = 0; + dp[0][0][1] = Integer.MIN_VALUE; + dp[0][1][0] = 0; + dp[0][1][1] = -prices[0]; + dp[0][2][0] = 0; + dp[0][2][1] = Integer.MIN_VALUE; + + for (int i = 1; i < prices.length; i++) { + for (int j = 2; j >= 1; j--) { + // dp公式 + dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); + dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); + } + } + + int res = 0; + for (int i = 1; i < 3; i++) { + res = Math.max(res, dp[prices.length - 1][i][0]); + } + return res; + } +} +``` + Python: +> 版本一: +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + if len(prices) == 0: + return 0 + dp = [[0] * 5 for _ in range(len(prices))] + dp[0][1] = -prices[0] + dp[0][3] = -prices[0] + for i in range(1, len(prices)): + dp[i][0] = dp[i-1][0] + dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i]) + dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i]) + dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i]) + dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i]) + return dp[-1][4] +``` + +> 版本二: +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + if len(prices) == 0: + return 0 + dp = [0] * 5 + dp[1] = -prices[0] + dp[3] = -prices[0] + for i in range(1, len(prices)): + dp[1] = max(dp[1], dp[0] - prices[i]) + dp[2] = max(dp[2], dp[1] + prices[i]) + dp[3] = max(dp[3], dp[2] - prices[i]) + dp[4] = max(dp[4], dp[3] + prices[i]) + return dp[4] +``` Go: diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md index 9c86a3bc..9d23fd13 100644 --- a/problems/0131.分割回文串.md +++ b/problems/0131.分割回文串.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 切割问题其实是一种组合问题! @@ -292,11 +292,59 @@ class Solution { ``` Python: - +```py +class Solution: + def partition(self, s: str) -> List[List[str]]: + res = [] + path = [] #放已经回文的子串 + def backtrack(s,startIndex): + if startIndex >= len(s): #如果起始位置已经大于s的大小,说明已经找到了一组分割方案了 + return res.append(path[:]) + for i in range(startIndex,len(s)): + p = s[startIndex:i+1] #获取[startIndex,i+1]在s中的子串 + if p == p[::-1]: path.append(p) #是回文子串 + else: continue #不是回文,跳过 + backtrack(s,i+1) #寻找i+1为起始位置的子串 + path.pop() #回溯过程,弹出本次已经填在path的子串 + backtrack(s,0) + return res + +``` Go: +javaScript: +```js +/** + * @param {string} s + * @return {string[][]} + */ +const isPalindrome = (s, l, r) => { + for (let i = l, j = r; i < j; i++, j--) { + if(s[i] !== s[j]) return false; + } + return true; +} + +var partition = function(s) { + const res = [], path = [], len = s.length; + backtracking(0); + return res; + function backtracking(i) { + if(i >= len) { + res.push(Array.from(path)); + return; + } + for(let j = i; j < len; j++) { + if(!isPalindrome(s, i, j)) continue; + path.push(s.substr(i, j - i + 1)); + backtracking(j + 1); + path.pop(); + } + } +}; +``` ----------------------- diff --git a/problems/0134.加油站.md b/problems/0134.加油站.md index 393e4627..dfed2d96 100644 --- a/problems/0134.加油站.md +++ b/problems/0134.加油站.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 134. 加油站 @@ -223,15 +223,50 @@ class Solution { ``` Python: - +```python +class Solution: + def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int: + start = 0 + curSum = 0 + totalSum = 0 + for i in range(len(gas)): + curSum += gas[i] - cost[i] + totalSum += gas[i] - cost[i] + if curSum < 0: + curSum = 0 + start = i + 1 + if totalSum < 0: return -1 + return start +``` Go: +Javascript: +```Javascript +var canCompleteCircuit = function(gas, cost) { + const gasLen = gas.length + let start = 0 + let curSum = 0 + let totalSum = 0 + for(let i = 0; i < gasLen; i++) { + curSum += gas[i] - cost[i] + totalSum += gas[i] - cost[i] + if(curSum < 0) { + curSum = 0 + start = i + 1 + } + } + + if(totalSum < 0) return -1 + + return start +}; +``` ----------------------- * 作者微信:[程序员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/0135.分发糖果.md b/problems/0135.分发糖果.md index fedf8765..fd791277 100644 --- a/problems/0135.分发糖果.md +++ b/problems/0135.分发糖果.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 135. 分发糖果 @@ -161,15 +161,49 @@ class Solution { ``` Python: - +```python +class Solution: + def candy(self, ratings: List[int]) -> int: + candyVec = [1] * len(ratings) + for i in range(1, len(ratings)): + if ratings[i] > ratings[i - 1]: + candyVec[i] = candyVec[i - 1] + 1 + for j in range(len(ratings) - 2, -1, -1): + if ratings[j] > ratings[j + 1]: + candyVec[j] = max(candyVec[j], candyVec[j + 1] + 1) + return sum(candyVec) +``` Go: +Javascript: +```Javascript +var candy = function(ratings) { + let candys = new Array(ratings.length).fill(1) + for(let i = 1; i < ratings.length; i++) { + if(ratings[i] > ratings[i - 1]) { + candys[i] = candys[i - 1] + 1 + } + } + + for(let i = ratings.length - 2; i >= 0; i--) { + if(ratings[i] > ratings[i + 1]) { + candys[i] = Math.max(candys[i], candys[i + 1] + 1) + } + } + + let count = candys.reduce((a, b) => { + return a + b + }) + + return count +}; +``` ----------------------- * 作者微信:[程序员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/0139.单词拆分.md b/problems/0139.单词拆分.md index c6d8e43b..b6a6242e 100644 --- a/problems/0139.单词拆分.md +++ b/problems/0139.单词拆分.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:单词拆分 ## 139.单词拆分 @@ -252,9 +252,44 @@ class Solution { Python: +```python3 +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + '''排列''' + dp = [False]*(len(s) + 1) + dp[0] = True + # 遍历背包 + for j in range(1, len(s) + 1): + # 遍历单词 + for word in wordDict: + if j >= len(word): + dp[j] = dp[j] or (dp[j - len(word)] and word == s[j - len(word):j]) + return dp[len(s)] +``` + + + Go: - +```Go +func wordBreak(s string,wordDict []string) bool { + wordDictSet:=make(map[string]bool) + for _,w:=range wordDict{ + wordDictSet[w]=true + } + dp:=make([]bool,len(s)+1) + dp[0]=true + for i:=1;i<=len(s);i++{ + for j:=0;j \ No newline at end of file +
diff --git a/problems/0142.环形链表II.md b/problems/0142.环形链表II.md index 7556b854..9deb1e0c 100644 --- a/problems/0142.环形链表II.md +++ b/problems/0142.环形链表II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -234,6 +234,67 @@ class Solution: ``` Go: +```go +func detectCycle(head *ListNode) *ListNode { + slow, fast := head, head + for fast != nil && fast.Next != nil { + slow = slow.Next + fast = fast.Next.Next + if slow == fast { + for slow != head { + slow = slow.Next + head = head.Next + } + return head + } + } + return nil +} +``` + +javaScript + +```js +// 两种循环实现方式 +/** + * @param {ListNode} head + * @return {ListNode} + */ +// 先判断是否是环形链表 +var detectCycle = function(head) { + if(!head || !head.next) return null; + let slow =head.next, fast = head.next.next; + while(fast && fast.next && fast!== slow) { + slow = slow.next; + fast = fast.next.next; + } + if(!fast || !fast.next ) return null; + slow = head; + while (fast !== slow) { + slow = slow.next; + fast = fast.next; + } + return slow; +}; + +var detectCycle = function(head) { + if(!head || !head.next) return null; + let slow =head.next, fast = head.next.next; + while(fast && fast.next) { + slow = slow.next; + fast = fast.next.next; + if(fast == slow) { + slow = head; + while (fast !== slow) { + slow = slow.next; + fast = fast.next; + } + return slow; + } + } + return null; +}; +``` ----------------------- diff --git a/problems/0150.逆波兰表达式求值.md b/problems/0150.逆波兰表达式求值.md index 4e7365f7..c8b0da08 100644 --- a/problems/0150.逆波兰表达式求值.md +++ b/problems/0150.逆波兰表达式求值.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -170,8 +170,75 @@ public class EvalRPN { } ``` +Go: +```Go +func evalRPN(tokens []string) int { + stack := []int{} + for _, token := range tokens { + val, err := strconv.Atoi(token) + if err == nil { + stack = append(stack, val) + } else { + num1, num2 := stack[len(stack)-2], stack[(len(stack))-1] + stack = stack[:len(stack)-2] + switch token { + case "+": + stack = append(stack, num1+num2) + case "-": + stack = append(stack, num1-num2) + case "*": + stack = append(stack, num1*num2) + case "/": + stack = append(stack, num1/num2) + } + } + } + return stack[0] +} +``` +javaScript: +```js + +/** + * @param {string[]} tokens + * @return {number} + */ +var evalRPN = function(tokens) { + const s = new Map([ + ["+", (a, b) => a * 1 + b * 1], + ["-", (a, b) => b - a], + ["*", (a, b) => b * a], + ["/", (a, b) => (b / a) | 0] + ]); + const stack = []; + for (const i of tokens) { + if(!s.has(i)) { + stack.push(i); + continue; + } + stack.push(s.get(i)(stack.pop(),stack.pop())) + } + return stack.pop(); +}; +``` + +python3 + +```python +def evalRPN(tokens) -> int: + stack = list() + for i in range(len(tokens)): + if tokens[i] not in ["+", "-", "*", "/"]: + stack.append(tokens[i]) + else: + tmp1 = stack.pop() + tmp2 = stack.pop() + res = eval(tmp2+tokens[i]+tmp1) + stack.append(str(int(res))) + return stack[-1] +``` ----------------------- diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index 1c567f84..d76734e4 100644 --- a/problems/0151.翻转字符串里的单词.md +++ b/problems/0151.翻转字符串里的单词.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -183,7 +183,7 @@ public: int end = 0; // 反转的单词在字符串里终止位置 bool entry = false; // 标记枚举字符串的过程中是否已经进入了单词区间 for (int i = 0; i < s.size(); i++) { // 开始反转单词 - if ((!entry) || (s[i] != ' ' && s[i - 1] == ' ')) { + if (!entry) { start = i; // 确定单词起始位置 entry = true; // 进入单词区间 } @@ -318,9 +318,61 @@ class Solution { Python: - Go: +```go +import ( + "fmt" +) + +func reverseWords(s string) string { + //1.使用双指针删除冗余的空格 + slowIndex, fastIndex := 0, 0 + b := []byte(s) + //删除头部冗余空格 + for len(b) > 0 && fastIndex < len(b) && b[fastIndex] == ' ' { + fastIndex++ + } + //删除单词间冗余空格 + for ; fastIndex < len(b); fastIndex++ { + if fastIndex-1 > 0 && b[fastIndex-1] == b[fastIndex] && b[fastIndex] == ' ' { + continue + } + b[slowIndex] = b[fastIndex] + slowIndex++ + } + //删除尾部冗余空格 + if slowIndex-1 > 0 && b[slowIndex-1] == ' ' { + b = b[:slowIndex-1] + } else { + b = b[:slowIndex] + } + //2.反转整个字符串 + reverse(&b, 0, len(b)-1) + //3.反转单个单词 i单词开始位置,j单词结束位置 + i := 0 + for i < len(b) { + j := i + for ; j < len(b) && b[j] != ' '; j++ { + } + reverse(&b, i, j-1) + i = j + i++ + } + return string(b) +} + +func reverse(b *[]byte, left, right int) { + for left < right { + (*b)[left], (*b)[right] = (*b)[right], (*b)[left] + left++ + right-- + } +} +``` + + + diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md index 11c5805b..431c292b 100644 --- a/problems/0188.买卖股票的最佳时机IV.md +++ b/problems/0188.买卖股票的最佳时机IV.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 188.买卖股票的最佳时机IV @@ -25,7 +25,7 @@ 输入:k = 2, prices = [3,2,6,5,0,3] 输出:7 解释:在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4。随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。 -  + 提示: @@ -167,12 +167,65 @@ public: ## 其他语言版本 - Java: +```java +class Solution { //动态规划 + public int maxProfit(int k, int[] prices) { + if (prices == null || prices.length < 2 || k == 0) { + return 0; + } + + // [天数][交易次数][是否持有股票] + int[][][] dp = new int[prices.length][k + 1][2]; + + // bad case + dp[0][0][0] = 0; + dp[0][0][1] = Integer.MIN_VALUE; + dp[0][1][0] = 0; + dp[0][1][1] = -prices[0]; + // dp[0][j][0] 都均为0 + // dp[0][j][1] 异常值都取Integer.MIN_VALUE; + for (int i = 2; i < k + 1; i++) { + dp[0][i][0] = 0; + dp[0][i][1] = Integer.MIN_VALUE; + } + + for (int i = 1; i < prices.length; i++) { + for (int j = k; j >= 1; j--) { + // dp公式 + dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]); + dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]); + } + } + + int res = 0; + for (int i = 1; i < k + 1; i++) { + res = Math.max(res, dp[prices.length - 1][i][0]); + } + + return res; + } +} +``` + Python: +```python +class Solution: + def maxProfit(self, k: int, prices: List[int]) -> int: + if len(prices) == 0: + return 0 + dp = [[0] * (2*k+1) for _ in range(len(prices))] + for j in range(1, 2*k, 2): + dp[0][j] = -prices[0] + for i in range(1, len(prices)): + for j in range(0, 2*k-1, 2): + dp[i][j+1] = max(dp[i-1][j+1], dp[i-1][j] - prices[i]) + dp[i][j+2] = max(dp[i-1][j+2], dp[i-1][j+1] + prices[i]) + return dp[-1][2*k] +``` Go: diff --git a/problems/0198.打家劫舍.md b/problems/0198.打家劫舍.md index c64648ad..63a68c36 100644 --- a/problems/0198.打家劫舍.md +++ b/problems/0198.打家劫舍.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 198.打家劫舍 @@ -111,10 +111,40 @@ public: Java: +```Java +// 动态规划 +class Solution { + public int rob(int[] nums) { + if (nums == null || nums.length == 0) return 0; + if (nums.length == 1) return nums[0]; + int[] dp = new int[nums.length]; + dp[0] = nums[0]; + dp[1] = Math.max(dp[0], nums[1]); + for (int i = 2; i < nums.length; i++) { + dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]); + } + + return dp[nums.length - 1]; + } +} +``` Python: - +```python +class Solution: + def rob(self, nums: List[int]) -> int: + if len(nums) == 0: + return 0 + if len(nums) == 1: + return nums[0] + dp = [0] * len(nums) + dp[0] = nums[0] + dp[1] = max(nums[0], nums[1]) + for i in range(2, len(nums)): + dp[i] = max(dp[i-2]+nums[i], dp[i-1]) + return dp[-1] +``` Go: ```Go diff --git a/problems/0202.快乐数.md b/problems/0202.快乐数.md index 8c0dd1e7..d1bccd64 100644 --- a/problems/0202.快乐数.md +++ b/problems/0202.快乐数.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -22,13 +22,13 @@ https://leetcode-cn.com/problems/happy-number/ **示例:** -输入:19 -输出:true -解释: -1^2 + 9^2 = 82 -8^2 + 2^2 = 68 -6^2 + 8^2 = 100 -1^2 + 0^2 + 0^2 = 1 +输入:19 +输出:true +解释: +1^2 + 9^2 = 82 +8^2 + 2^2 = 68 +6^2 + 8^2 = 100 +1^2 + 0^2 + 0^2 = 1 # 思路 @@ -36,7 +36,7 @@ https://leetcode-cn.com/problems/happy-number/ 题目中说了会 **无限循环**,那么也就是说**求和的过程中,sum会重复出现,这对解题很重要!** -正如:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。** +正如:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中所说,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。** 所以这道题目使用哈希法,来判断这个sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。 @@ -80,7 +80,7 @@ public: -## 其他语言版本 +# 其他语言版本 Java: @@ -108,10 +108,84 @@ class Solution { ``` Python: - +```python +class Solution: + def isHappy(self, n: int) -> bool: + set_ = set() + while 1: + sum_ = self.getSum(n) + if sum_ == 1: + return True + #如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false + if sum_ in set_: + return False + else: + set_.add(sum_) + n = sum_ + + #取数值各个位上的单数之和 + def getSum(self, n): + sum_ = 0 + while n > 0: + sum_ += (n%10) * (n%10) + n //= 10 + return sum_ +``` Go: +```go +func isHappy(n int) bool { + m := make(map[int]bool) + for n != 1 && !m[n] { + n, m[n] = getSum(n), true + } + return n == 1 +} +func getSum(n int) int { + sum := 0 + for n > 0 { + sum += (n % 10) * (n % 10) + n = n / 10 + } + return sum +} +``` + +javaScript: + +```js +function getN(n) { + if (n == 1 || n == 0) return n; + let res = 0; + while (n) { + res += (n % 10) * (n % 10); + n = parseInt(n / 10); + } + return res; +} + +var isHappy = function(n) { + const sumSet = new Set(); + while (n != 1 && !sumSet.has(n)) { + sumSet.add(n); + n = getN(n); + } + return n == 1; +}; + +// 使用环形链表的思想 说明出现闭环 退出循环 +var isHappy = function(n) { + if (getN(n) == 1) return true; + let a = getN(n), b = getN(getN(n)); + // 如果 a === b + while (b !== 1 && getN(b) !== 1 && a !== b) { + a = getN(a); + b = getN(getN(b)); + } + return b === 1 || getN(b) === 1 ; +}; +``` diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md index 9fca1ee0..cac9f233 100644 --- a/problems/0203.移除链表元素.md +++ b/problems/0203.移除链表元素.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 链表操作中,可以使用原链表来直接进行删除操作,也可以设置一个虚拟头结点在进行删除操作,接下来看一看哪种方式更方便。 @@ -15,7 +15,18 @@ https://leetcode-cn.com/problems/remove-linked-list-elements/ 题意:删除链表中等于给定值 val 的所有节点。 -![203题目示例](https://img-blog.csdnimg.cn/20200814104441179.png) +示例 1: +输入:head = [1,2,6,3,4,5,6], val = 6 +输出:[1,2,3,4,5] + +示例 2: +输入:head = [], val = 1 +输出:[] + +示例 3: +输入:head = [7,7,7,7], val = 7 +输出:[] + # 思路 @@ -197,10 +208,71 @@ public ListNode removeElements(ListNode head, int val) { ``` Python: - +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def removeElements(self, head: ListNode, val: int) -> ListNode: + dummy_head = ListNode(next=head) #添加一个虚拟节点 + cur = dummy_head + while(cur.next!=None): + if(cur.next.val == val): + cur.next = cur.next.next #删除cur.next节点 + else: + cur = cur.next + return dummy_head.next +``` Go: +```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +func removeElements(head *ListNode, val int) *ListNode { + dummyHead := &ListNode{} + dummyHead.Next = head + cur := dummyHead + for cur != nil && cur.Next != nil { + if cur.Next.Val == val { + cur.Next = cur.Next.Next + } else { + cur = cur.Next + } + } + return dummyHead.Next +} +``` + +javaScript: + +```js +/** + * @param {ListNode} head + * @param {number} val + * @return {ListNode} + */ +var removeElements = function(head, val) { + const ret = new ListNode(0, head); + let cur = ret; + while(cur.next) { + if(cur.next.val === val) { + cur.next = cur.next.next; + continue; + } + cur = cur.next; + } + return ret.next; +}; +``` + diff --git a/problems/0206.翻转链表.md b/problems/0206.翻转链表.md index 886bbfcd..7c002382 100644 --- a/problems/0206.翻转链表.md +++ b/problems/0206.翻转链表.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 反转链表的写法很简单,一些同学甚至可以背下来但过一阵就忘了该咋写,主要是因为没有理解真正的反转过程。 @@ -143,10 +143,112 @@ class Solution { ``` Python: - +```python +#双指针 +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def reverseList(self, head: ListNode) -> ListNode: + cur = head + pre = None + while(cur!=None): + temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next + cur.next = pre #反转 + #更新pre、cur指针 + pre = cur + cur = temp + return pre +``` Go: +```go +//双指针 +func reverseList(head *ListNode) *ListNode { + var pre *ListNode + cur := head + for cur != nil { + next := cur.Next + cur.Next = pre + pre = cur + cur = next + } + return pre +} + +//递归 +func reverseList(head *ListNode) *ListNode { + return help(nil, head) +} + +func help(pre, head *ListNode)*ListNode{ + if head == nil { + return pre + } + next := head.Next + head.Next = pre + return help(head, next) +} + +``` +javaScript: + +```js +/** + * @param {ListNode} head + * @return {ListNode} + */ + +// 双指针: +var reverseList = function(head) { + if(!head || !head.next) return head; + let temp = null, pre = null, cur = head; + while(cur) { + temp = cur.next; + cur.next = pre; + pre = cur; + cur = temp; + } + // temp = cur = null; + return pre; +}; + +// 递归: +var reverse = function(pre, head) { + if(!head) return pre; + const temp = head.next; + head.next = pre; + pre = head + return reverse(pre, temp); +} + +var reverseList = function(head) { + return reverse(null, head); +}; + +// 递归2 +var reverse = function(head) { + if(!head || !head.next) return head; + // 从后往前翻 + const pre = reverse(head.next); + head.next = pre.next; + pre.next = head; + return head; +} + +var reverseList = function(head) { + let cur = head; + while(cur && cur.next) { + cur = cur.next; + } + reverse(head); + return cur; +}; +``` + @@ -154,4 +256,4 @@ Go: * 作者微信:[程序员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/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index 0aaa466e..90280451 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 209.长度最小的子数组 @@ -116,26 +116,6 @@ public: 不要以为for里放一个while就以为是$O(n^2)$啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是2 * n 也就是$O(n)$。 -## 其他语言补充 - -python: - -```python -class Solution: - def minSubArrayLen(self, s: int, nums: List[int]) -> int: - # 定义一个无限大的数 - res = float("inf") - Sum = 0 - index = 0 - for i in range(len(nums)): - Sum += nums[i] - while Sum >= s: - res = min(res, i-index+1) - Sum -= nums[index] - index += 1 - return 0 if res==float("inf") else res -``` - ## 相关题目推荐 * 904.水果成篮 @@ -170,24 +150,69 @@ class Solution { Python: +```python +class Solution: + def minSubArrayLen(self, s: int, nums: List[int]) -> int: + # 定义一个无限大的数 + res = float("inf") + Sum = 0 + index = 0 + for i in range(len(nums)): + Sum += nums[i] + while Sum >= s: + res = min(res, i-index+1) + Sum -= nums[index] + index += 1 + return 0 if res==float("inf") else res +``` + Go: +```go +func minSubArrayLen(target int, nums []int) int { + i := 0 + l := len(nums) // 数组长度 + sum := 0 // 子数组之和 + result := l + 1 // 初始化返回长度为l+1,目的是为了判断“不存在符合条件的子数组,返回0”的情况 + for j := 0; j < l; j++ { + sum += nums[j] + for sum >= target { + subLength := j - i + 1 + if subLength < result { + result = subLength + } + sum -= nums[i] + i++ + } + } + if result == l+1 { + return 0 + } else { + return result + } +} +``` JavaScript: -``` -var minSubArrayLen = (target, nums) => { - let left = 0, right = 0,win = Infinity,sum = 0; - while(right < nums.length){ - sum += nums[right]; - while(sum >= target){ - win = right - left + 1 < win? right - left + 1 : win; - sum -= nums[left]; - left++; - } - right++; + +```js + +var minSubArrayLen = function(target, nums) { + // 长度计算一次 + const len = nums.length; + let l = r = sum = 0, + res = len + 1; // 子数组最大不会超过自身 + while(r < len) { + sum += nums[r++]; + // 窗口滑动 + while(sum >= target) { + // r始终为开区间 [l, r) + res = res < r - l ? res : r - l; + sum-=nums[l++]; + } } - return win === Infinity? 0:win; + return res > len ? 0 : res; }; ``` @@ -195,4 +220,4 @@ var minSubArrayLen = (target, nums) => { * 作者微信:[程序员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/0213.打家劫舍II.md b/problems/0213.打家劫舍II.md index 55e71bc0..12951117 100644 --- a/problems/0213.打家劫舍II.md +++ b/problems/0213.打家劫舍II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 213.打家劫舍II @@ -98,11 +98,73 @@ public: Java: +```Java +class Solution { + public int rob(int[] nums) { + if (nums == null || nums.length == 0) + return 0; + int len = nums.length; + if (len == 1) + return nums[0]; + return Math.max(robAction(nums, 0, len - 1), robAction(nums, 1, len)); + } + int robAction(int[] nums, int start, int end) { + int x = 0, y = 0, z = 0; + for (int i = start; i < end; i++) { + y = z; + z = Math.max(y, x + nums[i]); + x = y; + } + return z; + } +} +``` Python: +```Python +class Solution: + def rob(self, nums: List[int]) -> int: + if (n := len(nums)) == 0: + return 0 + if n == 1: + return nums[0] + result1 = self.robRange(nums, 0, n - 2) + result2 = self.robRange(nums, 1, n - 1) + return max(result1 , result2) + def robRange(self, nums: List[int], start: int, end: int) -> int: + if end == start: return nums[start] + dp = [0] * len(nums) + dp[start] = nums[start] + dp[start + 1] = max(nums[start], nums[start + 1]) + for i in range(start + 2, end + 1): + dp[i] = max(dp[i -2] + nums[i], dp[i - 1]) + return dp[end] +``` +javascipt: +```javascript +var rob = function(nums) { + const n = nums.length + if (n === 0) return 0 + if (n === 1) return nums[0] + const result1 = robRange(nums, 0, n - 2) + const result2 = robRange(nums, 1, n - 1) + return Math.max(result1, result2) +}; + +const robRange = (nums, start, end) => { + if (end === start) return nums[start] + const dp = Array(nums.length).fill(0) + dp[start] = nums[start] + dp[start + 1] = Math.max(nums[start], nums[start + 1]) + for (let i = start + 2; i <= end; i++) { + dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]) + } + return dp[end] +} +``` Go: diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index 21230e0f..9f75b23d 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -94,7 +94,7 @@ void backtracking(int targetSum, int k, int sum, int startIndex) 所以 终止代码如下: -``` +```C++ if (path.size() == k) { if (sum == targetSum) result.push_back(path); return; // 如果path.size() == k 但sum != targetSum 直接返回 @@ -112,7 +112,7 @@ if (path.size() == k) { 代码如下: -``` +```C++ for (int i = startIndex; i <= 9; i++) { sum += i; path.push_back(i); @@ -126,7 +126,7 @@ for (int i = startIndex; i <= 9; i++) { 参照[关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw)中的模板,不难写出如下C++代码: -``` +```C++ class Solution { private: vector> result; // 存放结果集 @@ -180,7 +180,7 @@ if (sum > targetSum) { // 剪枝操作 最后C++代码如下: -``` +```c++ class Solution { private: vector> result; // 存放结果集 @@ -262,10 +262,66 @@ class Solution { ``` Python: - +```py +class Solution: + def combinationSum3(self, k: int, n: int) -> List[List[int]]: + res = [] #存放结果集 + path = [] #符合条件的结果 + def findallPath(n,k,sum,startIndex): + if sum > n: return #剪枝操作 + if sum == n and len(path) == k: #如果path.size() == k 但sum != n 直接返回 + return res.append(path[:]) + for i in range(startIndex,9-(k-len(path))+2): #剪枝操作 + path.append(i) + sum += i + findallPath(n,k,sum,i+1) #注意i+1调整startIndex + sum -= i #回溯 + path.pop() #回溯 + + findallPath(n,k,0,1) + return res +``` Go: +javaScript: + +```js +// 等差数列 +var maxV = k => k * (9 + 10 - k) / 2; +var minV = k => k * (1 + k) / 2; +var combinationSum3 = function(k, n) { + if (k > 9 || k < 1) return []; + // if (n > maxV(k) || n < minV(k)) return []; + // if (n === maxV(k)) return [Array.from({length: k}).map((v, i) => 9 - i)]; + // if (n === minV(k)) return [Array.from({length: k}).map((v, i) => i + 1)]; + + const res = [], path = []; + backtracking(k, n, 1, 0); + return res; + function backtracking(k, n, i, sum){ + const len = path.length; + if (len > k || sum > n) return; + if (maxV(k - len) < n - sum) return; + if (minV(k - len) > n - sum) return; + + if(len === k && sum == n) { + res.push(Array.from(path)); + return; + } + + const min = Math.min(n - sum, 9 + len - k + 1); + + for(let a = i; a <= min; a++) { + path.push(a); + sum += a; + backtracking(k, n, a + 1, sum); + path.pop(); + sum -= a; + } + } +}; +``` @@ -273,4 +329,4 @@ Go: * 作者微信:[程序员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/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index b28b8dfb..998e22f3 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 222.完全二叉树的节点个数 @@ -240,9 +240,171 @@ class Solution { Python: +> 递归法: +```python +class Solution: + def countNodes(self, root: TreeNode) -> int: + return self.getNodesNum(root) + + def getNodesNum(self, cur): + if not cur: + return 0 + leftNum = self.getNodesNum(cur.left) #左 + rightNum = self.getNodesNum(cur.right) #右 + treeNum = leftNum + rightNum + 1 #中 + return treeNum +``` + +> 递归法:精简版 +```python +class Solution: + def countNodes(self, root: TreeNode) -> int: + if not root: + return 0 + return 1 + self.countNodes(root.left) + self.countNodes(root.right) +``` + +> 迭代法: +```python +import collections +class Solution: + def countNodes(self, root: TreeNode) -> int: + queue = collections.deque() + if root: + queue.append(root) + result = 0 + while queue: + size = len(queue) + for i in range(size): + node = queue.popleft() + result += 1 #记录节点数量 + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + return result +``` + +> 完全二叉树 +```python +class Solution: + def countNodes(self, root: TreeNode) -> int: + if not root: + return 0 + left = root.left + right = root.right + leftHeight = 0 #这里初始为0是有目的的,为了下面求指数方便 + rightHeight = 0 + while left: #求左子树深度 + left = left.left + leftHeight += 1 + while right: #求右子树深度 + right = right.right + rightHeight += 1 + if leftHeight == rightHeight: + return (2 << leftHeight) - 1 #注意(2<<1) 相当于2^2,所以leftHeight初始为0 + return self.countNodes(root.left) + self.countNodes(root.right) + 1 +``` Go: +递归版本 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +//本题直接就是求有多少个节点,无脑存进数组算长度就行了。 +func countNodes(root *TreeNode) int { + if root == nil { + return 0 + } + res := 1 + if root.Right != nil { + res += countNodes(root.Right) + } + if root.Left != nil { + res += countNodes(root.Left) + } + return res +} +``` + + + +JavaScript: + +递归版本 +```javascript +var countNodes = function(root) { + //递归法计算二叉树节点数 + // 1. 确定递归函数参数 + const getNodeSum=function(node){ + //2. 确定终止条件 + if(node===null){ + return 0; + } + //3. 确定单层递归逻辑 + let leftNum=getNodeSum(node.left); + let rightNum=getNodeSum(node.right); + return leftNum+rightNum+1; + } + return getNodeSum(root); +}; +``` + +迭代(层序遍历)版本 +```javascript +var countNodes = function(root) { + //层序遍历 + let queue=[]; + if(root===null){ + return 0; + } + queue.push(root); + let nodeNums=0; + while(queue.length){ + let length=queue.length; + while(length--){ + let node=queue.shift(); + nodeNums++; + node.left&&queue.push(node.left); + node.right&&queue.push(node.right); + } + } + return nodeNums; +}; +``` + +利用完全二叉树性质 +```javascript +var countNodes = function(root) { + //利用完全二叉树的特点 + if(root===null){ + return 0; + } + let left=root.left; + let right=root.right; + let leftHeight=0,rightHeight=0; + while(left){ + left=left.left; + leftHeight++; + } + while(right){ + right=right.right; + rightHeight++; + } + if(leftHeight==rightHeight){ + return Math.pow(2,leftHeight+1)-1; + } + return countNodes(root.left)+countNodes(root.right)+1; +}; +``` @@ -250,4 +412,4 @@ Go: * 作者微信:[程序员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/0225.用队列实现栈.md b/problems/0225.用队列实现栈.md index 4018364a..055c2e3d 100644 --- a/problems/0225.用队列实现栈.md +++ b/problems/0225.用队列实现栈.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -156,6 +156,7 @@ public: Java: +使用两个 Queue 实现 ```java class MyStack { @@ -205,7 +206,94 @@ class MyStack { * boolean param_4 = obj.empty(); */ ``` +使用两个 Deque 实现 +```java +class MyStack { + // Deque 接口继承了 Queue 接口 + // 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst + Deque que1; // 和栈中保持一样元素的队列 + Deque que2; // 辅助队列 + /** Initialize your data structure here. */ + public MyStack() { + que1 = new ArrayDeque<>(); + que2 = new ArrayDeque<>(); + } + + /** Push element x onto stack. */ + public void push(int x) { + que1.addLast(x); + } + + /** Removes the element on top of the stack and returns that element. */ + public int pop() { + int size = que1.size(); + size--; + // 将 que1 导入 que2 ,但留下最后一个值 + while (size-- > 0) { + que2.addLast(que1.peekFirst()); + que1.pollFirst(); + } + int res = que1.pollFirst(); + // 将 que2 对象的引用赋给了 que1 ,此时 que1,que2 指向同一个队列 + que1 = que2; + // 如果直接操作 que2,que1 也会受到影响,所以为 que2 分配一个新的空间 + que2 = new ArrayDeque<>(); + return res; + } + + /** Get the top element. */ + public int top() { + return que1.peekLast(); + } + + /** Returns whether the stack is empty. */ + public boolean empty() { + return que1.isEmpty(); + } +} +``` +优化,使用一个 Deque 实现 +```java +class MyStack { + // Deque 接口继承了 Queue 接口 + // 所以 Queue 中的 add、poll、peek等效于 Deque 中的 addLast、pollFirst、peekFirst + Deque que1; + /** Initialize your data structure here. */ + public MyStack() { + que1 = new ArrayDeque<>(); + } + + /** Push element x onto stack. */ + public void push(int x) { + que1.addLast(x); + } + + /** Removes the element on top of the stack and returns that element. */ + public int pop() { + int size = que1.size(); + size--; + // 将 que1 导入 que2 ,但留下最后一个值 + while (size-- > 0) { + que1.addLast(que1.peekFirst()); + que1.pollFirst(); + } + + int res = que1.pollFirst(); + return res; + } + + /** Get the top element. */ + public int top() { + return que1.peekLast(); + } + + /** Returns whether the stack is empty. */ + public boolean empty() { + return que1.isEmpty(); + } +} +``` Python: @@ -269,11 +357,118 @@ class MyStack: Go: +javaScript: +使用数组(push, shift)模拟队列 +```js + +// 使用两个队列实现 +/** + * Initialize your data structure here. + */ +var MyStack = function() { + this.queue1 = []; + this.queue2 = []; +}; + +/** + * Push element x onto stack. + * @param {number} x + * @return {void} + */ +MyStack.prototype.push = function(x) { + this.queue1.push(x); +}; + +/** + * Removes the element on top of the stack and returns that element. + * @return {number} + */ +MyStack.prototype.pop = function() { + // 减少两个队列交换的次数, 只有当queue1为空时,交换两个队列 + if(!this.queue1.length) { + [this.queue1, this.queue2] = [this.queue2, this.queue1]; + } + while(this.queue1.length > 1) { + this.queue2.push(this.queue1.shift()); + } + return this.queue1.shift(); +}; + +/** + * Get the top element. + * @return {number} + */ +MyStack.prototype.top = function() { + const x = this.pop(); + this.queue1.push(x); + return x; +}; + +/** + * Returns whether the stack is empty. + * @return {boolean} + */ +MyStack.prototype.empty = function() { + return !this.queue1.length && !this.queue2.length; +}; + +``` + +```js + +// 使用一个队列实现 +/** + * Initialize your data structure here. + */ +var MyStack = function() { + this.queue = []; +}; + +/** + * Push element x onto stack. + * @param {number} x + * @return {void} + */ +MyStack.prototype.push = function(x) { + this.queue.push(x); +}; + +/** + * Removes the element on top of the stack and returns that element. + * @return {number} + */ +MyStack.prototype.pop = function() { + let size = this.queue.length; + while(size-- > 1) { + this.queue.push(this.queue.shift()); + } + return this.queue.shift(); +}; + +/** + * Get the top element. + * @return {number} + */ +MyStack.prototype.top = function() { + const x = this.pop(); + this.queue.push(x); + return x; +}; + +/** + * Returns whether the stack is empty. + * @return {boolean} + */ +MyStack.prototype.empty = function() { + return !this.queue.length; +}; + +``` ----------------------- * 作者微信:[程序员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/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md index 3b96b4f6..2b628ec4 100644 --- a/problems/0226.翻转二叉树.md +++ b/problems/0226.翻转二叉树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 226.翻转二叉树 @@ -230,6 +230,56 @@ class Solution { Python: +> 递归法:前序遍历 +```python +class Solution: + def invertTree(self, root: TreeNode) -> TreeNode: + if not root: + return None + root.left, root.right = root.right, root.left #中 + self.invertTree(root.left) #左 + self.invertTree(root.right) #右 + return root +``` + +> 迭代法:深度优先遍历(前序遍历) +```python +class Solution: + def invertTree(self, root: TreeNode) -> TreeNode: + if not root: + return root + st = [] + st.append(root) + while st: + node = st.pop() + node.left, node.right = node.right, node.left #中 + if node.right: + st.append(node.right) #右 + if node.left: + st.append(node.left) #左 + return root +``` + +> 迭代法:广度优先遍历(层序遍历) +```python +import collections +class Solution: + def invertTree(self, root: TreeNode) -> TreeNode: + queue = collections.deque() #使用deque() + if root: + queue.append(root) + while queue: + size = len(queue) + for i in range(size): + node = queue.popleft() + node.left, node.right = node.right, node.left #节点处理 + if node.left: + queue.append(node.left) + if node.right: + queue.append(node.right) + return root +``` + Go: ```Go func invertTree(root *TreeNode) *TreeNode { @@ -247,10 +297,100 @@ func invertTree(root *TreeNode) *TreeNode { } ``` +JavaScript: +使用递归版本的前序遍历 +```javascript +var invertTree = function(root) { + //1. 首先使用递归版本的前序遍历实现二叉树翻转 + //交换节点函数 + const inverNode=function(left,right){ + let temp=left; + left=right; + right=temp; + //需要重新给root赋值一下 + root.left=left; + root.right=right; + } + //确定递归函数的参数和返回值inverTree=function(root) + //确定终止条件 + if(root===null){ + return root; + } + //确定节点处理逻辑 交换 + inverNode(root.left,root.right); + invertTree(root.left); + invertTree(root.right); + return root; +}; +``` +使用迭代版本(统一模板))的前序遍历: +```javascript +var invertTree = function(root) { + //我们先定义节点交换函数 + const invertNode=function(root,left,right){ + let temp=left; + left=right; + right=temp; + root.left=left; + root.right=right; + } + //使用迭代方法的前序遍历 + let stack=[]; + if(root===null){ + return root; + } + stack.push(root); + while(stack.length){ + let node=stack.pop(); + if(node!==null){ + //前序遍历顺序中左右 入栈顺序是前序遍历的倒序右左中 + node.right&&stack.push(node.right); + node.left&&stack.push(node.left); + stack.push(node); + stack.push(null); + }else{ + node=stack.pop(); + //节点处理逻辑 + invertNode(node,node.left,node.right); + } + } + return root; +}; +``` +使用层序遍历: +```javascript +var invertTree = function(root) { + //我们先定义节点交换函数 + const invertNode=function(root,left,right){ + let temp=left; + left=right; + right=temp; + root.left=left; + root.right=right; + } + //使用层序遍历 + let queue=[]; + if(root===null){ + return root; + } + queue.push(root); + while(queue.length){ + let length=queue.length; + while(length--){ + let node=queue.shift(); + //节点处理逻辑 + invertNode(node,node.left,node.right); + node.left&&queue.push(node.left); + node.right&&queue.push(node.right); + } + } + return root; +}; +``` ----------------------- * 作者微信:[程序员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/0232.用栈实现队列.md b/problems/0232.用栈实现队列.md index abdc363d..c2af71f7 100644 --- a/problems/0232.用栈实现队列.md +++ b/problems/0232.用栈实现队列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 工作上一定没人这么搞,但是考察对栈、队列理解程度的好题 @@ -131,6 +131,101 @@ public: Java: +使用Stack(堆栈)同名方法: +```java +class MyQueue { + // java中的 Stack 有设计上的缺陷,官方推荐使用 Deque(双端队列) 代替 Stack + Deque stIn; + Deque stOut; + /** Initialize your data structure here. */ + public MyQueue() { + stIn = new ArrayDeque<>(); + stOut = new ArrayDeque<>(); + } + + /** Push element x to the back of queue. */ + public void push(int x) { + stIn.push(x); + } + + /** Removes the element from in front of queue and returns that element. */ + public int pop() { + // 只要 stOut 为空,那么就应该将 stIn 中所有的元素倒腾到 stOut 中 + if (stOut.isEmpty()) { + while (!stIn.isEmpty()) { + stOut.push(stIn.pop()); + } + } + // 再返回 stOut 中的元素 + return stOut.pop(); + } + + /** Get the front element. */ + public int peek() { + // 直接使用已有的pop函数 + int res = this.pop(); + // 因为pop函数弹出了元素res,所以再添加回去 + stOut.push(res); + return res; + } + + /** Returns whether the queue is empty. */ + public boolean empty() { + // 当 stIn 栈为空时,说明没有元素可以倒腾到 stOut 栈了 + // 并且 stOut 栈也为空时,说明没有以前从 stIn 中倒腾到的元素了 + return stIn.isEmpty() && stOut.isEmpty(); + } +} +``` + +个人习惯写法,使用Deque通用api: +```java +class MyQueue { + // java中的 Stack 有设计上的缺陷,官方推荐使用 Deque(双端队列) 代替 Stack + // Deque 中的 addFirst、removeFirst、peekFirst 等方法等效于 Stack(堆栈) 中的 push、pop、peek + Deque stIn; + Deque stOut; + /** Initialize your data structure here. */ + public MyQueue() { + stIn = new ArrayDeque<>(); + stOut = new ArrayDeque<>(); + } + + /** Push element x to the back of queue. */ + public void push(int x) { + stIn.addLast(x); + } + + /** Removes the element from in front of queue and returns that element. */ + public int pop() { + // 只要 stOut 为空,那么就应该将 stIn 中所有的元素倒腾到 stOut 中 + if (stOut.isEmpty()) { + while (!stIn.isEmpty()) { + stOut.addLast(stIn.pollLast()); + } + } + // 再返回 stOut 中的元素 + return stOut.pollLast(); + } + + /** Get the front element. */ + public int peek() { + // 直接使用已有的pop函数 + int res = this.pop(); + // 因为pop函数弹出了元素res,所以再添加回去 + stOut.addLast(res); + return res; + } + + /** Returns whether the queue is empty. */ + public boolean empty() { + // 当 stIn 栈为空时,说明没有元素可以倒腾到 stOut 栈了 + // 并且 stOut 栈也为空时,说明没有以前从 stIn 中倒腾到的元素了 + return stIn.isEmpty() && stOut.isEmpty(); + } +} +``` + ```java class MyQueue { @@ -187,9 +282,175 @@ class MyQueue { Python: +```python +# 使用两个栈实现先进先出的队列 +class MyQueue: + def __init__(self): + """ + Initialize your data structure here. + """ + self.stack1 = list() + self.stack2 = list() + + def push(self, x: int) -> None: + """ + Push element x to the back of queue. + """ + # self.stack1用于接受元素 + self.stack1.append(x) + + def pop(self) -> int: + """ + Removes the element from in front of queue and returns that element. + """ + # self.stack2用于弹出元素,如果self.stack2为[],则将self.stack1中元素全部弹出给self.stack2 + if self.stack2 == []: + while self.stack1: + tmp = self.stack1.pop() + self.stack2.append(tmp) + return self.stack2.pop() + + def peek(self) -> int: + """ + Get the front element. + """ + if self.stack2 == []: + while self.stack1: + tmp = self.stack1.pop() + self.stack2.append(tmp) + return self.stack2[-1] + + def empty(self) -> bool: + """ + Returns whether the queue is empty. + """ + return self.stack1 == [] and self.stack2 == [] +``` Go: +```Go +type MyQueue struct { + stack []int + back []int +} + +/** Initialize your data structure here. */ +func Constructor() MyQueue { + return MyQueue{ + stack: make([]int, 0), + back: make([]int, 0), + } +} + +/** Push element x to the back of queue. */ +func (this *MyQueue) Push(x int) { + for len(this.back) != 0 { + val := this.back[len(this.back)-1] + this.back = this.back[:len(this.back)-1] + this.stack = append(this.stack, val) + } + this.stack = append(this.stack, x) +} + +/** Removes the element from in front of queue and returns that element. */ +func (this *MyQueue) Pop() int { + for len(this.stack) != 0 { + val := this.stack[len(this.stack)-1] + this.stack = this.stack[:len(this.stack)-1] + this.back = append(this.back, val) + } + if len(this.back) == 0 { + return 0 + } + val := this.back[len(this.back)-1] + this.back = this.back[:len(this.back)-1] + return val +} + +/** Get the front element. */ +func (this *MyQueue) Peek() int { + for len(this.stack) != 0 { + val := this.stack[len(this.stack)-1] + this.stack = this.stack[:len(this.stack)-1] + this.back = append(this.back, val) + } + if len(this.back) == 0 { + return 0 + } + val := this.back[len(this.back)-1] + return val +} + +/** Returns whether the queue is empty. */ +func (this *MyQueue) Empty() bool { + return len(this.stack) == 0 && len(this.back) == 0 +} + +/** + * Your MyQueue object will be instantiated and called as such: + * obj := Constructor(); + * obj.Push(x); + * param_2 := obj.Pop(); + * param_3 := obj.Peek(); + * param_4 := obj.Empty(); + */ + ``` + + javaScript: + + ```js + // 使用两个数组的栈方法(push, pop) 实现队列 + /** + * Initialize your data structure here. + */ +var MyQueue = function() { + this.stack1 = []; + this.stack2 = []; +}; + +/** + * Push element x to the back of queue. + * @param {number} x + * @return {void} + */ +MyQueue.prototype.push = function(x) { + this.stack1.push(x); +}; + +/** + * Removes the element from in front of queue and returns that element. + * @return {number} + */ +MyQueue.prototype.pop = function() { + const size = this.stack2.length; + if(size) { + return this.stack2.pop(); + } + while(this.stack1.length) { + this.stack2.push(this.stack1.pop()); + } + return this.stack2.pop(); +}; + +/** + * Get the front element. + * @return {number} + */ +MyQueue.prototype.peek = function() { + const x = this.pop(); + this.stack2.push(x); + return x; +}; + +/** + * Returns whether the queue is empty. + * @return {boolean} + */ +MyQueue.prototype.empty = function() { + return !this.stack1.length && !this.stack2.length +}; + ``` @@ -198,4 +459,4 @@ Go: * 作者微信:[程序员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/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md index 93642de5..15ff7af4 100644 --- a/problems/0235.二叉搜索树的最近公共祖先.md +++ b/problems/0235.二叉搜索树的最近公共祖先.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 235. 二叉搜索树的最近公共祖先 @@ -247,8 +247,23 @@ class Solution { ``` Python: +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None - +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if not root: return root //中 + if root.val >p.val and root.val > q.val: + return self.lowestCommonAncestor(root.left,p,q) //左 + elif root.val < p.val and root.val < q.val: + return self.lowestCommonAncestor(root.right,p,q) //右 + else: return root +``` Go: @@ -258,4 +273,4 @@ Go: * 作者微信:[程序员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/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md index 69127bd9..2f7aa6c3 100644 --- a/problems/0236.二叉树的最近公共祖先.md +++ b/problems/0236.二叉树的最近公共祖先.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 本来是打算将二叉树和二叉搜索树的公共祖先问题一起讲,后来发现篇幅过长了,只能先说一说二叉树的公共祖先问题。 @@ -263,10 +263,53 @@ class Solution { ``` Python: - - +```python +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, x): +# self.val = x +# self.left = None +# self.right = None +//递归 +class Solution: + def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode': + if not root or root == p or root == q: return root //找到了节点p或者q,或者遇到空节点 + left = self.lowestCommonAncestor(root.left,p,q) //左 + right = self.lowestCommonAncestor(root.right,p,q) //右 + if left and right: return root //中: left和right不为空,root就是最近公共节点 + elif left and not right: return left //目标节点是通过left返回的 + elif not left and right: return right //目标节点是通过right返回的 + else: return None //没找到 +``` Go: +```Go +func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode { + // check + if root == nil { + return root + } + // 相等 直接返回root节点即可 + if root == p || root == q { + return root + } + // Divide + left := lowestCommonAncestor(root.Left, p, q) + right := lowestCommonAncestor(root.Right, p, q) + // Conquer + // 左右两边都不为空,则根节点为祖先 + if left != nil && right != nil { + return root + } + if left != nil { + return left + } + if right != nil { + return right + } + return nil +} +``` @@ -274,4 +317,4 @@ Go: * 作者微信:[程序员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/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md index 781bfa6f..d108335b 100644 --- a/problems/0239.滑动窗口最大值.md +++ b/problems/0239.滑动窗口最大值.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -263,11 +263,159 @@ class Solution { ``` Python: +```python +class MyQueue: #单调队列(从大到小 + def __init__(self): + self.queue = [] #使用list来实现单调队列 + + #每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。 + #同时pop之前判断队列当前是否为空。 + def pop(self, value): + if self.queue and value == self.queue[0]: + self.queue.pop(0)#list.pop()时间复杂度为O(n),这里可以使用collections.deque() + + #如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。 + #这样就保持了队列里的数值是单调从大到小的了。 + def push(self, value): + while self.queue and value > self.queue[-1]: + self.queue.pop() + self.queue.append(value) + + #查询当前队列里的最大值 直接返回队列前端也就是front就可以了。 + def front(self): + return self.queue[0] + +class Solution: + def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]: + que = MyQueue() + result = [] + for i in range(k): #先将前k的元素放进队列 + que.push(nums[i]) + result.append(que.front()) #result 记录前k的元素的最大值 + for i in range(k, len(nums)): + que.pop(nums[i - k]) #滑动窗口移除最前面元素 + que.push(nums[i]) #滑动窗口前加入最后面的元素 + result.append(que.front()) #记录对应的最大值 + return result +``` 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 { + queue []int +} + +func NewMyQueue() *MyQueue { + return &MyQueue{ + queue: make([]int, 0), + } +} + +func (m *MyQueue) Front() int { + return m.queue[0] +} + +func (m *MyQueue) Back() int { + return m.queue[len(m.queue)-1] +} + +func (m *MyQueue) Empty() bool { + return len(m.queue) == 0 +} + +func (m *MyQueue) Push(val int) { + for !m.Empty() && val > m.Back() { + m.queue = m.queue[:len(m.queue)-1] + } + m.queue = append(m.queue, val) +} + +func (m *MyQueue) Pop(val int) { + if !m.Empty() && val == m.Front() { + m.queue = m.queue[1:] + } +} + +func maxSlidingWindow(nums []int, k int) []int { + queue := NewMyQueue() + length := len(nums) + res := make([]int, 0) + // 先将前k个元素放入队列 + for i := 0; i < k; i++ { + queue.Push(nums[i]) + } + // 记录前k个元素的最大值 + res = append(res, queue.Front()) + + for i := k; i < length; i++ { + // 滑动窗口移除最前面的元素 + queue.Pop(nums[i-k]) + // 滑动窗口添加最后面的元素 + queue.Push(nums[i]) + // 记录最大值 + res = append(res, queue.Front()) + } + return res +} +``` + +Javascript: +```javascript +var maxSlidingWindow = function (nums, k) { + // 队列数组(存放的是元素下标,为了取值方便) + const q = []; + // 结果数组 + const ans = []; + for (let i = 0; i < nums.length; i++) { + // 若队列不为空,且当前元素大于等于队尾所存下标的元素,则弹出队尾 + while (q.length && nums[i] >= nums[q[q.length - 1]]) { + q.pop(); + } + // 入队当前元素下标 + q.push(i); + // 判断当前最大值(即队首元素)是否在窗口中,若不在便将其出队 + while (q[0] <= i - k) { + q.shift(); + } + // 当达到窗口大小时便开始向结果中添加数据 + if (i >= k - 1) ans.push(nums[q[0]]); + } + return ans; +}; +``` ----------------------- diff --git a/problems/0242.有效的字母异位词.md b/problems/0242.有效的字母异位词.md index 1927f476..ba942f70 100644 --- a/problems/0242.有效的字母异位词.md +++ b/problems/0242.有效的字母异位词.md @@ -1,26 +1,33 @@

- + - +

-

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

+

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

> 数组就是简单的哈希表,但是数组的大小可不是无限开辟的 -# 242.有效的字母异位词 +## 242.有效的字母异位词 https://leetcode-cn.com/problems/valid-anagram/ 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 -![](https://img-blog.csdnimg.cn/202008171902298.png) +示例 1: +输入: s = "anagram", t = "nagaram" +输出: true + +示例 2: +输入: s = "rat", t = "car" +输出: false + **说明:** 你可以假设字符串只包含小写字母。 -# 思路 +## 思路 先看暴力的解法,两层for循环,同时还要记录字符是否重复出现,很明显时间复杂度是 O(n^2)。 @@ -52,7 +59,6 @@ https://leetcode-cn.com/problems/valid-anagram/ 时间复杂度为O(n),空间上因为定义是的一个常量大小的辅助数组,所以空间复杂度为O(1)。 -看完这篇哈希表总结:[哈希表:总结篇!(每逢总结必经典)](https://mp.weixin.qq.com/s/1s91yXtarL-PkX07BfnwLg),详细就可以哈希表的各种用法非常清晰了。 C++ 代码如下: ```C++ @@ -107,7 +113,22 @@ class Solution { ``` Python: - +```python +class Solution: + def isAnagram(self, s: str, t: str) -> bool: + record = [0] * 26 + for i in range(len(s)): + #并不需要记住字符a的ASCII,只要求出一个相对数值就可以了 + record[ord(s[i]) - ord("a")] += 1 + print(record) + for i in range(len(t)): + record[ord(t[i]) - ord("a")] -= 1 + for i in range(26): + if record[i] != 0: + #record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。 + return False + return True +``` Go: ```go @@ -134,9 +155,38 @@ func isAnagram(s string, t string) bool { } ``` +javaScript: + +```js +/** + * @param {string} s + * @param {string} t + * @return {boolean} + */ +var isAnagram = function(s, t) { + if(s.length !== t.length) return false; + const resSet = new Array(26).fill(0); + const base = "a".charCodeAt(); + for(const i of s) { + resSet[i.charCodeAt() - base]++; + } + for(const i of t) { + if(!resSet[i.charCodeAt() - base]) return false; + resSet[i.charCodeAt() - base]--; + } + return true; +}; +``` + +## 相关题目 + +* 383.赎金信 +* 49.字母异位词分组 +* 438.找到字符串中所有字母异位词 + ----------------------- * 作者微信:[程序员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/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index 73387257..fe7a2604 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 以为只用了递归,其实还用了回溯 @@ -77,7 +77,7 @@ if (cur->left == NULL && cur->right == NULL) { 这里我们先使用vector结构的path容器来记录路径,那么终止处理逻辑如下: -``` +```C++ if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点 string sPath; for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式 @@ -113,7 +113,7 @@ if (cur->right) { 那么回溯要怎么回溯呢,一些同学会这么写,如下: -``` +```C++ if (cur->left) { traversal(cur->left, path, result); } @@ -129,7 +129,7 @@ path.pop_back(); 那么代码应该这么写: -``` +```C++ if (cur->left) { traversal(cur->left, path, result); path.pop_back(); // 回溯 @@ -324,10 +324,57 @@ class Solution { ``` Python: +```Python +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def binaryTreePaths(self, root: TreeNode) -> List[str]: + path=[] + res=[] + def backtrace(root, path): + if not root:return + path.append(root.val) + if (not root.left)and (not root.right): + res.append(path[:]) + ways=[] + if root.left:ways.append(root.left) + if root.right:ways.append(root.right) + for way in ways: + backtrace(way,path) + path.pop() + backtrace(root,path) + return ["->".join(list(map(str,i))) for i in res] - +``` Go: +JavaScript: +1.递归版本 +```javascript +var binaryTreePaths = function(root) { + //递归遍历+递归三部曲 + let res=[]; + //1. 确定递归函数 函数参数 + const getPath=function(node,curPath){ + //2. 确定终止条件,到叶子节点就终止 + if(node.left===null&&node.right===null){ + curPath+=node.val; + res.push(curPath); + return ; + } + //3. 确定单层递归逻辑 + curPath+=node.val+'->'; + node.left&&getPath(node.left,curPath); + node.right&&getPath(node.right,curPath); + } + getPath(root,''); + return res; +}; +``` + @@ -335,4 +382,4 @@ Go: * 作者微信:[程序员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/0279.完全平方数.md b/problems/0279.完全平方数.md index d72e9099..60d6d165 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:一样的套路,再求一次完全平方数 ## 279.完全平方数 @@ -26,7 +26,7 @@ 输入:n = 13 输出:2 解释:13 = 4 + 9 -  + 提示: * 1 <= n <= 10^4 @@ -184,9 +184,89 @@ class Solution { Python: +```python3 +class Solution: + def numSquares(self, n: int) -> int: + '''版本一''' + # 初始化 + nums = [i**2 for i in range(1, n + 1) if i**2 <= n] + dp = [10**4]*(n + 1) + dp[0] = 0 + # 遍历背包 + for j in range(1, n + 1): + # 遍历物品 + for num in nums: + if j >= num: + dp[j] = min(dp[j], dp[j - num] + 1) + return dp[n] + + def numSquares1(self, n: int) -> int: + '''版本二''' + # 初始化 + nums = [i**2 for i in range(1, n + 1) if i**2 <= n] + dp = [10**4]*(n + 1) + dp[0] = 0 + # 遍历物品 + for num in nums: + # 遍历背包 + for j in range(num, n + 1) + dp[j] = min(dp[j], dp[j - num] + 1) + return dp[n] +``` + + + Go: +```go +// 版本一,先遍历物品, 再遍历背包 +func numSquares1(n int) int { + //定义 + dp := make([]int, n+1) + // 初始化 + dp[0] = 0 + for i := 1; i <= n; i++ { + dp[i] = math.MaxInt32 + } + // 遍历物品 + for i := 1; i <= n; i++ { + // 遍历背包 + for j := i*i; j <= n; j++ { + dp[j] = min(dp[j], dp[j-i*i]+1) + } + } + return dp[n] +} + +// 版本二,先遍历背包, 再遍历物品 +func numSquares2(n int) int { + //定义 + dp := make([]int, n+1) + // 初始化 + dp[0] = 0 + // 遍历背包 + for j := 1; j <= n; j++ { + //初始化 + dp[j] = math.MaxInt32 + // 遍历物品 + for i := 1; i <= n; i++ { + if j >= i*i { + dp[j] = min(dp[j], dp[j-i*i]+1) + } + } + } + + return dp[n] +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} +``` diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index 2bc5cf9c..8b4ad5b1 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 300.最长递增子序列 @@ -98,8 +98,6 @@ public: }; ``` -杨老师的这个专栏很不错,他本身也是Oracle 首席工程师,对Java有极其深刻的理解,讲的内容很硬核,适合使用Java语言的录友们用来进阶!作为面试突击手册非常合适, 所以推荐给大家!现在下单输入口令:javahexin,可以省40元那[机智] - ## 总结 本题最关键的是要想到dp[i]由哪些状态可以推出来,并取最大值,那么很自然就能想到递推公式:dp[i] = max(dp[i], dp[j] + 1); @@ -110,14 +108,71 @@ public: Java: - +```Java +class Solution { + public int lengthOfLIS(int[] nums) { + int[] dp = new int[nums.length]; + Arrays.fill(dp, 1); + for (int i = 0; i < dp.length; i++) { + for (int j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + } + int res = 0; + for (int i = 0; i < dp.length; i++) { + res = Math.max(res, dp[i]); + } + return res; + } +} +``` Python: - +```python +class Solution: + def lengthOfLIS(self, nums: List[int]) -> int: + if len(nums) <= 1: + return len(nums) + dp = [1] * len(nums) + result = 0 + for i in range(1, len(nums)): + for j in range(0, i): + if nums[i] > nums[j]: + dp[i] = max(dp[i], dp[j] + 1) + result = max(result, dp[i]) #取长的子序列 + return result +``` Go: - - +```go +func lengthOfLIS(nums []int ) int { + dp := []int{} + for _, num := range nums { + if len(dp) ==0 || dp[len(dp) - 1] < num { + dp = append(dp, num) + } else { + l, r := 0, len(dp) - 1 + pos := r + for l <= r { + mid := (l + r) >> 1 + if dp[mid] >= num { + pos = mid; + r = mid - 1 + } else { + l = mid + 1 + } + } + dp[pos] = num + }//二分查找 + } + return len(dp) +} +``` +*复杂度分析* +- 时间复杂度:O(nlogn)。数组 nums 的长度为 n,我们依次用数组中的元素去更新 dp 数组,相当于插入最后递增的元素,而更新 dp 数组时需要进行 O(logn) 的二分搜索,所以总时间复杂度为 O(nlogn)。 +- 空间复杂度:O(n),需要额外使用长度为 n 的 dp 数组。 ----------------------- diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md index 15bba0b7..4667c122 100644 --- a/problems/0309.最佳买卖股票时机含冷冻期.md +++ b/problems/0309.最佳买卖股票时机含冷冻期.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 309.最佳买卖股票时机含冷冻期 @@ -159,12 +159,51 @@ public: ## 其他语言版本 - Java: +```java +class Solution { + public int maxProfit(int[] prices) { + if (prices == null || prices.length < 2) { + return 0; + } + int[][] dp = new int[prices.length][2]; + + // bad case + dp[0][0] = 0; + dp[0][1] = -prices[0]; + dp[1][0] = Math.max(dp[0][0], dp[0][1] + prices[1]); + dp[1][1] = Math.max(dp[0][1], -prices[1]); + + for (int i = 2; i < prices.length; i++) { + // dp公式 + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]); + } + + return dp[prices.length - 1][0]; + } +} +``` + Python: +```python +class Solution: + def maxProfit(self, prices: List[int]) -> int: + n = len(prices) + if n == 0: + return 0 + dp = [[0] * 4 for _ in range(n)] + dp[0][0] = -prices[0] #持股票 + for i in range(1, n): + dp[i][0] = max(dp[i-1][0], max(dp[i-1][3], 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] + return max(dp[n-1][3], dp[n-1][1], dp[n-1][2]) +``` Go: diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index e67695d8..cf537088 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划: 给我个机会,我再兑换一次零钱 ## 322. 零钱兑换 @@ -35,7 +35,7 @@ 示例 5: 输入:coins = [1], amount = 2 输出:2 -  + 提示: * 1 <= coins.length <= 12 @@ -209,9 +209,100 @@ class Solution { Python: +```python3 +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + '''版本一''' + # 初始化 + dp = [amount + 1]*(amount + 1) + dp[0] = 0 + # 遍历物品 + for coin in coins: + # 遍历背包 + for j in range(coin, amount + 1): + dp[j] = min(dp[j], dp[j - coin] + 1) + return dp[amount] if dp[amount] < amount + 1 else -1 + + def coinChange1(self, coins: List[int], amount: int) -> int: + '''版本二''' + # 初始化 + dp = [amount + 1]*(amount + 1) + dp[0] = 0 + # 遍历物品 + for j in range(1, amount + 1): + # 遍历背包 + for coin in coins: + if j >= coin: + dp[j] = min(dp[j], dp[j - coin] + 1) + return dp[amount] if dp[amount] < amount + 1 else -1 +``` + + + Go: +```go +// 版本一, 先遍历物品,再遍历背包 +func coinChange1(coins []int, amount int) int { + dp := make([]int, amount+1) + // 初始化dp[0] + dp[0] = 0 + // 初始化为math.MaxInt32 + for j := 1; j <= amount; j++ { + dp[j] = math.MaxInt32 + } + // 遍历物品 + for i := 0; i < len(coins); i++ { + // 遍历背包 + for j := coins[i]; j <= amount; j++ { + if dp[j-coins[i]] != math.MaxInt32 { + // 推导公式 + dp[j] = min(dp[j], dp[j-coins[i]]+1) + //fmt.Println(dp,j,i) + } + } + } + // 没找到能装满背包的, 就返回-1 + if dp[amount] == math.MaxInt32 { + return -1 + } + return dp[amount] +} + +// 版本二,先遍历背包,再遍历物品 +func coinChange2(coins []int, amount int) int { + dp := make([]int, amount+1) + // 初始化dp[0] + dp[0] = 0 + // 遍历背包,从1开始 + for j := 1; j <= amount; j++ { + // 初始化为math.MaxInt32 + dp[j] = math.MaxInt32 + // 遍历物品 + for i := 0; i < len(coins); i++ { + if j >= coins[i] && dp[j-coins[i]] != math.MaxInt32 { + // 推导公式 + dp[j] = min(dp[j], dp[j-coins[i]]+1) + //fmt.Println(dp) + } + } + } + // 没找到能装满背包的, 就返回-1 + if dp[amount] == math.MaxInt32 { + return -1 + } + return dp[amount] +} + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +``` diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 756ecc86..97059e4a 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 这也可以用回溯法? 其实深搜和回溯也是相辅相成的,毕竟都用递归。 @@ -399,6 +399,49 @@ char ** findItinerary(char *** tickets, int ticketsSize, int* ticketsColSize, in } ``` +Javascript: +```Javascript + +var findItinerary = function(tickets) { + let result = ['JFK'] + let map = {} + + for (const tickt of tickets) { + const [from, to] = tickt + if (!map[from]) { + map[from] = [] + } + map[from].push(to) + } + + for (const city in map) { + // 对到达城市列表排序 + map[city].sort() + } + function backtracing() { + if (result.length === tickets.length + 1) { + return true + } + if (!map[result[result.length - 1]] || !map[result[result.length - 1]].length) { + return false + } + for(let i = 0 ; i < map[result[result.length - 1]].length; i++) { + let city = map[result[result.length - 1]][i] + // 删除已走过航线,防止死循环 + map[result[result.length - 1]].splice(i, 1) + result.push(city) + if (backtracing()) { + return true + } + result.pop() + map[result[result.length - 1]].splice(i, 0, city) + } + } + backtracing() + return result +}; + +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0337.打家劫舍III.md b/problems/0337.打家劫舍III.md index 50b06e22..0a4b752b 100644 --- a/problems/0337.打家劫舍III.md +++ b/problems/0337.打家劫舍III.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 337.打家劫舍 III @@ -218,10 +218,94 @@ public: Java: +```Java +class Solution { + // 1.递归去偷,超时 + public int rob(TreeNode root) { + if (root == null) + return 0; + int money = root.val; + if (root.left != null) { + money += rob(root.left.left) + rob(root.left.right); + } + if (root.right != null) { + money += rob(root.right.left) + rob(root.right.right); + } + return Math.max(money, rob(root.left) + rob(root.right)); + } + // 2.递归去偷,记录状态 + // 执行用时:3 ms , 在所有 Java 提交中击败了 56.24% 的用户 + public int rob1(TreeNode root) { + Map memo = new HashMap<>(); + return robAction(root, memo); + } + + int robAction(TreeNode root, Map memo) { + if (root == null) + return 0; + if (memo.containsKey(root)) + return memo.get(root); + int money = root.val; + if (root.left != null) { + money += robAction(root.left.left, memo) + robAction(root.left.right, memo); + } + if (root.right != null) { + money += robAction(root.right.left, memo) + robAction(root.right.right, memo); + } + int res = Math.max(money, robAction(root.left, memo) + robAction(root.right, memo)); + memo.put(root, res); + return res; + } + + // 3.状态标记递归 + // 执行用时:0 ms , 在所有 Java 提交中击败了 100% 的用户 + // 不偷:Max(左孩子不偷,左孩子偷) + Max(又孩子不偷,右孩子偷) + // root[0] = Math.max(rob(root.left)[0], rob(root.left)[1]) + + // Math.max(rob(root.right)[0], rob(root.right)[1]) + // 偷:左孩子不偷+ 右孩子不偷 + 当前节点偷 + // root[1] = rob(root.left)[0] + rob(root.right)[0] + root.val; + public int rob3(TreeNode root) { + int[] res = robAction1(root); + return Math.max(res[0], res[1]); + } + + int[] robAction1(TreeNode root) { + int res[] = new int[2]; + if (root == null) + return res; + + int[] left = robAction1(root.left); + int[] right = robAction1(root.right); + + res[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); + res[1] = root.val + left[0] + right[0]; + return res; + } +} +``` Python: +> 动态规划 +```python +class Solution: + def rob(self, root: TreeNode) -> int: + result = self.robTree(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) +``` Go: diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index e7550285..cf60575f 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -1,13 +1,15 @@

- + - +

-

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

+

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

## 343. 整数拆分 +题目链接:https://leetcode-cn.com/problems/integer-break/ + 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 示例 1: @@ -49,11 +51,14 @@ dp[i]的定义讲贯彻整个解题过程,下面哪一步想不懂了,就想 **那有同学问了,j怎么就不拆分呢?** -j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。 +j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j 取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); -那么从1遍历j,比较(i - j) * j和dp[i - j] * j 取最大的。 +也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。 + +如果定义dp[i - j] * dp[j] 也是默认将一个数强制拆成4份以及4份以上了。 + +所以递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j}); -递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j)); 3. dp的初始化 @@ -185,11 +190,38 @@ public: Java: - +```Java +class Solution { + public int integerBreak(int n) { + //dp[i]为正整数i拆分结果的最大乘积 + int[] dp = new int[n+1]; + dp[2] = 1; + for (int i = 3; i <= n; ++i) { + for (int j = 1; j < i - 1; ++j) { + //j*(i-j)代表把i拆分为j和i-j两个数相乘 + //j*dp[i-j]代表把i拆分成j和继续把(i-j)这个数拆分,取(i-j)拆分结果中的最大乘积与j相乘 + dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j])); + } + } + return dp[n]; + } +} +``` Python: - - +```python +class Solution: + def integerBreak(self, n: int) -> int: + dp = [0] * (n + 1) + dp[2] = 1 + for i in range(3, n + 1): + # 假设对正整数 i 拆分出的第一个正整数是 j(1 <= j < i),则有以下两种方案: + # 1) 将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j * (i-j) + # 2) 将 i 拆分成 j 和 i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j * dp[i-j] + for j in range(1, i - 1): + dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) + return dp[n] +``` Go: diff --git a/problems/0344.反转字符串.md b/problems/0344.反转字符串.md index b4c843b7..1b86e847 100644 --- a/problems/0344.反转字符串.md +++ b/problems/0344.反转字符串.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -157,7 +157,18 @@ class Solution { ``` Python: - +```python3 +class Solution: + def reverseString(self, s: List[str]) -> None: + """ + Do not return anything, modify s in-place instead. + """ + left, right = 0, len(s) - 1 + while(left < right): + s[left], s[right] = s[right], s[left] + left += 1 + right -= 1 +``` Go: ```Go @@ -172,6 +183,26 @@ func reverseString(s []byte) { } ``` +javaScript: + +```js +/** + * @param {character[]} s + * @return {void} Do not return anything, modify s in-place instead. + */ +var reverseString = function(s) { + return s.reverse(); +}; + +var reverseString = function(s) { + let l = -1, r = s.length; + while(++l < --r) [s[l], s[r]] = [s[r], s[l]]; + return s; +}; +``` + + + diff --git a/problems/0347.前K个高频元素.md b/problems/0347.前K个高频元素.md index 0e70f553..71af618e 100644 --- a/problems/0347.前K个高频元素.md +++ b/problems/0347.前K个高频元素.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -162,7 +162,33 @@ class Solution { Python: - +```python +#时间复杂度:O(nlogk) +#空间复杂度:O(n) +import heapq +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + #要统计元素出现频率 + map_ = {} #nums[i]:对应出现的次数 + for i in range(len(nums)): + map_[nums[i]] = map_.get(nums[i], 0) + 1 + + #对频率排序 + #定义一个小顶堆,大小为k + pri_que = [] #小顶堆 + + #用固定大小为k的小顶堆,扫面所有频率的数值 + for key, freq in map_.items(): + heapq.heappush(pri_que, (freq, key)) + if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k + heapq.heappop(pri_que) + + #找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒叙来输出到数组 + result = [0] * k + for i in range(k-1, -1, -1): + result[i] = heapq.heappop(pri_que)[1] + return result +``` Go: diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md index b5116ee1..090480a4 100644 --- a/problems/0349.两个数组的交集.md +++ b/problems/0349.两个数组的交集.md @@ -1,17 +1,18 @@

- + - +

-

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

+

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

> 如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费! -# 349. 两个数组的交集 + +## 349. 两个数组的交集 https://leetcode-cn.com/problems/intersection-of-two-arrays/ @@ -23,7 +24,7 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/ 输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序。 -# 思路 +## 思路 这道题目,主要要学会使用一种哈希数据结构:unordered_set,这个数据结构可以解决很多类似的问题。 @@ -31,7 +32,7 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/ 这道题用暴力的解法时间复杂度是O(n^2),那来看看使用哈希法进一步优化。 -那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig),[0383.赎金信](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ) +那么用数组来做哈希表也是不错的选择,例如[242. 有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA) 但是要注意,**使用数组来做哈希的题目,是因为题目都限制了数值的大小。** @@ -52,6 +53,7 @@ std::set和std::multiset底层实现都是红黑树,std::unordered_set的底 ![set哈希法](https://img-blog.csdnimg.cn/2020080918570417.png) C++代码如下: + ```C++ class Solution { public: @@ -69,6 +71,13 @@ public: }; ``` +## 拓展 + +那有同学可能问了,遇到哈希问题我直接都用set不就得了,用什么数组啊。 + +直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。 + +不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。 ## 其他语言版本 @@ -109,11 +118,70 @@ class Solution { ``` Python: +```python3 +class Solution: + def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]: + result_set = set() + + set1 = set(nums1) + for num in nums2: + if num in set1: + result_set.add(num) # set1里出现的nums2元素 存放到结果 + return result_set +``` Go: +```go +func intersection(nums1 []int, nums2 []int) []int { + m := make(map[int]int) + for _, v := range nums1 { + m[v] = 1 + } + var res []int + // 利用count>0,实现重复值只拿一次放入返回结果中 + for _, v := range nums2 { + if count, ok := m[v]; ok && count > 0 { + res = append(res, v) + m[v]-- + } + } + return res +} +``` + +javaScript: + +```js +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @return {number[]} + */ +var intersection = function(nums1, nums2) { + // 根据数组大小交换操作的数组 + if(nums1.length < nums2.length) { + const _ = nums1; + nums1 = nums2; + nums2 = _; + } + const nums1Set = new Set(nums1); + const resSet = new Set(); + // for(const n of nums2) { + // nums1Set.has(n) && resSet.add(n); + // } + // 循环 比 迭代器快 + for(let i = nums2.length - 1; i >= 0; i--) { + nums1Set.has(nums2[i]) && resSet.add(nums2[i]); + } + return Array.from(resSet); +}; +``` +## 相关题目 + +* 350.两个数组的交集 II ----------------------- diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index fa90142d..4d283eb0 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 本周讲解了[贪心理论基础](https://mp.weixin.qq.com/s/O935TaoHE9Eexwe_vSbRAg),以及第一道贪心的题目:[贪心算法:分发饼干](https://mp.weixin.qq.com/s/YSuLIAYyRGlyxbp9BNC1uw),可能会给大家一种贪心算法比较简单的错觉,好了,接下来几天的题目难度要上来了,哈哈。 @@ -138,12 +138,38 @@ class Solution { ``` Python: - +```python3 +class Solution: + def wiggleMaxLength(self, nums: List[int]) -> int: + preC,curC,res = 0,0,1 #题目里nums长度大于等于1,当长度为1时,其实到不了for循环里去,所以不用考虑nums长度 + for i in range(len(nums) - 1): + curC = nums[i + 1] - nums[i] + if curC * preC <= 0 and curC !=0: #差值为0时,不算摆动 + res += 1 + preC = curC #如果当前差值和上一个差值为一正一负时,才需要用当前差值替代上一个差值 + return res +``` Go: - +Javascript: +```Javascript +var wiggleMaxLength = function(nums) { + if(nums.length <= 1) return nums.length + let result = 1 + let preDiff = 0 + let curDiff = 0 + for(let i = 0; i <= nums.length; i++) { + curDiff = nums[i + 1] - nums[i] + if((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) { + result++ + preDiff = curDiff + } + } + return result +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index eca9a13b..d0394706 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:Carl称它为排列总和! ## 377. 组合总和 Ⅳ @@ -163,7 +163,7 @@ class Solution { return dp[target]; } } - +``` Python: @@ -183,7 +183,23 @@ class Solution: Go: - +```go +func combinationSum4(nums []int, target int) int { + //定义dp数组 + dp := make([]int, target+1) + // 初始化 + dp[0] = 1 + // 遍历顺序, 先遍历背包,再循环遍历物品 + for j:=0;j<=target;j++ { + for i:=0 ;i < len(nums);i++ { + if j >= nums[i] { + dp[j] += dp[j-nums[i]] + } + } + } + return dp[target] +} +``` @@ -191,4 +207,4 @@ Go: * 作者微信:[程序员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/0383.赎金信.md b/problems/0383.赎金信.md index 742bfdc3..2f3c4f4d 100644 --- a/problems/0383.赎金信.md +++ b/problems/0383.赎金信.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -22,13 +22,13 @@ https://leetcode-cn.com/problems/ransom-note/ 你可以假设两个字符串均只含有小写字母。 -canConstruct("a", "b") -> false -canConstruct("aa", "ab") -> false -canConstruct("aa", "aab") -> true +canConstruct("a", "b") -> false +canConstruct("aa", "ab") -> false +canConstruct("aa", "aab") -> true -# 思路 +## 思路 -这道题目和[242.有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 +这道题目和[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 本题判断第一个字符串ransom能不能由第二个字符串magazines里面的字符构成,但是这里需要注意两点。 @@ -36,7 +36,7 @@ canConstruct("aa", "aab") -> true * 第二点 “你可以假设两个字符串均只含有小写字母。” *说明只有小写字母*,这一点很重要 -# 暴力解法 +## 暴力解法 那么第一个思路其实就是暴力枚举了,两层for循环,不断去寻找,代码如下: @@ -67,7 +67,7 @@ public: 这里时间复杂度是比较高的,而且里面还有一个字符串删除也就是erase的操作,也是费时的,当然这段代码也可以过这道题。 -# 哈希解法 +## 哈希解法 因为题目所只有小写字母,那可以采用空间换取时间的哈希策略, 用一个长度为26的数组还记录magazine里字母出现的次数。 @@ -75,7 +75,7 @@ public: 依然是数组在哈希法中的应用。 -一些同学可能想,用数组干啥,都用map完事了,**其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数。 所以数组更加简单直接有效!** +一些同学可能想,用数组干啥,都用map完事了,**其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!** 代码如下: @@ -105,8 +105,6 @@ public: - - ## 其他语言版本 @@ -138,10 +136,74 @@ class Solution { ``` Python: - +```py +class Solution(object): + def canConstruct(self, ransomNote, magazine): + """ + :type ransomNote: str + :type magazine: str + :rtype: bool + """ + + # use a dict to store the number of letter occurance in ransomNote + hashmap = dict() + for s in ransomNote: + if s in hashmap: + hashmap[s] += 1 + else: + hashmap[s] = 1 + + # check if the letter we need can be found in magazine + for l in magazine: + if l in hashmap: + hashmap[l] -= 1 + + for key in hashmap: + if hashmap[key] > 0: + return False + + return True +``` Go: +```go +func canConstruct(ransomNote string, magazine string) bool { + record := make([]int, 26) + for _, v := range magazine { + record[v-'a']++ + } + for _, v := range ransomNote { + record[v-'a']-- + if record[v-'a'] < 0 { + return false + } + } + return true +} +``` +javaScript: + +```js +/** + * @param {string} ransomNote + * @param {string} magazine + * @return {boolean} + */ +var canConstruct = function(ransomNote, magazine) { + const strArr = new Array(26).fill(0), + base = "a".charCodeAt(); + for(const s of magazine) { + strArr[s.charCodeAt() - base]++; + } + for(const s of ransomNote) { + const index = s.charCodeAt() - base; + if(!strArr[index]) return false; + strArr[index]--; + } + return true; +}; +``` diff --git a/problems/0392.判断子序列.md b/problems/0392.判断子序列.md index 95e0b124..9048ac44 100644 --- a/problems/0392.判断子序列.md +++ b/problems/0392.判断子序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 392.判断子序列 @@ -144,7 +144,20 @@ Java: Python: - +```python +class Solution: + def isSubsequence(self, s: str, t: str) -> bool: + dp = [[0] * (len(t)+1) for _ in range(len(s)+1)] + for i in range(1, len(s)+1): + for j in range(1, len(t)+1): + if s[i-1] == t[j-1]: + dp[i][j] = dp[i-1][j-1] + 1 + else: + dp[i][j] = dp[i][j-1] + if dp[-1][-1] == len(s): + return True + return False +``` Go: diff --git a/problems/0404.左叶子之和.md b/problems/0404.左叶子之和.md index 0290dccd..aa758367 100644 --- a/problems/0404.左叶子之和.md +++ b/problems/0404.左叶子之和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 404.左叶子之和 @@ -205,10 +205,139 @@ class Solution { 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 sumOfLeftLeaves(self, root: TreeNode) -> int: + self.res=0 + def areleftleaves(root): + if not root:return + if root.left and (not root.left.left) and (not root.left.right):self.res+=root.left.val + areleftleaves(root.left) + areleftleaves(root.right) + areleftleaves(root) + return self.res +``` Go: +> 递归法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func sumOfLeftLeaves(root *TreeNode) int { + var res int + findLeft(root,&res) + return res +} +func findLeft(root *TreeNode,res *int){ + //左节点 + if root.Left!=nil&&root.Left.Left==nil&&root.Left.Right==nil{ + *res=*res+root.Left.Val + } + if root.Left!=nil{ + findLeft(root.Left,res) + } + if root.Right!=nil{ + findLeft(root.Right,res) + } +} +``` + +> 迭代法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func sumOfLeftLeaves(root *TreeNode) int { + var res int + queue:=list.New() + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + for i:=0;i - diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index 2ef0a2fd..9aca5b94 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 406.根据身高重建队列 @@ -211,15 +211,44 @@ class Solution { ``` 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) + que = [] + for p in people: + if p[1] > len(que): + que.append(p) + else: + que.insert(p[1], p) + return que +``` Go: +Javascript: +```Javascript +var reconstructQueue = function(people) { + let queue = [] + people.sort((a, b ) => { + if(b[0] !== a[0]) { + return b[0] - a[0] + } else { + return a[1] - b[1] + } + + }) + for(let i = 0; i < people.length; i++) { + queue.splice(people[i][1], 0, people[i]) + } + return queue +}; +``` ----------------------- * 作者微信:[程序员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/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 789a7fb5..0d306c74 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -1,11 +1,10 @@

- + - +

-

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

-# 动态规划:分割等和子集可以用01背包! +

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

## 416. 分割等和子集 @@ -29,6 +28,10 @@ 输出: false 解释: 数组不能分割成两个元素和相等的子集. +提示: +* 1 <= nums.length <= 200 +* 1 <= nums[i] <= 100 + ## 思路 这道题目初步看,是如下两题几乎是一样的,大家可以用回溯法,解决如下两题 @@ -174,7 +177,7 @@ public: 这道题目就是一道01背包应用类的题目,需要我们拆解题目,然后套入01背包的场景。 -01背包相对于本题,主要要理解,题目中物品是nums[i],重量是nums[i]i,价值也是nums[i],背包体积是sum/2。 +01背包相对于本题,主要要理解,题目中物品是nums[i],重量是nums[i],价值也是nums[i],背包体积是sum/2。 看代码的话,就可以发现,基本就是按照01背包的写法来的。 @@ -222,8 +225,18 @@ class Solution { ``` Python: - - +```python +class Solution: + def canPartition(self, nums: List[int]) -> bool: + taraget = sum(nums) + if taraget % 2 == 1: return False + taraget //= 2 + dp = [0] * 10001 + for i in range(len(nums)): + for j in range(taraget, nums[i] - 1, -1): + dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) + return taraget == dp[taraget] +``` Go: diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index 4341f3b8..248526a1 100644 --- a/problems/0435.无重叠区间.md +++ b/problems/0435.无重叠区间.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 435. 无重叠区间 @@ -212,7 +212,19 @@ class Solution { ``` Python: - +```python +class Solution: + def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int: + if len(intervals) == 0: return 0 + intervals.sort(key=lambda x: x[1]) + count = 1 # 记录非交叉区间的个数 + end = intervals[0][1] # 记录区间分割点 + for i in range(1, len(intervals)): + if end <= intervals[i][0]: + count += 1 + end = intervals[i][1] + return len(intervals) - count +``` Go: @@ -223,4 +235,4 @@ Go: * 作者微信:[程序员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/0450.删除二叉搜索树中的节点.md b/problems/0450.删除二叉搜索树中的节点.md index 604fb376..3ed44c0a 100644 --- a/problems/0450.删除二叉搜索树中的节点.md +++ b/problems/0450.删除二叉搜索树中的节点.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 二叉搜索树删除节点就涉及到结构调整了 @@ -281,7 +281,43 @@ class Solution { ``` 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 deleteNode(self, root: TreeNode, key: int) -> TreeNode: + if not root: return root #第一种情况:没找到删除的节点,遍历到空节点直接返回了 + if root.val == key: + if not root.left and not root.right: #第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点 + del root + return None + if not root.left and root.right: #第三种情况:其左孩子为空,右孩子不为空,删除节点,右孩子补位 ,返回右孩子为根节点 + tmp = root + root = root.right + del tmp + return root + if root.left and not root.right: #第四种情况:其右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点 + tmp = root + root = root.left + del tmp + return root + else: #第五种情况:左右孩子节点都不为空,则将删除节点的左子树放到删除节点的右子树的最左面节点的左孩子的位置 + v = root.right + while v.left: + v = v.left + v.left = root.left + tmp = root + root = root.right + del tmp + return root + if root.val > key: root.left = self.deleteNode(root.left,key) #左递归 + if root.val < key: root.right = self.deleteNode(root.right,key) #右递归 + return root +``` Go: ```Go @@ -330,4 +366,4 @@ func deleteNode1(root *TreeNode)*TreeNode{ * 作者微信:[程序员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/0452.用最少数量的箭引爆气球.md b/problems/0452.用最少数量的箭引爆气球.md index f62fb153..bb3ebbdc 100644 --- a/problems/0452.用最少数量的箭引爆气球.md +++ b/problems/0452.用最少数量的箭引爆气球.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 452. 用最少数量的箭引爆气球 @@ -70,7 +70,7 @@ 其实都可以!只不过对应的遍历顺序不同,我就按照气球的起始位置排序了。 -既然按照其实位置排序,那么就从前向后遍历气球数组,靠左尽可能让气球重复。 +既然按照起始位置排序,那么就从前向后遍历气球数组,靠左尽可能让气球重复。 从前向后遍历遇到重叠的气球了怎么办? @@ -142,16 +142,8 @@ Java: ```java class Solution { public int findMinArrowShots(int[][] points) { - Arrays.sort(points, new Comparator() { - @Override - public int compare(int[] o1, int[] o2) { - if (o1[0] != o2[0]) { - return Integer.compare(o1[0],o2[0]); - } else { - return Integer.compare(o1[0],o2[0]); - } - } - }); + if (points.length == 0) return 0; + Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[0])); int count = 1; for (int i = 1; i < points.length; i++) { @@ -167,15 +159,44 @@ class Solution { ``` Python: - +```python +class Solution: + def findMinArrowShots(self, points: List[List[int]]) -> int: + if len(points) == 0: return 0 + points.sort(key=lambda x: x[0]) + result = 1 + for i in range(1, len(points)): + if points[i][0] > points[i - 1][1]: # 气球i和气球i-1不挨着,注意这里不是>= + result += 1 + else: + points[i][1] = min(points[i - 1][1], points[i][1]) # 更新重叠气球最小右边界 + return result +``` Go: +Javascript: +```Javascript +var findMinArrowShots = function(points) { + points.sort((a, b) => { + return a[0] - b[0] + }) + let result = 1 + for(let i = 1; i < points.length; i++) { + if(points[i][0] > points[i - 1][1]) { + result++ + } else { + points[i][1] = Math.min(points[i - 1][1], points[i][1]) + } + } + return result +}; +``` ----------------------- * 作者微信:[程序员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/0454.四数相加II.md b/problems/0454.四数相加II.md index a2ef928b..835178dd 100644 --- a/problems/0454.四数相加II.md +++ b/problems/0454.四数相加II.md @@ -1,16 +1,17 @@

- + - +

-

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

+

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

> 需要哈希的地方都能找到map的身影 # 第454题.四数相加II + https://leetcode-cn.com/problems/4sum-ii/ 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。 @@ -24,10 +25,8 @@ A = [ 1, 2] B = [-2,-1] C = [-1, 2] D = [ 0, 2] - 输出: 2 - **解释:** 两个元组如下: 1. (0, 0, 0, 1) -> A[0] + B[0] + C[0] + D[1] = 1 + (-2) + (-1) + 2 = 0 @@ -121,8 +120,88 @@ class Solution { Python: +``` +class Solution(object): + def fourSumCount(self, nums1, nums2, nums3, nums4): + """ + :type nums1: List[int] + :type nums2: List[int] + :type nums3: List[int] + :type nums4: List[int] + :rtype: int + """ + # use a dict to store the elements in nums1 and nums2 and their sum + hashmap = dict() + for n1 in nums1: + for n2 in nums2: + if n1 + n2 in hashmap: + hashmap[n1+n2] += 1 + else: + hashmap[n1+n2] = 1 + + # if the -(a+b) exists in nums3 and nums4, we shall add the count + count = 0 + for n3 in nums3: + for n4 in nums4: + key = - n3 - n4 + if key in hashmap: + count += hashmap[key] + return count + + +``` + Go: +```go +func fourSumCount(nums1 []int, nums2 []int, nums3 []int, nums4 []int) int { + m := make(map[int]int) + count := 0 + for _, v1 := range nums1 { + for _, v2 := range nums2 { + m[v1+v2]++ + } + } + for _, v3 := range nums3 { + for _, v4 := range nums4 { + count += m[-v3-v4] + } + } + return count +} +``` + +javaScript: + +```js +/** + * @param {number[]} nums1 + * @param {number[]} nums2 + * @param {number[]} nums3 + * @param {number[]} nums4 + * @return {number} + */ +var fourSumCount = function(nums1, nums2, nums3, nums4) { + const twoSumMap = new Map(); + let count = 0; + + for(const n1 of nums1) { + for(const n2 of nums2) { + const sum = n1 + n2; + twoSumMap.set(sum, (twoSumMap.get(sum) || 0) + 1) + } + } + + for(const n3 of nums3) { + for(const n4 of nums4) { + const sum = n3 + n4; + count += (twoSumMap.get(0 - sum) || 0) + } + } + + return count; +}; +``` diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md index f57a7fa6..4814d414 100644 --- a/problems/0455.分发饼干.md +++ b/problems/0455.分发饼干.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 455.分发饼干 @@ -134,15 +134,41 @@ class Solution { ``` Python: - - +```python3 +class Solution: + def findContentChildren(self, g: List[int], s: List[int]) -> int: + g.sort() + s.sort() + res = 0 + for i in range(len(s)): + if res = g[res]: #小饼干先喂饱小胃口 + res += 1 + return res +``` Go: +Javascript: +```Javascript +var findContentChildren = function(g, s) { + g = g.sort((a, b) => a - b) + s = s.sort((a, b) => a - b) + let result = 0 + let index = s.length - 1 + for(let i = g.length - 1; i >= 0; i--) { + if(index >= 0 && s[index] >= g[i]) { + result++ + index-- + } + } + return result +}; + +``` ----------------------- * 作者微信:[程序员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/0459.重复的子字符串.md b/problems/0459.重复的子字符串.md index 5abc999a..deb755bf 100644 --- a/problems/0459.重复的子字符串.md +++ b/problems/0459.重复的子字符串.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -149,15 +149,151 @@ public: ## 其他语言版本 - Java: +```java +class Solution { + public boolean repeatedSubstringPattern(String s) { + if (s.equals("")) return false; + + int len = s.length(); + // 原串加个空格(哨兵),使下标从1开始,这样j从0开始,也不用初始化了 + s = " " + s; + char[] chars = s.toCharArray(); + int[] next = new int[len + 1]; + + // 构造 next 数组过程,j从0开始(空格),i从2开始 + for (int i = 2, j = 0; i <= len; i++) { + // 匹配不成功,j回到前一位置 next 数组所对应的值 + while (j > 0 && chars[i] != chars[j + 1]) j = next[j]; + // 匹配成功,j往后移 + if (chars[i] == chars[j + 1]) j++; + // 更新 next 数组的值 + next[i] = j; + } + + // 最后判断是否是重复的子字符串,这里 next[len] 即代表next数组末尾的值 + if (next[len] > 0 && len % (len - next[len]) == 0) { + return true; + } + return false; + } +} +``` + Python: +这里使用了前缀表统一减一的实现方式 + +```python +class Solution: + def repeatedSubstringPattern(self, s: str) -> bool: + if len(s) == 0: + return False + nxt = [0] * len(s) + self.getNext(nxt, s) + if nxt[-1] != -1 and len(s) % (len(s) - (nxt[-1] + 1)) == 0: + return True + return False + + def getNext(self, nxt, s): + nxt[0] = -1 + j = -1 + for i in range(1, len(s)): + while j >= 0 and s[i] != s[j+1]: + j = nxt[j] + if s[i] == s[j+1]: + j += 1 + nxt[i] = j + return nxt +``` + +前缀表(不减一)的代码实现 + +```python +class Solution: + def repeatedSubstringPattern(self, s: str) -> bool: + if len(s) == 0: + return False + nxt = [0] * len(s) + self.getNext(nxt, s) + if nxt[-1] != 0 and len(s) % (len(s) - nxt[-1]) == 0: + return True + return False + + def getNext(self, nxt, s): + nxt[0] = 0 + j = 0 + for i in range(1, len(s)): + while j > 0 and s[i] != s[j]: + j = nxt[j - 1] + if s[i] == s[j]: + j += 1 + nxt[i] = j + return nxt +``` Go: +这里使用了前缀表统一减一的实现方式 + +```go +func repeatedSubstringPattern(s string) bool { + n := len(s) + if n == 0 { + return false + } + next := make([]int, n) + j := -1 + next[0] = j + for i := 1; i < n; i++ { + for j >= 0 && s[i] != s[j+1] { + j = next[j] + } + if s[i] == s[j+1] { + j++ + } + next[i] = j + } + // next[n-1]+1 最长相同前后缀的长度 + if next[n-1] != -1 && n%(n-(next[n-1]+1)) == 0 { + return true + } + return false +} +``` + +前缀表(不减一)的代码实现 + +```go +func repeatedSubstringPattern(s string) bool { + n := len(s) + if n == 0 { + return false + } + j := 0 + next := make([]int, n) + next[0] = j + for i := 1; i < n; i++ { + for j > 0 && s[i] != s[j] { + j = next[j-1] + } + if s[i] == s[j] { + j++ + } + next[i] = j + } + // next[n-1] 最长相同前后缀的长度 + if next[n-1] != 0 && n%(n-next[n-1]) == 0 { + return true + } + return false +} +``` + + + @@ -165,4 +301,4 @@ Go: * 作者微信:[程序员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 4ec5fd4d..fd925f76 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:一和零! ## 474.一和零 @@ -190,10 +190,59 @@ class Solution { ``` Python: - +```python3 +class Solution: + def findMaxForm(self, strs: List[str], m: int, n: int) -> int: + dp = [[0] * (n + 1) for _ in range(m + 1)] # 默认初始化0 + # 遍历物品 + for str in strs: + ones = str.count('1') + zeros = str.count('0') + # 遍历背包容量且从后向前遍历! + for i in range(m, zeros - 1, -1): + for j in range(n, ones - 1, -1): + dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1) + return dp[m][n] +``` Go: +```go +func findMaxForm(strs []string, m int, n int) int { + // 定义数组 + dp := make([][]int, m+1) + for i,_ := range dp { + dp[i] = make([]int, n+1 ) + } + // 遍历 + for i:=0;i= zeroNum;j-- { + for k:=n ; k >= oneNum;k-- { + // 推导公式 + dp[j][k] = max(dp[j][k],dp[j-zeroNum][k-oneNum]+1) + } + } + //fmt.Println(dp) + } + return dp[m][n] +} +func max(a,b int) int { + if a > b { + return a + } + return b +} +``` diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 5e36d6be..5538a2c9 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 和子集问题有点像,但又处处是陷阱 @@ -229,10 +229,59 @@ class Solution { Python: - +```python3 +class Solution: + def findSubsequences(self, nums: List[int]) -> List[List[int]]: + res = [] + path = [] + def backtrack(nums,startIndex): + repeat = [] #这里使用数组来进行去重操作 + if len(path) >=2: + res.append(path[:]) #注意这里不要加return,要取树上的节点 + for i in range(startIndex,len(nums)): + if nums[i] in repeat: + continue + if len(path) >= 1: + if nums[i] < path[-1]: + continue + repeat.append(nums[i]) #记录这个元素在本层用过了,本层后面不能再用了 + path.append(nums[i]) + backtrack(nums,i+1) + path.pop() + backtrack(nums,0) + return res +``` Go: +Javascript: + +```Javascript + +var findSubsequences = function(nums) { + let result = [] + let path = [] + function backtracing(startIndex) { + if(path.length > 1) { + result.push(path.slice()) + } + let uset = [] + for(let i = startIndex; i < nums.length; i++) { + if((path.length > 0 && nums[i] < path[path.length - 1]) || uset[nums[i] + 100]) { + continue + } + uset[nums[i] + 100] = true + path.push(nums[i]) + backtracing(i + 1) + path.pop() + } + } + backtracing(0) + return result +}; + +``` + @@ -240,4 +289,4 @@ Go: * 作者微信:[程序员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/0494.目标和.md b/problems/0494.目标和.md index 1782c88c..c917ed5c 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:目标和! ## 494. 目标和 @@ -19,15 +19,15 @@ 示例: -输入:nums: [1, 1, 1, 1, 1], S: 3 -输出:5 -解释: +输入:nums: [1, 1, 1, 1, 1], S: 3 +输出:5 --1+1+1+1+1 = 3 -+1-1+1+1+1 = 3 -+1+1-1+1+1 = 3 -+1+1+1-1+1 = 3 -+1+1+1+1-1 = 3 +解释: +-1+1+1+1+1 = 3 ++1-1+1+1+1 = 3 ++1+1-1+1+1 = 3 ++1+1+1-1+1 = 3 ++1+1+1+1-1 = 3 一共有5种方法让最终目标和为3。 @@ -150,7 +150,7 @@ dp[j] 表示:填满j(包括j)这么大容积的包,有dp[i]种方法 有哪些来源可以推出dp[j]呢? -不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]中方法。 +不考虑nums[i]的情况下,填满容量为j - nums[i]的背包,有dp[j - nums[i]]种方法。 那么只要搞到nums[i]的话,凑成dp[j]就有dp[j - nums[i]] 种方法。 @@ -225,7 +225,7 @@ public: 是的,如果仅仅是求个数的话,就可以用dp,但[回溯算法:39. 组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)要求的是把所有组合列出来,还是要使用回溯法爆搜的。 -本地还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为: +本题还是有点难度,大家也可以记住,在求装满背包有几种方法的情况下,递推公式一般为: ``` dp[j] += dp[j - nums[i]]; @@ -261,10 +261,50 @@ class Solution { ``` Python: - +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + sumValue = sum(nums) + if target > sumValue or (sumValue + target) % 2 == 1: return 0 + bagSize = (sumValue + target) // 2 + dp = [0] * (bagSize + 1) + dp[0] = 1 + for i in range(len(nums)): + for j in range(bagSize, nums[i] - 1, -1): + dp[j] += dp[j - nums[i]] + return dp[bagSize] +``` Go: - +```go +func findTargetSumWays(nums []int, target int) int { + sum := 0 + for _, v := range nums { + sum += v + } + if target > sum { + return 0 + } + if (sum+target)%2 == 1 { + return 0 + } + // 计算背包大小 + bag := (sum + target) / 2 + // 定义dp数组 + dp := make([]int, bag+1) + // 初始化 + dp[0] = 1 + // 遍历顺序 + for i := 0; i < len(nums); i++ { + for j := bag; j >= nums[i]; j-- { + //推导公式 + dp[j] += dp[j-nums[i]] + //fmt.Println(dp) + } + } + return dp[bag] +} +``` @@ -272,4 +312,4 @@ Go: * 作者微信:[程序员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/0501.二叉搜索树中的众数.md b/problems/0501.二叉搜索树中的众数.md index 385ce2f1..dfd589ce 100644 --- a/problems/0501.二叉搜索树中的众数.md +++ b/problems/0501.二叉搜索树中的众数.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 二叉树上应该怎么求,二叉搜索树上又应该怎么求? @@ -394,8 +394,39 @@ class Solution { ``` 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 findMode(self, root: TreeNode) -> List[int]: + if not root: return + self.pre = root + self.count = 0 //统计频率 + self.countMax = 0 //最大频率 + self.res = [] + def findNumber(root): + if not root: return None // 第一个节点 + findNumber(root.left) //左 + if self.pre.val == root.val: //中: 与前一个节点数值相同 + self.count += 1 + else: // 与前一个节点数值不同 + self.pre = root + self.count = 1 + if self.count > self.countMax: // 如果计数大于最大值频率 + self.countMax = self.count // 更新最大频率 + self.res = [root.val] //更新res + elif self.count == self.countMax: // 如果和最大值相同,放进res中 + self.res.append(root.val) + findNumber(root.right) //右 + return + findNumber(root) + return self.res +``` Go: @@ -405,4 +436,4 @@ Go: * 作者微信:[程序员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/0509.斐波那契数.md b/problems/0509.斐波那契数.md index 8537ed8b..cf004b50 100644 --- a/problems/0509.斐波那契数.md +++ b/problems/0509.斐波那契数.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 509. 斐波那契数 @@ -171,12 +171,55 @@ public: Java: - +```Java +class Solution { + public int fib(int n) { + if (n < 2) return n; + int a = 0, b = 1, c = 0; + for (int i = 1; i < n; i++) { + c = a + b; + a = b; + b = c; + } + return c; + } +} +``` Python: +```python3 +class Solution: + def fib(self, n: int) -> int: + if n < 2: + return n + a, b, c = 0, 1, 0 + for i in range(1, n): + c = a + b + a, b = b, c + return c +# 递归实现 +class Solution: + def fib(self, n: int) -> int: + if n < 2: + return n + return self.fib(n - 1) + self.fib(n - 2) +``` Go: +```Go +func fib(n int) int { + if n < 2 { + return n + } + a, b, c := 0, 1, 0 + for i := 1; i < n; i++ { + c = a + b + a, b = b, c + } + return c +} +``` diff --git a/problems/0513.找树左下角的值.md b/problems/0513.找树左下角的值.md index ac21dc68..e252ccd3 100644 --- a/problems/0513.找树左下角的值.md +++ b/problems/0513.找树左下角的值.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 513.找树左下角的值 @@ -274,10 +274,151 @@ class Solution { 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 findBottomLeftValue(self, root: TreeNode) -> int: + depth=0 + self.res=[] + def level(root,depth): + if not root:return + if depth==len(self.res): + self.res.append([]) + self.res[depth].append(root.val) + level(root.left,depth+1) + level(root.right,depth+1) + level(root,depth) + return self.res[-1][0] +``` Go: +> 递归法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + var maxDeep int // 全局变量 深度 + var value int //全局变量 最终值 +func findBottomLeftValue(root *TreeNode) int { + if root.Left==nil&&root.Right==nil{//需要提前判断一下(不要这个if的话提交结果会出错,但执行代码不会。防止这种情况出现,故先判断是否只有一个节点) + return root.Val + } + findLeftValue (root,maxDeep) + return value +} +func findLeftValue (root *TreeNode,deep int){ + //最左边的值在左边 + if root.Left==nil&&root.Right==nil{ + if deep>maxDeep{ + value=root.Val + maxDeep=deep + } + } + //递归 + if root.Left!=nil{ + deep++ + findLeftValue(root.Left,deep) + deep--//回溯 + } + if root.Right!=nil{ + deep++ + findLeftValue(root.Right,deep) + deep--//回溯 + } +} +``` + +> 迭代法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func findBottomLeftValue(root *TreeNode) int { + queue:=list.New() + var gradation int + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + for i:=0;imaxPath){ + maxPath = curPath; + resNode = node.val; + } + // return ; + } + node.left&&dfsTree(node.left,curPath+1); + node.right&&dfsTree(node.right,curPath+1); + } + dfsTree(root,1); + return resNode; +}; +``` +2. 层序遍历 +```javascript +var findBottomLeftValue = function(root) { + //考虑层序遍历 记录最后一行的第一个节点 + let queue = []; + if(root===null){ + return null; + } + queue.push(root); + let resNode; + while(queue.length){ + let length = queue.length; + for(let i=0; i \ No newline at end of file +
diff --git a/problems/0516.最长回文子序列.md b/problems/0516.最长回文子序列.md index d7acef99..a4f0522f 100644 --- a/problems/0516.最长回文子序列.md +++ b/problems/0516.最长回文子序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 516.最长回文子序列 题目链接:https://leetcode-cn.com/problems/longest-palindromic-subsequence/ @@ -170,9 +170,49 @@ public class Solution { Python: - +```python +class Solution: + def longestPalindromeSubseq(self, s: str) -> int: + dp = [[0] * len(s) for _ in range(len(s))] + for i in range(len(s)): + dp[i][i] = 1 + for i in range(len(s)-1, -1, -1): + for j in range(i+1, len(s)): + if s[i] == s[j]: + dp[i][j] = dp[i+1][j-1] + 2 + else: + dp[i][j] = max(dp[i+1][j], dp[i][j-1]) + return dp[0][-1] +``` Go: +```Go +func longestPalindromeSubseq(s string) int { + lenth:=len(s) + dp:=make([][]int,lenth) + for i:=0;i=0;i--{ + for j:=i+1;j - + - +

-

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

+

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

# 动态规划:给你一些零钱,你要怎么凑? ## 518. 零钱兑换 II @@ -33,7 +33,7 @@ 示例 3: 输入: amount = 10, coins = [10] 输出: 1 -  + 注意,你可以假设: * 0 <= amount (总金额) <= 5000 @@ -101,7 +101,7 @@ dp[j] (考虑coins[i]的组合总和) 就是所有的dp[j - coins[i]](不 而本题要求凑成总和的组合数,元素之间要求没有顺序。 -所以纯完全背包是能凑成总结就行,不用管怎么凑的。 +所以纯完全背包是能凑成总和就行,不用管怎么凑的。 本题是求凑出来的方案个数,且每个方案个数是为组合数。 @@ -208,10 +208,42 @@ class Solution { Python: -Go: +```python3 +class Solution: + def change(self, amount: int, coins: List[int]) -> int: + dp = [0]*(amount + 1) + dp[0] = 1 + # 遍历物品 + for i in range(len(coins)): + # 遍历背包 + for j in range(coins[i], amount + 1): + dp[j] += dp[j - coins[i]] + return dp[amount] +``` +Go: +```go +func change(amount int, coins []int) int { + // 定义dp数组 + dp := make([]int, amount+1) + // 初始化,0大小的背包, 当然是不装任何东西了, 就是1种方法 + dp[0] = 1 + // 遍历顺序 + // 遍历物品 + for i := 0 ;i < len(coins);i++ { + // 遍历背包 + for j:= coins[i] ; j <= amount ;j++ { + // 推导公式 + dp[j] += dp[j-coins[i]] + } + } + return dp[amount] +} +``` + + ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md index 903ebf78..03c48d9c 100644 --- a/problems/0530.二叉搜索树的最小绝对差.md +++ b/problems/0530.二叉搜索树的最小绝对差.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 利用二叉搜索树的特性搞起! @@ -151,7 +151,30 @@ public: Java: - +递归 +```java +class Solution { + TreeNode pre;// 记录上一个遍历的结点 + int result = Integer.MAX_VALUE; + public int getMinimumDifference(TreeNode root) { + if(root==null)return 0; + traversal(root); + return result; + } + public void traversal(TreeNode root){ + if(root==null)return; + //左 + traversal(root.left); + //中 + if(pre!=null){ + result = Math.min(result,root.val-pre.val); + } + pre = root; + //右 + traversal(root.right); + } +} +``` ```Java class Solution { TreeNode pre;// 记录上一个遍历的结点 @@ -177,8 +200,29 @@ class Solution { ``` 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 getMinimumDifference(self, root: TreeNode) -> int: + res = [] + r = float("inf") + def buildaList(root): //把二叉搜索树转换成有序数组 + if not root: return None + if root.left: buildaList(root.left) //左 + res.append(root.val) //中 + if root.right: buildaList(root.right) //右 + return res + + buildaList(root) + for i in range(len(res)-1): // 统计有序数组的最小差值 + r = min(abs(res[i]-res[i+1]),r) + return r +``` Go: @@ -188,4 +232,4 @@ Go: * 作者微信:[程序员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/0538.把二叉搜索树转换为累加树.md b/problems/0538.把二叉搜索树转换为累加树.md index 209c989b..765e78d3 100644 --- a/problems/0538.把二叉搜索树转换为累加树.md +++ b/problems/0538.把二叉搜索树转换为累加树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 538.把二叉搜索树转换为累加树 @@ -196,8 +196,26 @@ class Solution { ``` 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 convertBST(self, root: TreeNode) -> TreeNode: + def buildalist(root): + if not root: return None + buildalist(root.right) #右中左遍历 + root.val += self.pre + self.pre = root.val + buildalist(root.left) + self.pre = 0 #记录前一个节点的数值 + buildalist(root) + return root +``` Go: @@ -207,4 +225,4 @@ Go: * 作者微信:[程序员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/0541.反转字符串II.md b/problems/0541.反转字符串II.md index 0a92ac8b..00581fc0 100644 --- a/problems/0541.反转字符串II.md +++ b/problems/0541.反转字符串II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -106,27 +106,24 @@ Java: class Solution { public String reverseStr(String s, int k) { StringBuffer res = new StringBuffer(); - - for (int i = 0; i < s.length(); i += (2 * k)) { + int length = s.length(); + int start = 0; + while (start < length) { + // 找到k处和2k处 StringBuffer temp = new StringBuffer(); - // 剩余字符大于 k 个,每隔 2k 个字符的前 k 个字符进行反转 - if (i + k <= s.length()) { - // 反转前 k 个字符 - temp.append(s.substring(i, i + k)); - res.append(temp.reverse()); + // 与length进行判断,如果大于length了,那就将其置为length + int firstK = (start + k > length) ? length : start + k; + int secondK = (start + (2 * k) > length) ? length : start + (2 * k); - // 反转完前 k 个字符之后,如果紧接着还有 k 个字符,则直接加入这 k 个字符 - if (i + 2 * k <= s.length()) { - res.append(s.substring(i + k, i + 2 * k)); - // 不足 k 个字符,则直接加入剩下所有字符 - } else { - res.append(s.substring(i + k, s.length())); - } - continue; - } - // 剩余字符少于 k 个,则将剩余字符全部反转。 - temp.append(s.substring(i, s.length())); + //无论start所处位置,至少会反转一次 + temp.append(s.substring(start, firstK)); res.append(temp.reverse()); + + // 如果firstK到secondK之间有元素,这些元素直接放入res里即可。 + if (firstK < secondK) { //此时剩余长度一定大于k。 + res.append(s.substring(firstK, secondK)); + } + start += (2 * k); } return res.toString(); } @@ -134,10 +131,84 @@ class Solution { ``` Python: +```python + +class Solution(object): + def reverseStr(self, s, k): + """ + :type s: str + :type k: int + :rtype: str + """ + from functools import reduce + # turn s into a list + s = list(s) + + # another way to simply use a[::-1], but i feel this is easier to understand + def reverse(s): + left, right = 0, len(s) - 1 + while left < right: + s[left], s[right] = s[right], s[left] + left += 1 + right -= 1 + return s + + # make sure we reverse each 2k elements + for i in range(0, len(s), 2*k): + s[i:(i+k)] = reverse(s[i:(i+k)]) + + # combine list into str. + return reduce(lambda a, b: a+b, s) + +``` Go: +```go +func reverseStr(s string, k int) string { + ss := []byte(s) + length := len(s) + for i := 0; i < length; i += 2 * k { + if i + k <= length { + reverse(ss[i:i+k]) + } else { + reverse(ss[i:length]) + } + } + return string(ss) +} +func reverse(b []byte) { + left := 0 + right := len(b) - 1 + for left < right { + b[left], b[right] = b[right], b[left] + left++ + right-- + } +} +``` + +javaScript: + +```js + +/** + * @param {string} s + * @param {number} k + * @return {string} + */ +var reverseStr = function(s, k) { + const len = s.length; + let resArr = s.split(""); + for(let i = 0; i < len; i += 2 * k) { + let l = i - 1, r = i + k > len ? len : i + k; + while(++l < --r) [resArr[l], resArr[r]] = [resArr[r], resArr[l]]; + } + return resArr.join(""); +}; + +``` diff --git a/problems/0583.两个字符串的删除操作.md b/problems/0583.两个字符串的删除操作.md index 9679347a..0b32d129 100644 --- a/problems/0583.两个字符串的删除操作.md +++ b/problems/0583.两个字符串的删除操作.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 583. 两个字符串的删除操作 @@ -107,7 +107,22 @@ Java: Python: - +```python +class Solution: + def minDistance(self, word1: str, word2: str) -> int: + dp = [[0] * (len(word2)+1) for _ in range(len(word1)+1)] + for i in range(len(word1)+1): + dp[i][0] = i + for j in range(len(word2)+1): + dp[0][j] = j + for i in range(1, len(word1)+1): + for j in range(1, len(word2)+1): + if word1[i-1] == word2[j-1]: + dp[i][j] = dp[i-1][j-1] + else: + dp[i][j] = min(dp[i-1][j-1] + 2, dp[i-1][j] + 1, dp[i][j-1] + 1) + return dp[-1][-1] +``` Go: diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md index 848454de..d65275f0 100644 --- a/problems/0617.合并二叉树.md +++ b/problems/0617.合并二叉树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 617.合并二叉树 @@ -312,7 +312,23 @@ class Solution { ``` 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 mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: + if not root1: return root2 // 如果t1为空,合并之后就应该是t2 + if not root2: return root1 // 如果t2为空,合并之后就应该是t1 + root1.val = root1.val + root2.val //中 + root1.left = self.mergeTrees(root1.left , root2.left) //左 + root1.right = self.mergeTrees(root1.right , root2.right) //右 + return root1 //root1修改了结构和数值 +``` Go: @@ -323,4 +339,4 @@ Go: * 作者微信:[程序员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/0647.回文子串.md b/problems/0647.回文子串.md index fbc9133e..31734bbc 100644 --- a/problems/0647.回文子串.md +++ b/problems/0647.回文子串.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 647. 回文子串 @@ -219,14 +219,147 @@ public: ## 其他语言版本 - Java: +动态规划: + +```java +class Solution { + public int countSubstrings(String s) { + int len, ans = 0; + if (s == null || (len = s.length()) < 1) return 0; + //dp[i][j]:s字符串下标i到下标j的字串是否是一个回文串,即s[i, j] + boolean[][] dp = new boolean[len][len]; + for (int j = 0; j < len; j++) { + for (int i = 0; i <= j; i++) { + //当两端字母一样时,才可以两端收缩进一步判断 + if (s.charAt(i) == s.charAt(j)) { + //i++,j--,即两端收缩之后i,j指针指向同一个字符或者i超过j了,必然是一个回文串 + if (j - i < 3) { + dp[i][j] = true; + } else { + //否则通过收缩之后的字串判断 + dp[i][j] = dp[i + 1][j - 1]; + } + } else {//两端字符不一样,不是回文串 + dp[i][j] = false; + } + } + } + //遍历每一个字串,统计回文串个数 + for (int i = 0; i < len; i++) { + for (int j = 0; j < len; j++) { + if (dp[i][j]) ans++; + } + } + return ans; + } +} +``` + +中心扩散法: + +```java +class Solution { + public int countSubstrings(String s) { + int len, ans = 0; + if (s == null || (len = s.length()) < 1) return 0; + //总共有2 * len - 1个中心点 + for (int i = 0; i < 2 * len - 1; i++) { + //通过遍历每个回文中心,向两边扩散,并判断是否回文字串 + //有两种情况,left == right,right = left + 1,这两种回文中心是不一样的 + int left = i / 2, right = left + i % 2; + while (left >= 0 && right < len && s.charAt(left) == s.charAt(right)) { + //如果当前是一个回文串,则记录数量 + ans++; + left--; + right++; + } + } + return ans; + } +} +``` + Python: +> 动态规划: +```python +class Solution: + def countSubstrings(self, s: str) -> int: + dp = [[False] * len(s) for _ in range(len(s))] + result = 0 + for i in range(len(s)-1, -1, -1): #注意遍历顺序 + for j in range(i, len(s)): + if s[i] == s[j]: + if j - i <= 1: #情况一 和 情况二 + result += 1 + dp[i][j] = True + elif dp[i+1][j-1]: #情况三 + result += 1 + dp[i][j] = True + return result +``` + +> 动态规划:简洁版 +```python +class Solution: + def countSubstrings(self, s: str) -> int: + dp = [[False] * len(s) for _ in range(len(s))] + result = 0 + for i in range(len(s)-1, -1, -1): #注意遍历顺序 + for j in range(i, len(s)): + if s[i] == s[j] and (j - i <= 1 or dp[i+1][j-1]): + result += 1 + dp[i][j] = True + return result +``` + +> 双指针法: +```python +class Solution: + def countSubstrings(self, s: str) -> int: + result = 0 + for i in range(len(s)): + result += self.extend(s, i, i, len(s)) #以i为中心 + result += self.extend(s, i, i+1, len(s)) #以i和i+1为中心 + return result + + def extend(self, s, i, j, n): + res = 0 + while i >= 0 and j < n and s[i] == s[j]: + i -= 1 + j += 1 + res += 1 + return res +``` Go: +```Go +func countSubstrings(s string) int { + res:=0 + dp:=make([][]bool,len(s)) + for i:=0;i=0;i--{ + for j:=i;j - + - +

-

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

+

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

## 654.最大二叉树 @@ -256,7 +256,25 @@ class Solution { ``` 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 constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: + if not nums: return None //终止条件 + root = TreeNode(max(nums)) //新建节点 + p = nums.index(root.val) //找到最大值位置 + if p > 0: //保证有左子树 + root.left = self.constructMaximumBinaryTree(nums[:p]) //递归 + if p < len(nums): //保证有右子树 + root.right = self.constructMaximumBinaryTree(nums[p+1:]) //递归 + return root +``` Go: @@ -267,4 +285,4 @@ Go: * 作者微信:[程序员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/0669.修剪二叉搜索树.md b/problems/0669.修剪二叉搜索树.md index 8b9e1f6d..06d99b9d 100644 --- a/problems/0669.修剪二叉搜索树.md +++ b/problems/0669.修剪二叉搜索树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 如果不对递归有深刻的理解,本题有点难 @@ -266,7 +266,25 @@ class Solution { 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 trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode: + if not root: return root + if root.val < low: + return self.trimBST(root.right,low,high) // 寻找符合区间[low, high]的节点 + if root.val > high: + return self.trimBST(root.left,low,high) // 寻找符合区间[low, high]的节点 + root.left = self.trimBST(root.left,low,high) // root->left接入符合条件的左孩子 + root.right = self.trimBST(root.right,low,high) // root->right接入符合条件的右孩子 + return root +``` Go: @@ -276,4 +294,4 @@ Go: * 作者微信:[程序员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/0674.最长连续递增序列.md b/problems/0674.最长连续递增序列.md index ad18029e..31ab6b0e 100644 --- a/problems/0674.最长连续递增序列.md +++ b/problems/0674.最长连续递增序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 674. 最长连续递增序列 @@ -156,10 +156,65 @@ public: Java: - +```java + /** + * 1.dp[i] 代表当前下表最大连续值 + * 2.递推公式 if(nums[i+1]>nums[i]) dp[i+1] = dp[i]+1 + * 3.初始化 都为1 + * 4.遍历方向,从其那往后 + * 5.结果推导 。。。。 + * @param nums + * @return + */ + public static int findLengthOfLCIS(int[] nums) { + int[] dp = new int[nums.length]; + for (int i = 0; i < dp.length; i++) { + dp[i] = 1; + } + int res = 1; + for (int i = 0; i < nums.length - 1; i++) { + if (nums[i + 1] > nums[i]) { + dp[i + 1] = dp[i] + 1; + } + res = res > dp[i + 1] ? res : dp[i + 1]; + } + return res; + } +``` Python: +> 动态规划: +```python +class Solution: + def findLengthOfLCIS(self, nums: List[int]) -> int: + if len(nums) == 0: + return 0 + result = 1 + dp = [1] * len(nums) + for i in range(len(nums)-1): + if nums[i+1] > nums[i]: #连续记录 + dp[i+1] = dp[i] + 1 + result = max(result, dp[i+1]) + return result +``` + +> 贪心法: +```python +class Solution: + def findLengthOfLCIS(self, nums: List[int]) -> int: + if len(nums) == 0: + return 0 + result = 1 #连续子序列最少也是1 + count = 1 + for i in range(len(nums)-1): + if nums[i+1] > nums[i]: #连续记录 + count += 1 + else: #不连续,count从头开始 + count = 1 + result = max(result, count) + return result +``` Go: diff --git a/problems/0700.二叉搜索树中的搜索.md b/problems/0700.二叉搜索树中的搜索.md index 277ef681..25a06617 100644 --- a/problems/0700.二叉搜索树中的搜索.md +++ b/problems/0700.二叉搜索树中的搜索.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 700.二叉搜索树中的搜索 @@ -212,13 +212,18 @@ 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 searchBST(self, root: TreeNode, val: int) -> TreeNode: - if root is None: - return None - if val < root.val: return self.searchBST(root.left, val) - elif val > root.val: return self.searchBST(root.right, val) - else: return root + if not root or root.val == val: return root //为空或者已经找到都是直接返回root,所以合并了 + if root.val > val: return self.searchBST(root.left,val) //注意一定要加return + else: return self.searchBST(root.right,val) + ``` 迭代法: @@ -243,4 +248,4 @@ Go: * 作者微信:[程序员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/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index ee690d04..6a8ba7fc 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 701.二叉搜索树中的插入操作 @@ -271,6 +271,20 @@ class Solution: Go: +```Go +func insertIntoBST(root *TreeNode, val int) *TreeNode { + if root == nil { + root = &TreeNode{Val: val} + return root + } + if root.Val > val { + root.Left = insertIntoBST(root.Left, val) + } else { + root.Right = insertIntoBST(root.Right, val) + } + return root +} +``` @@ -279,4 +293,4 @@ Go: * 作者微信:[程序员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/0704.二分查找.md b/problems/0704.二分查找.md index c7207364..931e6e5c 100644 --- a/problems/0704.二分查找.md +++ b/problems/0704.二分查找.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 704. 二分查找 @@ -13,18 +13,23 @@ 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 +示例 1: -示例 1: -输入: nums = [-1,0,3,5,9,12], target = 9 -输出: 4 -解释: 9 出现在 nums 中并且下标为 4 +``` +输入: nums = [-1,0,3,5,9,12], target = 9 +输出: 4 +解释: 9 出现在 nums 中并且下标为 4 +``` -示例 2: -输入: nums = [-1,0,3,5,9,12], target = 2 -输出: -1 -解释: 2 不存在 nums 中因此返回 -1 +示例 2: -提示: +``` +输入: nums = [-1,0,3,5,9,12], target = 2 +输出: -1 +解释: 2 不存在 nums 中因此返回 -1 +``` + +提示: * 你可以假设 nums 中的所有元素是不重复的。 * n 将在 [1, 10000]之间。 @@ -146,13 +151,17 @@ public: ## 其他语言版本 -Java: +**Java:** (版本一)左闭右闭区间 ```java class Solution { public int search(int[] nums, int target) { + // 避免当 target 小于nums[0] nums[nums.length - 1]时多次循环运算 + if (target < nums[0] || target > nums[nums.length - 1]) { + return -1; + } int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + ((right - left) >> 1); @@ -188,9 +197,11 @@ class Solution { } ``` -Python: +**Python:** -```python3 +(版本一)左闭右闭区间 + +```python class Solution: def search(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) - 1 @@ -207,8 +218,104 @@ class Solution: return -1 ``` +(版本二)左闭右开区间 + +```python +class Solution: + def search(self, nums: List[int], target: int) -> int: + left,right =0, len(nums) + while left < right: + mid = (left + right) // 2 + if nums[mid] < target: + left = mid+1 + elif nums[mid] > target: + right = mid + else: + return mid + return -1 +``` + + + +**Go:** + +(版本一)左闭右闭区间 + +```go +func search(nums []int, target int) int { + high := len(nums)-1 + low := 0 + for low <= high { + mid := low + (high-low)/2 + if nums[mid] == target { + return mid + } else if nums[mid] > target { + high = mid-1 + } else { + low = mid+1 + } + } + return -1 +} +``` + +(版本二)左闭右开区间 + +```go +func search(nums []int, target int) int { + high := len(nums) + low := 0 + for low < high { + mid := low + (high-low)/2 + if nums[mid] == target { + return mid + } else if nums[mid] > target { + high = mid + } else { + low = mid+1 + } + } + return -1 +} +``` + +**javaScript:** + +```js + +// (版本一)左闭右闭区间 + +var search = function(nums, target) { + let l = 0, r = nums.length - 1; + // 区间 [l, r] + while(l <= r) { + let mid = (l + r) >> 1; + if(nums[mid] === target) return mid; + let isSmall = nums[mid] < target; + l = isSmall ? mid + 1 : l; + r = isSmall ? r : mid - 1; + } + return -1; +}; + +// (版本二)左闭右开区间 + +var search = function(nums, target) { + let l = 0, r = nums.length; + // 区间 [l, r) + while(l < r) { + let mid = (l + r) >> 1; + if(nums[mid] === target) return mid; + let isSmall = nums[mid] < target; + l = isSmall ? mid + 1 : l; + // 所以 mid 不会被取到 + r = isSmall ? r : mid; + } + return -1; +}; + +``` -Go: @@ -217,4 +324,4 @@ Go: * 作者微信:[程序员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/0707.设计链表.md b/problems/0707.设计链表.md index 93133b55..2fa1af29 100644 --- a/problems/0707.设计链表.md +++ b/problems/0707.设计链表.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 听说这道题目把链表常见的五个操作都覆盖了? @@ -158,6 +158,14 @@ private: Java: ```Java +class ListNode { +int val; +ListNode next; +ListNode(){} +ListNode(int val) { +this.val=val; +} +} class MyLinkedList { //size存储链表元素的个数 int size; @@ -231,10 +239,425 @@ class MyLinkedList { ``` Python: +```python3 +# 单链表 +class Node: + + def __init__(self, val): + self.val = val + self.next = None +class MyLinkedList: + + def __init__(self): + self._head = Node(0) # 虚拟头部节点 + self._count = 0 # 添加的节点数 + + def get(self, index: int) -> int: + """ + Get the value of the index-th node in the linked list. If the index is invalid, return -1. + """ + if 0 <= index < self._count: + node = self._head + for _ in range(index + 1): + node = node.next + return node.val + else: + return -1 + + def addAtHead(self, val: int) -> None: + """ + Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. + """ + self.addAtIndex(0, val) + + def addAtTail(self, val: int) -> None: + """ + Append a node of value val to the last element of the linked list. + """ + self.addAtIndex(self._count, val) + + def addAtIndex(self, index: int, val: int) -> None: + """ + Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. + """ + if index < 0: + index = 0 + elif index > self._count: + return + + # 计数累加 + self._count += 1 + + add_node = Node(val) + prev_node, current_node = None, self._head + for _ in range(index + 1): + prev_node, current_node = current_node, current_node.next + else: + prev_node.next, add_node.next = add_node, current_node + + def deleteAtIndex(self, index: int) -> None: + """ + Delete the index-th node in the linked list, if the index is valid. + """ + if 0 <= index < self._count: + # 计数-1 + self._count -= 1 + prev_node, current_node = None, self._head + for _ in range(index + 1): + prev_node, current_node = current_node, current_node.next + else: + prev_node.next, current_node.next = current_node.next, None + + +# 双链表 +# 相对于单链表, Node新增了prev属性 +class Node: + + def __init__(self, val): + self.val = val + self.prev = None + self.next = None + + +class MyLinkedList: + + def __init__(self): + self._head, self._tail = Node(0), Node(0) # 虚拟节点 + self._head.next, self._tail.prev = self._tail, self._head + self._count = 0 # 添加的节点数 + + def _get_node(self, index: int) -> Node: + # 当index小于_count//2时, 使用_head查找更快, 反之_tail更快 + if index >= self._count // 2: + # 使用prev往前找 + node = self._tail + for _ in range(self._count - index): + node = node.prev + else: + # 使用next往后找 + node = self._head + for _ in range(index + 1): + node = node.next + return node + + def get(self, index: int) -> int: + """ + Get the value of the index-th node in the linked list. If the index is invalid, return -1. + """ + if 0 <= index < self._count: + node = self._get_node(index) + return node.val + else: + return -1 + + def addAtHead(self, val: int) -> None: + """ + Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. + """ + self._update(self._head, self._head.next, val) + + def addAtTail(self, val: int) -> None: + """ + Append a node of value val to the last element of the linked list. + """ + self._update(self._tail.prev, self._tail, val) + + def addAtIndex(self, index: int, val: int) -> None: + """ + Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. + """ + if index < 0: + index = 0 + elif index > self._count: + return + node = self._get_node(index) + self._update(node.prev, node, val) + + def _update(self, prev: Node, next: Node, val: int) -> None: + """ + 更新节点 + :param prev: 相对于更新的前一个节点 + :param next: 相对于更新的后一个节点 + :param val: 要添加的节点值 + """ + # 计数累加 + self._count += 1 + node = Node(val) + prev.next, next.prev = node, node + node.prev, node.next = prev, next + + def deleteAtIndex(self, index: int) -> None: + """ + Delete the index-th node in the linked list, if the index is valid. + """ + if 0 <= index < self._count: + node = self._get_node(index) + # 计数-1 + self._count -= 1 + node.prev.next, node.next.prev = node.next, node.prev +``` + Go: +```go +//循环双链表 +type MyLinkedList struct { + dummy *Node +} + +type Node struct { + Val int + Next *Node + Pre *Node +} + +//仅保存哑节点,pre-> rear, next-> head +/** Initialize your data structure here. */ +func Constructor() MyLinkedList { + rear := &Node{ + Val: -1, + Next: nil, + Pre: nil, + } + rear.Next = rear + rear.Pre = rear + return MyLinkedList{rear} +} + +/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */ +func (this *MyLinkedList) Get(index int) int { + head := this.dummy.Next + //head == this, 遍历完全 + for head != this.dummy && index > 0 { + index-- + head = head.Next + } + //否则, head == this, 索引无效 + if 0 != index { + return -1 + } + return head.Val +} + +/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */ +func (this *MyLinkedList) AddAtHead(val int) { + dummy := this.dummy + node := &Node{ + Val: val, + //head.Next指向原头节点 + Next: dummy.Next, + //head.Pre 指向哑节点 + Pre: dummy, + } + + //更新原头节点 + dummy.Next.Pre = node + //更新哑节点 + dummy.Next = node + //以上两步不能反 +} + +/** Append a node of value val to the last element of the linked list. */ +func (this *MyLinkedList) AddAtTail(val int) { + dummy := this.dummy + rear := &Node{ + Val: val, + //rear.Next = dummy(哑节点) + Next: dummy, + //rear.Pre = ori_rear + Pre: dummy.Pre, + } + + //ori_rear.Next = rear + dummy.Pre.Next = rear + //update dummy + dummy.Pre = rear + //以上两步不能反 +} + +/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */ +func (this *MyLinkedList) AddAtIndex(index int, val int) { + head := this.dummy.Next + //head = MyLinkedList[index] + for head != this.dummy && index > 0 { + head = head.Next + index-- + } + node := &Node{ + Val: val, + //node.Next = MyLinkedList[index] + Next: head, + //node.Pre = MyLinkedList[index-1] + Pre: head.Pre, + } + //MyLinkedList[index-1].Next = node + head.Pre.Next = node + //MyLinkedList[index].Pre = node + head.Pre = node + //以上两步不能反 +} + +/** Delete the index-th node in the linked list, if the index is valid. */ +func (this *MyLinkedList) DeleteAtIndex(index int) { + //链表为空 + if this.dummy.Next == this.dummy { + return + } + head := this.dummy.Next + //head = MyLinkedList[index] + for head.Next != this.dummy && index > 0 { + head = head.Next + index-- + } + //验证index有效 + if index == 0 { + //MyLinkedList[index].Pre = index[index-2] + head.Next.Pre = head.Pre + //MyLinedList[index-2].Next = index[index] + head.Pre.Next = head.Next + //以上两步顺序无所谓 + } +} +``` + +javaScript: + +```js + +class LinkNode { + constructor(val, next) { + this.val = val; + this.next = next; + } +} + +/** + * Initialize your data structure here. + * 单链表 储存头尾节点 和 节点数量 + */ +var MyLinkedList = function() { + this._size = 0; + this._tail = null; + this._head = null; +}; + +/** + * Get the value of the index-th node in the linked list. If the index is invalid, return -1. + * @param {number} index + * @return {number} + */ +MyLinkedList.prototype.getNode = function(index) { + if(index < 0 || index >= this._size) return null; + // 创建虚拟头节点 + let cur = new LinkNode(0, this._head); + // 0 -> head + while(index-- >= 0) { + cur = cur.next; + } + return cur; +}; +MyLinkedList.prototype.get = function(index) { + if(index < 0 || index >= this._size) return -1; + // 获取当前节点 + return this.getNode(index).val; +}; + +/** + * Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. + * @param {number} val + * @return {void} + */ +MyLinkedList.prototype.addAtHead = function(val) { + const node = new LinkNode(val, this._head); + this._head = node; + this._size++; + if(!this._tail) { + this._tail = node; + } +}; + +/** + * Append a node of value val to the last element of the linked list. + * @param {number} val + * @return {void} + */ +MyLinkedList.prototype.addAtTail = function(val) { + const node = new LinkNode(val, null); + this._size++; + if(this._tail) { + this._tail.next = node; + this._tail = node; + return; + } + this._tail = node; + this._head = node; +}; + +/** + * Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. + * @param {number} index + * @param {number} val + * @return {void} + */ +MyLinkedList.prototype.addAtIndex = function(index, val) { + if(index > this._size) return; + if(index <= 0) { + this.addAtHead(val); + return; + } + if(index === this._size) { + this.addAtTail(val); + return; + } + // 获取目标节点的上一个的节点 + const node = this.getNode(index - 1); + node.next = new LinkNode(val, node.next); + this._size++; +}; + +/** + * Delete the index-th node in the linked list, if the index is valid. + * @param {number} index + * @return {void} + */ +MyLinkedList.prototype.deleteAtIndex = function(index) { + if(index < 0 || index >= this._size) return; + if(index === 0) { + this._head = this._head.next; + this._size--; + return; + } + // 获取目标节点的上一个的节点 + const node = this.getNode(index - 1); + node.next = node.next.next; + // 处理尾节点 + if(index === this._size - 1) { + this._tail = node; + } + this._size--; +}; + +// MyLinkedList.prototype.out = function() { +// let cur = this._head; +// const res = []; +// while(cur) { +// res.push(cur.val); +// cur = cur.next; +// } +// }; +/** + * Your MyLinkedList object will be instantiated and called as such: + * var obj = new MyLinkedList() + * var param_1 = obj.get(index) + * obj.addAtHead(val) + * obj.addAtTail(val) + * obj.addAtIndex(index,val) + * obj.deleteAtIndex(index) + */ +``` + diff --git a/problems/0714.买卖股票的最佳时机含手续费.md b/problems/0714.买卖股票的最佳时机含手续费.md index 86954c14..abd20625 100644 --- a/problems/0714.买卖股票的最佳时机含手续费.md +++ b/problems/0714.买卖股票的最佳时机含手续费.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 714. 买卖股票的最佳时机含手续费 @@ -154,9 +154,9 @@ public: ## 其他语言版本 - Java: ```java +// 贪心思路 class Solution { public int maxProfit(int[] prices, int fee) { int buy = prices[0] + fee; @@ -174,8 +174,46 @@ class Solution { } ``` -Python: +```java +class Solution { // 动态规划 + public int maxProfit(int[] prices, int fee) { + if (prices == null || prices.length < 2) { + return 0; + } + int[][] dp = new int[prices.length][2]; + + // bad case + dp[0][0] = 0; + dp[0][1] = -prices[0]; + + for (int i = 1; i < prices.length; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]); + } + + return dp[prices.length - 1][0]; + } +} +``` + + +Python: +```python +class Solution: # 贪心思路 + def maxProfit(self, prices: List[int], fee: int) -> int: + result = 0 + minPrice = prices[0] + for i in range(1, len(prices)): + if prices[i] < minPrice: + minPrice = prices[i] + elif prices[i] >= minPrice and prices[i] <= minPrice + fee: + continue + else: + result += prices[i] - minPrice - fee + minPrice = prices[i] - fee + return result +``` Go: diff --git a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md index 3926643d..5eb3453b 100644 --- a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md +++ b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 714.买卖股票的最佳时机含手续费 @@ -95,10 +95,61 @@ public: Java: +```java +/** + * 卖出时支付手续费 + * @param prices + * @param fee + * @return + */ +public int maxProfit(int[] prices, int fee) { + int len = prices.length; + // 0 : 持股(买入) + // 1 : 不持股(售出) + // dp 定义第i天持股/不持股 所得最多现金 + int[][] dp = new int[len][2]; + dp[0][0] = -prices[0]; + for (int i = 1; i < len; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]); + dp[i][1] = Math.max(dp[i - 1][0] + prices[i] - fee, dp[i - 1][1]); + } + return Math.max(dp[len - 1][0], dp[len - 1][1]); +} +/** + * 买入时支付手续费 + * @param prices + * @param fee + * @return + */ +public int maxProfit(int[] prices, int fee) { + int len = prices.length; + // 0 : 持股(买入) + // 1 : 不持股(售出) + // dp 定义第i天持股/不持股 所得最多现金 + int[][] dp = new int[len][2]; + // 考虑买入的时候就支付手续费 + dp[0][0] = -prices[0] - fee; + for (int i = 1; i < len; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i] - fee); + dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]); + } + return Math.max(dp[len - 1][0], dp[len - 1][1]); +} +``` Python: - +```python +class Solution: + def maxProfit(self, prices: List[int], fee: int) -> int: + n = len(prices) + dp = [[0] * 2 for _ in range(n)] + dp[0][0] = -prices[0] #持股票 + for i in range(1, n): + dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]) + dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i] - fee) + return max(dp[-1][0], dp[-1][1]) +``` Go: diff --git a/problems/0718.最长重复子数组.md b/problems/0718.最长重复子数组.md index c20ea79f..bef616d3 100644 --- a/problems/0718.最长重复子数组.md +++ b/problems/0718.最长重复子数组.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 718. 最长重复子数组 @@ -128,7 +128,7 @@ public: **此时遍历B数组的时候,就要从后向前遍历,这样避免重复覆盖**。 -``` +```C++ class Solution { public: int findLength(vector& A, vector& B) { @@ -154,13 +154,82 @@ public: Java: - +```java +class Solution { + public int findLength(int[] nums1, int[] nums2) { + int result = 0; + int[][] dp = new int[nums1.length + 1][nums2.length + 1]; + + for (int i = 1; i < nums1.length + 1; i++) { + for (int j = 1; j < nums2.length + 1; j++) { + if (nums1[i - 1] == nums2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + max = Math.max(max, dp[i][j]); + } + } + } + + return result; + } +} +``` Python: +> 动态规划: +```python +class Solution: + def findLength(self, A: List[int], B: List[int]) -> int: + dp = [[0] * (len(B)+1) for _ in range(len(A)+1)] + result = 0 + for i in range(1, len(A)+1): + for j in range(1, len(B)+1): + if A[i-1] == B[j-1]: + dp[i][j] = dp[i-1][j-1] + 1 + result = max(result, dp[i][j]) + return result +``` + +> 动态规划:滚动数组 +```python +class Solution: + def findLength(self, A: List[int], B: List[int]) -> int: + dp = [0] * (len(B) + 1) + result = 0 + for i in range(1, len(A)+1): + for j in range(len(B), 0, -1): + if A[i-1] == B[j-1]: + dp[j] = dp[j-1] + 1 + else: + dp[j] = 0 #注意这里不相等的时候要有赋0的操作 + result = max(result, dp[j]) + return result +``` + Go: +```Go +func findLength(A []int, B []int) int { + m, n := len(A), len(B) + res := 0 + dp := make([][]int, m+1) + for i := 0; i <= m; i++ { + dp[i] = make([]int, n+1) + } + for i := 1; i <= m; i++ { + for j := 1; j <= n; j++ { + if A[i-1] == B[j-1] { + dp[i][j] = dp[i-1][j-1] + 1 + } + if dp[i][j] > res { + res = dp[i][j] + } + } + } + return res +} +``` diff --git a/problems/0738.单调递增的数字.md b/problems/0738.单调递增的数字.md index dc136028..5bddb234 100644 --- a/problems/0738.单调递增的数字.md +++ b/problems/0738.单调递增的数字.md @@ -1,13 +1,14 @@

- + - +

-

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

+

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

## 738.单调递增的数字 +题目链接: https://leetcode-cn.com/problems/monotone-increasing-digits/ 给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。 @@ -30,7 +31,7 @@ ## 暴力解法 -题意很简单,那么首先想的就是暴力解法了,来我提大家暴力一波,结果自然是超时! +题意很简单,那么首先想的就是暴力解法了,来我替大家暴力一波,结果自然是超时! 代码如下: ```C++ @@ -146,7 +147,19 @@ class Solution { Python: - +```python +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)) +``` Go: @@ -157,4 +170,4 @@ Go: * 作者微信:[程序员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/0739.每日温度.md b/problems/0739.每日温度.md new file mode 100644 index 00000000..1f91e42a --- /dev/null +++ b/problems/0739.每日温度.md @@ -0,0 +1,225 @@ + +

+ + + + +

+

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

+ +# 739. 每日温度 + + +https://leetcode-cn.com/problems/daily-temperatures/ + +请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 + +例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 + +提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。 + + +## 思路 + +首先想到的当然是暴力解法,两层for循环,把至少需要等待的天数就搜出来了。时间复杂度是O(n^2) + +那么接下来在来看看使用单调栈的解法。 + +那有同学就问了,我怎么能想到用单调栈呢? 什么时候用单调栈呢? + +**通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了**。 + +时间复杂度为O(n)。 + +例如本题其实就是找找到一个元素右边第一个比自己大的元素。 + +此时就应该想到用单调栈了。 + +那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢? + +单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素的元素,优点是只需要遍历一次。 + + +在使用单调栈的时候首先要明确如下几点: + +1. 单调栈里存放的元素是什么? + +单调栈里只需要存放元素的下标i就可以了,如果需要使用对应的元素,直接T[i]就可以获取。 + +2. 单调栈里元素是递增呢? 还是递减呢? + +**注意一下顺序为 从栈头到栈底的顺序**,因为单纯的说从左到右或者从前到后,不说栈头朝哪个方向的话,大家一定会越看越懵。 + + +这里我们要使用递增循序(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,加入一个元素i,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。 + +文字描述理解起来有点费劲,接下来我画了一系列的图,来讲解单调栈的工作过程。 + +使用单调栈主要有三个判断条件。 + +* 当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况 +* 当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况 +* 当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况 + +**把这三种情况分析清楚了,也就理解透彻了**。 + +接下来我们用temperatures = [73, 74, 75, 71, 71, 72, 76, 73]为例来逐步分析,输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。 + +首先先将第一个遍历元素加入单调栈 +![739.每日温度1](https://img-blog.csdnimg.cn/20210219124434172.jpg) + +加入T[1] = 74,因为T[1] > T[0](当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),而我们要保持一个递增单调栈(从栈头到栈底),所以将T[0]弹出,T[1]加入,此时result数组可以记录了,result[0] = 1,即T[0]右面第一个比T[0]大的元素是T[1]。 +![739.每日温度2](https://img-blog.csdnimg.cn/20210219124504299.jpg) + +加入T[2],同理,T[1]弹出 + +![739.每日温度3](https://img-blog.csdnimg.cn/20210219124527361.jpg) + +加入T[3],T[3] < T[2] (当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况),加T[3]加入单调栈。 + +![739.每日温度4](https://img-blog.csdnimg.cn/20210219124610761.jpg) + +加入T[4],T[4] == T[3] (当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况),此时依然要加入栈,不用计算距离,因为我们要求的是右面第一个大于本元素的位置,而不是大于等于! +![739.每日温度5](https://img-blog.csdnimg.cn/20210219124633444.jpg) + +加入T[5],T[5] > T[4] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[4]弹出,同时计算距离,更新result +![739.每日温度6](https://img-blog.csdnimg.cn/20210219124700567.jpg) + +T[4]弹出之后, T[5] > T[3] (当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况),将T[3]继续弹出,同时计算距离,更新result +![739.每日温度7](https://img-blog.csdnimg.cn/20210219124726613.jpg) + +直到发现T[5]小于T[st.top()],终止弹出,将T[5]加入单调栈 +![739.每日温度8](https://img-blog.csdnimg.cn/20210219124807715.jpg) + +加入T[6],同理,需要将栈里的T[5],T[2]弹出 +![739.每日温度9](https://img-blog.csdnimg.cn/2021021912483374.jpg) + +同理,继续弹出 +![739.每日温度10](https://img-blog.csdnimg.cn/2021021912490098.jpg) + +此时栈里只剩下了T[6] + +![739.每日温度11](https://img-blog.csdnimg.cn/20210219124930156.jpg) + +加入T[7], T[7] < T[6] 直接入栈,这就是最后的情况,result数组也更新完了。 +![739.每日温度12](https://img-blog.csdnimg.cn/20210219124957216.jpg) + + +此时有同学可能就疑惑了,那result[6] , result[7]怎么没更新啊,元素也一直在栈里。 + +其实定义result数组的时候,就应该直接初始化为0,如果result没有更新,说明这个元素右面没有更大的了,也就是为0。 + +以上在图解的时候,已经把,这三种情况都做了详细的分析。 + +* 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况 +* 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况 +* 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况 + +C++代码如下: + +```C++ +// 版本一 +class Solution { +public: + vector dailyTemperatures(vector& T) { + // 递减栈 + stack st; + vector result(T.size(), 0); + st.push(0); + for (int i = 1; i < T.size(); i++) { + if (T[i] < T[st.top()]) { // 情况一 + st.push(i); + } else if (T[i] == T[st.top()]) { // 情况二 + st.push(i); + } else { + while (!st.empty() && T[i] > T[st.top()]) { // 情况三 + result[st.top()] = i - st.top(); + st.pop(); + } + st.push(i); + } + } + return result; + } +}; +``` + +**建议一开始 都把每种情况分析好,不要上来看简短的代码,关键逻辑都被隐藏了**。 + +精简代码如下: + +```C++ +// 版本二 +class Solution { +public: + vector dailyTemperatures(vector& T) { + stack st; // 递减栈 + vector result(T.size(), 0); + st.push(0); + for (int i = 1; i < T.size(); i++) { + while (!st.empty() && T[i] > T[st.top()]) { // 注意栈不能为空 + result[st.top()] = i - st.top(); + st.pop(); + } + st.push(i); + + } + return result; + } +}; +``` +* 时间复杂度:O(n) +* 空间复杂度:O(n) + +精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。 + +建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简! + + +## 其他语言版本 + + +Java: +```java +/** + * 单调栈,栈内顺序要么从大到小 要么从小到大,本题从大到笑 + *

+ * 入站元素要和当前栈内栈首元素进行比较 + * 若大于栈首则 则与元素下标做差 + * 若大于等于则放入 + * + * @param temperatures + * @return + */ + public static int[] dailyTemperatures(int[] temperatures) { + Stack stack = new Stack<>(); + int[] res = new int[temperatures.length]; + for (int i = 0; i < temperatures.length; i++) { + /** + * 取出下标进行元素值的比较 + */ + while (!stack.isEmpty() && temperatures[i] > temperatures[stack.peek()]) { + int preIndex = stack.pop(); + res[preIndex] = i - preIndex; + } + /** + * 注意 放入的是元素位置 + */ + stack.push(i); + } + return res; + } +``` +Python: + +Go: + + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) +

+ diff --git a/problems/0746.使用最小花费爬楼梯.md b/problems/0746.使用最小花费爬楼梯.md index b8158205..4238a389 100644 --- a/problems/0746.使用最小花费爬楼梯.md +++ b/problems/0746.使用最小花费爬楼梯.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 746. 使用最小花费爬楼梯 @@ -225,12 +225,48 @@ class Solution { ``` Python: - +```python +class Solution: + def minCostClimbingStairs(self, cost: List[int]) -> int: + dp = [0] * (len(cost)) + dp[0] = cost[0] + dp[1] = cost[1] + for i in range(2, len(cost)): + dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] + return min(dp[len(cost) - 1], dp[len(cost) - 2]) +``` Go: +```Go +func minCostClimbingStairs(cost []int) int { + dp := make([]int, len(cost)) + dp[0], dp[1] = cost[0], cost[1] + for i := 2; i < len(cost); i++ { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i] + } + return min(dp[len(cost)-1], dp[len(cost)-2]) +} +func min(a, b int) int { + if a < b { + return a + } + return b +} +``` - +Javascript: +```Javascript +var minCostClimbingStairs = function(cost) { + const dp = [ cost[0], cost[1] ] + + for (let i = 2; i < cost.length; ++i) { + dp[i] = Math.min(dp[i -1] + cost[i], dp[i - 2] + cost[i]) + } + + return Math.min(dp[cost.length - 1], dp[cost.length - 2]) +}; +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index c1474280..bcdd71dc 100644 --- a/problems/0763.划分字母区间.md +++ b/problems/0763.划分字母区间.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 763.划分字母区间 @@ -108,7 +108,23 @@ class Solution { ``` Python: +```python +class Solution: + def partitionLabels(self, s: str) -> List[int]: + hash = [0] * 26 + for i in range(len(s)): + hash[ord(s[i]) - ord('a')] = i + result = [] + left = 0 + right = 0 + for i in range(len(s)): + right = max(right, hash[ord(s[i]) - ord('a')]) + if i == right: + result.append(right - left + 1) + left = i + 1 + return result +``` Go: diff --git a/problems/0860.柠檬水找零.md b/problems/0860.柠檬水找零.md index bf8a3776..a18c008d 100644 --- a/problems/0860.柠檬水找零.md +++ b/problems/0860.柠檬水找零.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 860.柠檬水找零 @@ -156,15 +156,69 @@ class Solution { ``` Python: +```python +class Solution: + def lemonadeChange(self, bills: List[int]) -> bool: + five, ten, twenty = 0, 0, 0 + for bill in bills: + if bill == 5: + five += 1 + elif bill == 10: + if five < 1: return False + five -= 1 + ten += 1 + else: + if ten > 0 and five > 0: + ten -= 1 + five -= 1 + twenty += 1 + elif five > 2: + five -= 3 + twenty += 1 + else: + return False + return True +``` Go: +Javascript: +```Javascript +var lemonadeChange = function(bills) { + let fiveCount = 0 + let tenCount = 0 + for(let i = 0; i < bills.length; i++) { + let bill = bills[i] + if(bill === 5) { + fiveCount += 1 + } else if (bill === 10) { + if(fiveCount > 0) { + fiveCount -=1 + tenCount += 1 + } else { + return false + } + } else { + if(tenCount > 0 && fiveCount > 0) { + tenCount -= 1 + fiveCount -= 1 + } else if(fiveCount >= 3) { + fiveCount -= 3 + } else { + return false + } + } + } + return true +}; + +``` ----------------------- * 作者微信:[程序员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/0968.监控二叉树.md b/problems/0968.监控二叉树.md index e541ba13..8f1a3fdb 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 968.监控二叉树 @@ -346,8 +346,27 @@ class Solution { Python: - - +```python +class Solution: + def minCameraCover(self, root: TreeNode) -> int: + result = 0 + def traversal(cur): + nonlocal result + if not cur: + return 2 + left = traversal(cur.left) + right = traversal(cur.right) + if left == 2 and right == 2: + return 0 + elif left == 0 or right == 0: + result += 1 + return 1 + elif left == 1 or right == 1: + return 2 + else: return -1 + if traversal(root) == 0: result += 1 + return result +``` Go: @@ -357,4 +376,4 @@ Go: * 作者微信:[程序员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/0977.有序数组的平方.md b/problems/0977.有序数组的平方.md new file mode 100644 index 00000000..d2e1f950 --- /dev/null +++ b/problems/0977.有序数组的平方.md @@ -0,0 +1,206 @@ +

+ + + + +

+

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

+ +> 双指针风骚起来,也是无敌 + +# 977.有序数组的平方 + +https://leetcode-cn.com/problems/squares-of-a-sorted-array/ + +给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 + +示例 1: +输入:nums = [-4,-1,0,3,10] +输出:[0,1,9,16,100] +解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100] + +示例 2: +输入:nums = [-7,-3,2,3,11] +输出:[4,9,9,49,121] + +# 思路 + +## 暴力排序 + +最直观的相反,莫过于:每个数平方之后,排个序,美滋滋,代码如下: + +```C++ +class Solution { +public: + vector sortedSquares(vector& A) { + for (int i = 0; i < A.size(); i++) { + A[i] *= A[i]; + } + sort(A.begin(), A.end()); // 快速排序 + return A; + } +}; +``` + +这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlogn)。 + +## 双指针法 + +数组其实是有序的, 只不过负数平方之后可能成为最大数了。 + +那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。 + +此时可以考虑双指针法了,i指向起始位置,j指向终止位置。 + +定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置。 + +如果`A[i] * A[i] < A[j] * A[j]` 那么`result[k--] = A[j] * A[j];` 。 + +如果`A[i] * A[i] >= A[j] * A[j]` 那么`result[k--] = A[i] * A[i];` 。 + +如动画所示: + +![](https://code-thinking.cdn.bcebos.com/gifs/977.有序数组的平方.gif) + +不难写出如下代码: + +```C++ +class Solution { +public: + vector sortedSquares(vector& A) { + int k = A.size() - 1; + vector result(A.size(), 0); + for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素 + if (A[i] * A[i] < A[j] * A[j]) { + result[k--] = A[j] * A[j]; + j--; + } + else { + result[k--] = A[i] * A[i]; + i++; + } + } + return result; + } +}; +``` + +此时的时间复杂度为O(n),相对于暴力排序的解法O(n + nlogn)还是提升不少的。 + + +**这里还是说一下,大家不必太在意leetcode上执行用时,打败多少多少用户,这个就是一个玩具,非常不准确。** + +做题的时候自己能分析出来时间复杂度就可以了,至于leetcode上执行用时,大概看一下就行,只要达到最优的时间复杂度就可以了, + +一样的代码多提交几次可能就击败百分之百了..... + +## 其他语言版本 + +Java: +```Java +class Solution { + public int[] sortedSquares(int[] nums) { + int right = nums.length - 1; + int left = 0; + int[] result = new int[nums.length]; + int index = result.length - 1; + while (left <= right) { + if (nums[left] * nums[left] > nums[right] * nums[right]) { + result[index--] = nums[left] * nums[left]; + ++left; + } else { + result[index--] = nums[right] * nums[right]; + --right; + } + } + return result; + } +} +``` + +```java +class Solution { + public int[] sortedSquares(int[] nums) { + int l = 0; + int r = nums.length - 1; + int[] res = new int[nums.length]; + int j = nums.length - 1; + while(l <= r){ + if(nums[l] * nums[l] > nums[r] * nums[r]){ + res[j--] = nums[l] * nums[l++]; + }else{ + res[j--] = nums[r] * nums[r--]; + } + } + return res; + } +} +``` + +Python: +```Python +class Solution: + def sortedSquares(self, nums: List[int]) -> List[int]: + n = len(nums) + i,j,k = 0,n - 1,n - 1 + ans = [-1] * n + while i <= j: + lm = nums[i] ** 2 + rm = nums[j] ** 2 + if lm > rm: + ans[k] = lm + i += 1 + else: + ans[k] = rm + j -= 1 + k -= 1 + return ans +``` + +Go: +```Go +func sortedSquares(nums []int) []int { + n := len(nums) + i, j, k := 0, n-1, n-1 + ans := make([]int, n) + for i <= j { + lm, rm := nums[i]*nums[i], nums[j]*nums[j] + if lm > rm { + ans[k] = lm + i++ + } else { + ans[k] = rm + j-- + } + k-- + } + return ans +} +``` +Rust +``` +impl Solution { + pub fn sorted_squares(nums: Vec) -> Vec { + let n = nums.len(); + let (mut i,mut j,mut k) = (0,n - 1,n- 1); + let mut ans = vec![0;n]; + while i <= j{ + if nums[i] * nums[i] < nums[j] * nums[j] { + ans[k] = nums[j] * nums[j]; + j -= 1; + }else{ + ans[k] = nums[i] * nums[i]; + i += 1; + } + k -= 1; + } + ans + } +} +``` + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) +
diff --git a/problems/1005.K次取反后最大化的数组和.md b/problems/1005.K次取反后最大化的数组和.md index 1653201e..387de147 100644 --- a/problems/1005.K次取反后最大化的数组和.md +++ b/problems/1005.K次取反后最大化的数组和.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 1005.K次取反后最大化的数组和 @@ -102,7 +102,7 @@ Java: ```java class Solution { public int largestSumAfterKNegations(int[] A, int K) { - if (A.length == 1) return A[0]; + if (A.length == 1) return k % 2 == 0 ? A[0] : -A[0]; Arrays.sort(A); int sum = 0; int idx = 0; @@ -124,15 +124,48 @@ class Solution { ``` Python: - +```python +class Solution: + def largestSumAfterKNegations(self, A: List[int], K: int) -> int: + A = sorted(A, key=abs, reverse=True) # 将A按绝对值从大到小排列 + for i in range(len(A)): + if K > 0 and A[i] < 0: + A[i] *= -1 + K -= 1 + if K > 0: + A[-1] *= (-1)**K #取A最后一个数只需要写-1 + return sum(A) +``` Go: +Javascript: +```Javascript +var largestSumAfterKNegations = function(nums, k) { + nums.sort((a, b) => { + return Math.abs(b) - Math.abs(a) + }) + for(let i = 0; i < nums.length; i++) { + if(nums[i] < 0 && k > 0) { + nums[i] *= -1 + k-- + } + } + if(k > 0 && k % 2 === 1) { + nums[nums.length - 1] *= -1 + } + k = 0 + + return nums.reduce((a, b) => { + return a + b + }) +}; +``` ----------------------- * 作者微信:[程序员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/1035.不相交的线.md b/problems/1035.不相交的线.md index 6f2b6646..be159543 100644 --- a/problems/1035.不相交的线.md +++ b/problems/1035.不相交的线.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 1035.不相交的线 @@ -73,10 +73,38 @@ public: Java: - + ```java + class Solution { + public int maxUncrossedLines(int[] A, int[] B) { + int [][] dp = new int[A.length+1][B.length+1]; + for(int i=1;i<=A.length;i++) { + for(int j=1;j<=B.length;j++) { + if (A[i-1]==B[j-1]) { + dp[i][j]=dp[i-1][j-1]+1; + } + else { + dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]); + } + } + } + return dp[A.length][B.length]; + } +} + ``` Python: - +```python +class Solution: + def maxUncrossedLines(self, A: List[int], B: List[int]) -> int: + dp = [[0] * (len(B)+1) for _ in range(len(A)+1)] + for i in range(1, len(A)+1): + for j in range(1, len(B)+1): + if A[i-1] == B[j-1]: + dp[i][j] = dp[i-1][j-1] + 1 + else: + dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + return dp[-1][-1] +``` Go: diff --git a/problems/1047.删除字符串中的所有相邻重复项.md b/problems/1047.删除字符串中的所有相邻重复项.md index d5a8c4ed..305a287d 100644 --- a/problems/1047.删除字符串中的所有相邻重复项.md +++ b/problems/1047.删除字符串中的所有相邻重复项.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -122,6 +122,8 @@ public: Java: + +使用 Deque 作为堆栈 ```Java class Solution { public String removeDuplicates(String S) { @@ -144,12 +146,81 @@ class Solution { } } ``` +拿字符串直接作为栈,省去了栈还要转为字符串的操作。 +```Java +class Solution { + public String removeDuplicates(String s) { + // 将 res 当做栈 + StringBuffer res = new StringBuffer(); + // top为 res 的长度 + int top = -1; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + // 当 top > 0,即栈中有字符时,当前字符如果和栈中字符相等,弹出栈顶字符,同时 top-- + if (top >= 0 && res.charAt(top) == c) { + res.deleteCharAt(top); + top--; + // 否则,将该字符 入栈,同时top++ + } else { + res.append(c); + top++; + } + } + return res.toString(); + } +} +``` Python: - +```python3 +class Solution: + def removeDuplicates(self, s: str) -> str: + t = list() + for i in s: + if t and t[-1] == i: + t.pop(-1) + else: + t.append(i) + return "".join(t) # 字符串拼接 +``` Go: +```go +func removeDuplicates(s string) string { + var stack []byte + for i := 0; i < len(s);i++ { + // 栈不空 且 与栈顶元素不等 + if len(stack) > 0 && stack[len(stack)-1] == s[i] { + // 弹出栈顶元素 并 忽略当前元素(s[i]) + stack = stack[:len(stack)-1] + }else{ + // 入栈 + stack = append(stack, s[i]) + } + } + return string(stack) +} +``` + +javaScript: + +```js +/** + * @param {string} s + * @return {string} + */ +var removeDuplicates = function(s) { + const stack = []; + for(const x of s) { + let c = null; + if(stack.length && x === (c = stack.pop())) continue; + c && stack.push(c); + stack.push(x); + } + return stack.join(""); +}; +``` diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md index 0ffd6a3e..c09e476a 100644 --- a/problems/1049.最后一块石头的重量II.md +++ b/problems/1049.最后一块石头的重量II.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:最后一块石头的重量 II ## 1049. 最后一块石头的重量 II @@ -178,10 +178,46 @@ class Solution { ``` Python: - +```python +class Solution: + def lastStoneWeightII(self, stones: List[int]) -> int: + sumweight = sum(stones) + target = sumweight // 2 + dp = [0] * 15001 + for i in range(len(stones)): + for j in range(target, stones[i] - 1, -1): + dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]) + return sumweight - 2 * dp[target] +``` Go: +```go +func lastStoneWeightII(stones []int) int { + // 15001 = 30 * 1000 /2 +1 + dp := make([]int, 15001) + // 求target + sum := 0 + for _, v := range stones { + sum += v + } + target := sum / 2 + // 遍历顺序 + for i := 0; i < len(stones); i++ { + for j := target; j >= stones[i]; j-- { + // 推导公式 + dp[j] = max(dp[j], dp[j-stones[i]]+stones[i]) + } + } + return sum - 2 * dp[target] +} +func max(a, b int) int { + if a > b { + return a + } + return b +} +``` diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index 19f4cc72..2ddab584 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 1143.最长公共子序列 @@ -166,6 +166,34 @@ class Solution: Go: +```Go +func longestCommonSubsequence(text1 string, text2 string) int { + t1 := len(text1) + t2 := len(text2) + dp:=make([][]int,t1+1) + for i:=range dp{ + dp[i]=make([]int,t2+1) + } + + for i := 1; i <= t1; i++ { + for j := 1; j <=t2; j++ { + if text1[i-1]==text2[j-1]{ + dp[i][j]=dp[i-1][j-1]+1 + }else{ + dp[i][j]=max(dp[i-1][j],dp[i][j-1]) + } + } + } + return dp[t1][t2] +} + +func max(a,b int)int { + if a>b{ + return a + } + return b +} +``` @@ -174,4 +202,4 @@ Go: * 作者微信:[程序员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/O(n)的算法居然超时了,此时的n究竟是多大?.md b/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md index 32277bb1..2b995c59 100644 --- a/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md +++ b/problems/O(n)的算法居然超时了,此时的n究竟是多大?.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 程序提交之后为什么会超时?O(n)的算法会超时,n究竟是多大? diff --git a/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md b/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md index 60334d1f..353bd68e 100644 --- a/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md +++ b/problems/为了绝杀编辑距离,卡尔做了三步铺垫.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划之编辑距离总结篇 diff --git a/problems/二叉树中递归带着回溯.md b/problems/二叉树中递归带着回溯.md index bf15e39b..372dc40c 100644 --- a/problems/二叉树中递归带着回溯.md +++ b/problems/二叉树中递归带着回溯.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 二叉树:以为使用了递归,其实还隐藏着回溯 @@ -175,13 +175,272 @@ if (cur->right) { Java: + 100. 相同的树:递归代码 + ```java + class Solution { + public boolean compare(TreeNode tree1, TreeNode tree2) { + + if(tree1==null && tree2==null)return true; + if(tree1==null || tree2==null)return false; + if(tree1.val!=tree2.val)return false; + // 此时就是:左右节点都不为空,且数值相同的情况 + // 此时才做递归,做下一层的判断 + boolean compareLeft = compare(tree1.left, tree2.left); // 左子树:左、 右子树:左 + boolean compareRight = compare(tree1.right, tree2.right); // 左子树:右、 右子树:右 + boolean isSame = compareLeft && compareRight; // 左子树:中、 右子树:中(逻辑处理) + return isSame; + } + boolean isSameTree(TreeNode p, TreeNode q) { + return compare(p, q); + } +} + ``` + 257. 二叉树的所有路径: 回溯代码 + ```java + class Solution { + public void traversal(TreeNode cur, List path, List result) { + path.add(cur.val); + // 这才到了叶子节点 + if (cur.left == null && cur.right == null) { + String sPath=""; + for (int i = 0; i < path.size() - 1; i++) { + sPath += ""+path.get(i); + sPath += "->"; + } + sPath += path.get(path.size() - 1); + result.add(sPath); + return; + } + if (cur.left!=null) { + traversal(cur.left, path, result); + path.remove(path.size()-1); // 回溯 + } + if (cur.right!=null) { + traversal(cur.right, path, result); + path.remove(path.size()-1); // 回溯 + } + } + + public List binaryTreePaths(TreeNode root) { + List result = new LinkedList<>(); + List path = new LinkedList<>(); + if (root == null) return result; + traversal(root, path, result); + return result; + } +} + + ``` + 如下为精简之后的递归代码:(257. 二叉树的所有路径) + ```java + class Solution { + public void traversal(TreeNode cur, String path, List result) { + path += cur.val; // 中 + if (cur.left == null && cur.right == null) { + result.add(path); + return; + } + if (cur.left!=null) traversal(cur.left, path + "->", result); // 左 回溯就隐藏在这里 + if (cur.right!=null) traversal(cur.right, path + "->", result); // 右 回溯就隐藏在这里 + } + + public List binaryTreePaths(TreeNode root) { + List result = new LinkedList<>(); + String path = ""; + if (root == null) return result; + traversal(root, path, result); + return result; + } +} + ``` Python: +100.相同的树 +> 递归法 +```python +class Solution: + def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: + return self.compare(p, q) + + def compare(self, tree1, tree2): + if not tree1 and tree2: + return False + elif tree1 and not tree2: + return False + elif not tree1 and not tree2: + return True + elif tree1.val != tree2.val: #注意这里我没有使用else + return False + + #此时就是:左右节点都不为空,且数值相同的情况 + #此时才做递归,做下一层的判断 + compareLeft = self.compare(tree1.left, tree2.left) #左子树:左、 右子树:左 + compareRight = self.compare(tree1.right, tree2.right) #左子树:右、 右子树:右 + isSame = compareLeft and compareRight #左子树:中、 右子树:中(逻辑处理) + return isSame +``` + +257.二叉的所有路径 +> 递归中隐藏着回溯 +```python +class Solution: + def binaryTreePaths(self, root: TreeNode) -> List[str]: + result = [] + path = [] + if not root: + return result + self.traversal(root, path, result) + return result + + def traversal(self, cur, path, result): + path.append(cur.val) + #这才到了叶子节点 + if not cur.left and not cur.right: + sPath = "" + for i in range(len(path)-1): + sPath += str(path[i]) + sPath += "->" + sPath += str(path[len(path)-1]) + result.append(sPath) + return + if cur.left: + self.traversal(cur.left, path, result) + path.pop() #回溯 + if cur.right: + self.traversal(cur.right, path, result) + path.pop() #回溯 +``` + +> 精简版 +```python +class Solution: + def binaryTreePaths(self, root: TreeNode) -> List[str]: + result = [] + path = "" + if not root: + return result + self.traversal(root, path, result) + return result + + def traversal(self, cur, path, result): + path += str(cur.val) #中 + if not cur.left and not cur.right: + result.append(path) + return + if cur.left: + self.traversal(cur.left, path+"->", result) #左 回溯就隐藏在这里 + if cur.right: + self.traversal(cur.right, path+"->", result) #右 回溯就隐藏在这里 +``` Go: +100.相同的树 +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func isSameTree(p *TreeNode, q *TreeNode) bool { + switch { + case p == nil && q == nil: + return true + case p == nil || q == nil: + fallthrough + case p.Val != q.Val: + return false + } + return isSameTree(p.Left, q.Left) && isSameTree(p.Right, q.Right) +} +``` + +257.二叉的所有路径 +> 递归法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func binaryTreePaths(root *TreeNode) []string { + var result []string + traversal(root,&result,"") + return result +} +func traversal(root *TreeNode,result *[]string,pathStr string){ + //判断是否为第一个元素 + if len(pathStr)!=0{ + pathStr=pathStr+"->"+strconv.Itoa(root.Val) + }else{ + pathStr=strconv.Itoa(root.Val) + } + //判断是否为叶子节点 + if root.Left==nil&&root.Right==nil{ + *result=append(*result,pathStr) + return + } + //左右 + if root.Left!=nil{ + traversal(root.Left,result,pathStr) + } + if root.Right!=nil{ + traversal(root.Right,result,pathStr) + } +} +``` + +> 回溯法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func binaryTreePaths(root *TreeNode) []string { + var result []string + var path []int + traversal(root,&result,&path) + return result +} +func traversal(root *TreeNode,result *[]string,path *[]int){ + *path=append(*path,root.Val) + //判断是否为叶子节点 + if root.Left==nil&&root.Right==nil{ + pathStr:=strconv.Itoa((*path)[0]) + for i:=1;i"+strconv.Itoa((*path)[i]) + } + *result=append(*result,pathStr) + return + } + //左右 + if root.Left!=nil{ + traversal(root.Left,result,path) + *path=(*path)[:len(*path)-1]//回溯到上一个节点(因为traversal会加下一个节点值到path中) + } + if root.Right!=nil{ + traversal(root.Right,result,path) + *path=(*path)[:len(*path)-1]//回溯 + } +} +``` + + + diff --git a/problems/二叉树总结篇.md b/problems/二叉树总结篇.md index 05c13a47..d4af8aae 100644 --- a/problems/二叉树总结篇.md +++ b/problems/二叉树总结篇.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 二叉树:总结篇!(需要掌握的二叉树技能都在这里了) diff --git a/problems/二叉树理论基础.md b/problems/二叉树理论基础.md index 60e65c69..55383e91 100644 --- a/problems/二叉树理论基础.md +++ b/problems/二叉树理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 二叉树理论基础篇 我们要开启新的征程了,大家跟上! diff --git a/problems/二叉树的理论基础.md b/problems/二叉树的理论基础.md deleted file mode 100644 index 271e1f4e..00000000 --- a/problems/二叉树的理论基础.md +++ /dev/null @@ -1,202 +0,0 @@ -

- - - - -

-

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

- -## 二叉树理论基础 - -我们要开启新的征程了,大家跟上! - -说道二叉树,大家对于二叉树其实都很熟悉了,本文呢我也不想教科书式的把二叉树的基础内容再啰嗦一遍,所以一下我讲的都是一些比较重点的内容。 - -相信只要耐心看完,都会有所收获。 - -## 二叉树的种类 - -在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。 - -### 满二叉树 - -满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。 - -如图所示: - - - -这棵二叉树为满二叉树,也可以说深度为 k,有 $(2^k)-1$ 个节点的二叉树。 - - -### 完全二叉树 - -什么是完全二叉树? - -完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1 ~ $2^{(h-1)}$  个节点。 - -**大家要自己看完全二叉树的定义,很多同学对完全二叉树其实不是真正的懂了。** - -我来举一个典型的例子如题: - - - -相信不少同学最后一个二叉树是不是完全二叉树都中招了。 - -**之前我们刚刚讲过优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。** - -### 二叉搜索树 - -前面介绍的书,都没有数值的,而二叉搜索树是有数值的了,**二叉搜索树是一个有序树**。 - - -* 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; -* 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; -* 它的左、右子树也分别为二叉排序树 - -下面这两棵树都是搜索树 - - - -### 平衡二叉搜索树 - -平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。 - -如图: - - - -最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。 - -**C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树**,所以map、set的增删操作时间时间复杂度是logn,注意我这里没有说unordered_map、unordered_set,unordered_map、unordered_map底层实现是哈希表。 - -**所以大家使用自己熟悉的编程语言写算法,一定要知道常用的容器底层都是如何实现的,最基本的就是map、set等等,否则自己写的代码,自己对其性能分析都分析不清楚!** - - -## 二叉树的存储方式 - -**二叉树可以链式存储,也可以顺序存储。** - -那么链式存储方式就用指针, 顺序存储的方式就是用数组。 - -顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。 - -链式存储如图: - - - -链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢? - -其实就是用数组来存储二叉树,顺序存储的方式如图: - - - -用数组来存储二叉树如何遍历的呢? - -**如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。** - -但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。 - -**所以大家要了解,用数组依然可以表示二叉树。** - -## 二叉树的遍历方式 - -关于二叉树的遍历方式,要知道二叉树遍历的基本方式都有哪些。 - -一些同学用做了很多二叉树的题目了,可能知道前序、中序、后序遍历,可能知道层序遍历,但是却没有框架。 - -我这里把二叉树的几种遍历方式列出来,大家就可以一一串起来了。 - -二叉树主要有两种遍历方式: -1. 深度优先遍历:先往深走,遇到叶子节点再往回走。 -2. 广度优先遍历:一层一层的去遍历。 - -**这两种遍历是图论中最基本的两种遍历方式**,后面在介绍图论的时候 还会介绍到。 - -那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式: - -* 深度优先遍历 - * 前序遍历(递归法,迭代法) - * 中序遍历(递归法,迭代法) - * 后序遍历(递归法,迭代法) -* 广度优先遍历 - * 层次遍历(迭代法) - - -在深度优先遍历中:有三个顺序,前序、中序、后序遍历, 有同学总分不清这三个顺序,经常搞混,我这里教大家一个技巧。 - -**这里前、中、后,其实指的就是中间节点的遍历顺序**,只要大家记住 前序、中序、后序指的就是中间节点的位置就可以了。 - -看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式 - -* 前序遍历:中左右 -* 中序遍历:左中右 -* 后序遍历:左右中 - -大家可以对着如下图,看看自己理解的前后中序有没有问题。 - - - -最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前序、中序、后序遍历,使用递归是比较方便的。 - -**之前我们讲栈与队列的时候,就说过栈其实就是递归的一种是实现结构**,也就说前序、中序、后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。 - -而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。 - -**这里其实我们又了解了栈与队列的一个应用场景了。** - -具体的实现我们后面都会讲的,这里大家先要清楚这些理论基础。 - -## 二叉树的定义 - -刚刚我们说过了二叉树有两种存储方式顺序存储,和链式存储,顺序存储就是用数组来存,这个定义没啥可说的,我们来看看链式存储的二叉树节点的定义方式。 - - -C++代码如下: - -``` -struct TreeNode { - int val; - TreeNode *left; - TreeNode *right; - TreeNode(int x) : val(x), left(NULL), right(NULL) {} -}; -``` - -大家会发现二叉树的定义 和链表是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子. - -这里要提醒大家要注意二叉树节点定义的书写方式。 - -**在现场面试的时候 面试官可能要求手写代码,所以数据结构的定义以及简单逻辑的代码一定要锻炼白纸写出来。** - -因为我们在刷leetcode的时候,节点的定义默认都定义好了,真到面试的时候,需要自己写节点定义的时候,有时候会一脸懵逼! - -## 总结 - -二叉树是一种基础数据结构,在算法面试中都是常客,也是众多数据结构的基石。 - -本篇我们介绍了二叉树的种类、存储方式、遍历方式以及定义,比较全面的介绍了二叉树各个方面的重点,帮助大家扫一遍基础。 - -**说道二叉树,就不得不说递归,很多同学对递归都是又熟悉又陌生,递归的代码一般很简短,但每次都是一看就会,一写就废。** - - - -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - - - ------------------------ -* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站视频:[代码随想录](https://space.bilibili.com/525438321) -* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
diff --git a/problems/二叉树的统一迭代法.md b/problems/二叉树的统一迭代法.md index 3ea4eeaf..533bdfe7 100644 --- a/problems/二叉树的统一迭代法.md +++ b/problems/二叉树的统一迭代法.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -153,13 +153,377 @@ public: Java: + 迭代法前序遍历代码如下: + ```java + class Solution { + + public List preorderTraversal(TreeNode root) { + List result = new LinkedList<>(); + Stack st = new Stack<>(); + if (root != null) st.push(root); + while (!st.empty()) { + TreeNode node = st.peek(); + if (node != null) { + st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 + if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) + if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) + st.push(node); // 添加中节点 + st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 + + } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 + st.pop(); // 将空节点弹出 + node = st.peek(); // 重新取出栈中元素 + st.pop(); + result.add(node.val); // 加入到结果集 + } + } + return result; + } +} + ``` + 迭代法中序遍历代码如下: + ```java + class Solution { + public List inorderTraversal(TreeNode root) { + List result = new LinkedList<>(); + Stack st = new Stack<>(); + if (root != null) st.push(root); + while (!st.empty()) { + TreeNode node = st.peek(); + if (node != null) { + st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 + if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) + st.push(node); // 添加中节点 + st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 + if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) + } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 + st.pop(); // 将空节点弹出 + node = st.peek(); // 重新取出栈中元素 + st.pop(); + result.add(node.val); // 加入到结果集 + } + } + return result; + } +} + ``` + 迭代法后序遍历代码如下: + ```java + class Solution { + + public List postorderTraversal(TreeNode root) { + List result = new LinkedList<>(); + Stack st = new Stack<>(); + if (root != null) st.push(root); + while (!st.empty()) { + TreeNode node = st.peek(); + if (node != null) { + st.pop(); // 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中 + st.push(node); // 添加中节点 + st.push(null); // 中节点访问过,但是还没有处理,加入空节点做为标记。 + if (node.right!=null) st.push(node.right); // 添加右节点(空节点不入栈) + if (node.left!=null) st.push(node.left); // 添加左节点(空节点不入栈) + + } else { // 只有遇到空节点的时候,才将下一个节点放进结果集 + st.pop(); // 将空节点弹出 + node = st.peek(); // 重新取出栈中元素 + st.pop(); + result.add(node.val); // 加入到结果集 + } + } + return result; + } +} + + ``` Python: +> 迭代法前序遍历 +```python +class Solution: + def preorderTraversal(self, root: TreeNode) -> List[int]: + result = [] + st= [] + if root: + st.append(root) + while st: + node = st.pop() + if node != None: + if node.right: #右 + st.append(node.right) + if node.left: #左 + st.append(node.left) + st.append(node) #中 + st.append(None) + else: + node = st.pop() + result.append(node.val) + return result +``` + +> 迭代法中序遍历 +```python +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + result = [] + st = [] + if root: + st.append(root) + while st: + node = st.pop() + if node != None: + if node.right: #添加右节点(空节点不入栈) + st.append(node.right) + + st.append(node) #添加中节点 + st.append(None) #中节点访问过,但是还没有处理,加入空节点做为标记。 + + if node.left: #添加左节点(空节点不入栈) + st.append(node.left) + else: #只有遇到空节点的时候,才将下一个节点放进结果集 + node = st.pop() #重新取出栈中元素 + result.append(node.val) #加入到结果集 + return result +``` + +> 迭代法后序遍历 +```python +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + result = [] + st = [] + if root: + st.append(root) + while st: + node = st.pop() + if node != None: + st.append(node) #中 + st.append(None) + + if node.right: #右 + st.append(node.right) + if node.left: #左 + st.append(node.left) + else: + node = st.pop() + result.append(node.val) + return result +``` Go: +> 前序遍历统一迭代法 +```GO + /** + type Element struct { + // 元素保管的值 + Value interface{} + // 内含隐藏或非导出字段 +} + +func (l *List) Back() *Element +前序遍历:中左右 +压栈顺序:右左中 + **/ +func preorderTraversal(root *TreeNode) []int { + if root == nil { + return nil + } + var stack = list.New()//栈 + res:=[]int{}//结果集 + stack.PushBack(root) + var node *TreeNode + for stack.Len()>0{ + e := stack.Back() + stack.Remove(e)//弹出元素 + if e.Value==nil{// 如果为空,则表明是需要处理中间节点 + e=stack.Back()//弹出元素(即中间节点) + stack.Remove(e)//删除中间节点 + node=e.Value.(*TreeNode) + res=append(res,node.Val)//将中间节点加入到结果集中 + continue//继续弹出栈中下一个节点 + } + node = e.Value.(*TreeNode) + //压栈顺序:右左中 + if node.Right!=nil{ + stack.PushBack(node.Right) + } + if node.Left!=nil{ + stack.PushBack(node.Left) + } + stack.PushBack(node)//中间节点压栈后再压入nil作为中间节点的标志符 + stack.PushBack(nil) + } + return res + +} +``` + +> 中序遍历统一迭代法 + +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ + //中序遍历:左中右 + //压栈顺序:右中左 +func inorderTraversal(root *TreeNode) []int { + if root==nil{ + return nil + } + stack:=list.New()//栈 + res:=[]int{}//结果集 + stack.PushBack(root) + var node *TreeNode + for stack.Len()>0{ + e := stack.Back() + stack.Remove(e) + if e.Value==nil{// 如果为空,则表明是需要处理中间节点 + e=stack.Back()//弹出元素(即中间节点) + stack.Remove(e)//删除中间节点 + node=e.Value.(*TreeNode) + res=append(res,node.Val)//将中间节点加入到结果集中 + continue//继续弹出栈中下一个节点 + } + node = e.Value.(*TreeNode) + //压栈顺序:右中左 + if node.Right!=nil{ + stack.PushBack(node.Right) + } + stack.PushBack(node)//中间节点压栈后再压入nil作为中间节点的标志符 + stack.PushBack(nil) + if node.Left!=nil{ + stack.PushBack(node.Left) + } + } + return res +} +``` + +> 后序遍历统一迭代法 + +```go +//后续遍历:左右中 +//压栈顺序:中右左 +func postorderTraversal(root *TreeNode) []int { + if root == nil { + return nil + } + var stack = list.New()//栈 + res:=[]int{}//结果集 + stack.PushBack(root) + var node *TreeNode + for stack.Len()>0{ + e := stack.Back() + stack.Remove(e) + if e.Value==nil{// 如果为空,则表明是需要处理中间节点 + e=stack.Back()//弹出元素(即中间节点) + stack.Remove(e)//删除中间节点 + node=e.Value.(*TreeNode) + res=append(res,node.Val)//将中间节点加入到结果集中 + continue//继续弹出栈中下一个节点 + } + node = e.Value.(*TreeNode) + //压栈顺序:中右左 + stack.PushBack(node)//中间节点压栈后再压入nil作为中间节点的标志符 + stack.PushBack(nil) + if node.Right!=nil{ + stack.PushBack(node.Right) + } + if node.Left!=nil{ + stack.PushBack(node.Left) + } + } + return res +} +``` + +javaScript: + +> 前序遍历统一迭代法 + +```js + +// 前序遍历:中左右 +// 压栈顺序:右左中 + +var preorderTraversal = function(root, res = []) { + const stack = []; + if (root) stack.push(root); + while(stack.length) { + const node = stack.pop(); + if(!node) { + res.push(stack.pop().val); + continue; + } + if (node.right) stack.push(node.right); // 右 + if (node.left) stack.push(node.left); // 左 + stack.push(node); // 中 + stack.push(null); + }; + return res; +}; + +``` + +> 中序遍历统一迭代法 + +```js + +// 中序遍历:左中右 +// 压栈顺序:右中左 + +var inorderTraversal = function(root, res = []) { + const stack = []; + if (root) stack.push(root); + while(stack.length) { + const node = stack.pop(); + if(!node) { + res.push(stack.pop().val); + continue; + } + if (node.right) stack.push(node.right); // 右 + stack.push(node); // 中 + stack.push(null); + if (node.left) stack.push(node.left); // 左 + }; + return res; +}; + +``` + +> 后序遍历统一迭代法 + +```js + +// 后续遍历:左右中 +// 压栈顺序:中右左 + +var postorderTraversal = function(root, res = []) { + const stack = []; + if (root) stack.push(root); + while(stack.length) { + const node = stack.pop(); + if(!node) { + res.push(stack.pop().val); + continue; + } + stack.push(node); // 中 + stack.push(null); + if (node.right) stack.push(node.right); // 右 + if (node.left) stack.push(node.left); // 左 + }; + return res; +}; + +``` diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index 2647616b..8706dc47 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 二叉树的迭代遍历 @@ -160,11 +160,239 @@ Java: Python: +```python3 +# 前序遍历-迭代-LC144_二叉树的前序遍历 +class Solution: + def preorderTraversal(self, root: TreeNode) -> List[int]: + # 根结点为空则返回空列表 + if not root: + return [] + stack = [root] + result = [] + while stack: + node = stack.pop() + # 中结点先处理 + result.append(node.val) + # 右孩子先入栈 + if node.right: + stack.append(node.right) + # 左孩子后入栈 + if node.left: + stack.append(node.left) + return result + +# 中序遍历-迭代-LC94_二叉树的中序遍历 +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + if not root: + return [] + stack = [] # 不能提前将root结点加入stack中 + result = [] + cur = root + while cur or stack: + # 先迭代访问最底层的左子树结点 + if cur: + stack.append(cur) + cur = cur.left + # 到达最左结点后处理栈顶结点 + else: + cur = stack.pop() + result.append(cur.val) + # 取栈顶元素右结点 + cur = cur.right + return result + +# 后序遍历-迭代-LC145_二叉树的后序遍历 +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + if not root: + return [] + stack = [root] + result = [] + while stack: + node = stack.pop() + # 中结点先处理 + result.append(node.val) + # 左孩子先入栈 + if node.left: + stack.append(node.left) + # 右孩子后入栈 + if node.right: + stack.append(node.right) + # 将最终的数组翻转 + return result[::-1] +``` Go: +> 迭代法前序遍历 +```go +//迭代法前序遍历 +/** + type Element struct { + // 元素保管的值 + Value interface{} + // 内含隐藏或非导出字段 +} +func (l *List) Back() *Element +前序遍历:中左右 +压栈顺序:右左中 + **/ +func preorderTraversal(root *TreeNode) []int { + if root == nil { + return nil + } + var stack = list.New() + stack.PushBack(root.Right) + stack.PushBack(root.Left) + res:=[]int{} + res=append(res,root.Val) + for stack.Len()>0 { + e:=stack.Back() + stack.Remove(e) + node := e.Value.(*TreeNode)//e是Element类型,其值为e.Value.由于Value为接口,所以要断言 + if node==nil{ + continue + } + res=append(res,node.Val) + stack.PushBack(node.Right) + stack.PushBack(node.Left) + } + return res +} +``` + +> 迭代法后序遍历 + +```go +//迭代法后序遍历 +//后续遍历:左右中 +//压栈顺序:中右左(按照前序遍历思路),再反转结果数组 +func postorderTraversal(root *TreeNode) []int { + if root == nil { + return nil + } + var stack = list.New() + stack.PushBack(root.Left) + stack.PushBack(root.Right) + res:=[]int{} + res=append(res,root.Val) + for stack.Len()>0 { + e:=stack.Back() + stack.Remove(e) + node := e.Value.(*TreeNode)//e是Element类型,其值为e.Value.由于Value为接口,所以要断言 + if node==nil{ + continue + } + res=append(res,node.Val) + stack.PushBack(node.Left) + stack.PushBack(node.Right) + } + for i:=0;i 迭代法中序遍历 + +```go +//迭代法中序遍历 +func inorderTraversal(root *TreeNode) []int { + rootRes:=[]int{} + if root==nil{ + return nil + } + stack:=list.New() + node:=root + //先将所有左节点找到,加入栈中 + for node!=nil{ + stack.PushBack(node) + node=node.Left + } + //其次对栈中的每个节点先弹出加入到结果集中,再找到该节点的右节点的所有左节点加入栈中 + for stack.Len()>0{ + e:=stack.Back() + node:=e.Value.(*TreeNode) + stack.Remove(e) + //找到该节点的右节点,再搜索他的所有左节点加入栈中 + rootRes=append(rootRes,node.Val) + node=node.Right + for node!=nil{ + stack.PushBack(node) + node=node.Left + } + } + return rootRes +} +``` + +javaScript + +```js + +前序遍历: + +// 入栈 右 -> 左 +// 出栈 中 -> 左 -> 右 +var preorderTraversal = function(root, res = []) { + if(!root) return res; + const stack = [root]; + let cur = null; + while(stack.length) { + cur = stack.pop(); + res.push(cur.val); + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + return res; +}; + +中序遍历: + +// 入栈 左 -> 右 +// 出栈 左 -> 中 -> 右 + +var inorderTraversal = function(root, res = []) { + const stack = []; + let cur = root; + while(stack.length || cur) { + if(cur) { + stack.push(cur); + // 左 + cur = cur.left; + } else { + // --> 弹出 中 + cur = stack.pop(); + res.push(cur.val); + // 右 + cur = cur.right; + } + }; + return res; +}; + +后序遍历: + +// 入栈 左 -> 右 +// 出栈 中 -> 右 -> 左 结果翻转 + +var postorderTraversal = function(root, res = []) { + if (!root) return res; + const stack = [root]; + let cur = null; + do { + cur = stack.pop(); + res.push(cur.val); + cur.left && stack.push(cur.left); + cur.right && stack.push(cur.right); + } while(stack.length); + return res.reverse(); +}; +``` ----------------------- diff --git a/problems/二叉树的递归遍历.md b/problems/二叉树的递归遍历.md index 02d4b060..f2072e30 100644 --- a/problems/二叉树的递归遍历.md +++ b/problems/二叉树的递归遍历.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

@@ -115,10 +115,108 @@ void traversal(TreeNode* cur, vector& vec) { Java: +```Java +// 前序遍历·递归·LC144_二叉树的前序遍历 +class Solution { + ArrayList preOrderReverse(TreeNode root) { + ArrayList result = new ArrayList(); + preOrder(root, result); + return result; + } + void preOrder(TreeNode root, ArrayList result) { + if (root == null) { + return; + } + result.add(root.val); // 注意这一句 + preOrder(root.left, result); + preOrder(root.right, result); + } +} +// 中序遍历·递归·LC94_二叉树的中序遍历 +class Solution { + public List inorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + inorder(root, res); + return res; + } + + void inorder(TreeNode root, List list) { + if (root == null) { + return; + } + inorder(root.left, list); + list.add(root.val); // 注意这一句 + inorder(root.right, list); + } +} +// 后序遍历·递归·LC145_二叉树的后序遍历 +class Solution { + public List postorderTraversal(TreeNode root) { + List res = new ArrayList<>(); + postorder(root, res); + return res; + } + + void postorder(TreeNode root, List list) { + if (root == null) { + return; + } + postorder(root.left, list); + postorder(root.right, list); + list.add(root.val); // 注意这一句 + } +} +``` Python: +```python3 +# 前序遍历-递归-LC144_二叉树的前序遍历 +class Solution: + def preorderTraversal(self, root: TreeNode) -> List[int]: + # 保存结果 + result = [] + + def traversal(root: TreeNode): + if root == None: + return + result.append(root.val) # 前序 + traversal(root.left) # 左 + traversal(root.right) # 右 + traversal(root) + return result + +# 中序遍历-递归-LC94_二叉树的中序遍历 +class Solution: + def inorderTraversal(self, root: TreeNode) -> List[int]: + result = [] + + def traversal(root: TreeNode): + if root == None: + return + traversal(root.left) # 左 + result.append(root.val) # 中序 + traversal(root.right) # 右 + + traversal(root) + return result + +# 后序遍历-递归-LC145_二叉树的后序遍历 +class Solution: + def postorderTraversal(self, root: TreeNode) -> List[int]: + result = [] + + def traversal(root: TreeNode): + if root == None: + return + traversal(root.left) # 左 + traversal(root.right) # 右 + result.append(root.val) # 后序 + + traversal(root) + return result +``` Go: @@ -128,7 +226,7 @@ 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) @@ -174,6 +272,94 @@ func PostorderTraversal(root *TreeNode) (res []int) { } ``` +javaScript: + +```js + +前序遍历: + +var preorderTraversal = function(root, res = []) { + if (!root) return res; + res.push(root.val); + preorderTraversal(root.left, res) + preorderTraversal(root.right, res) + return res; +}; + +中序遍历: + +var inorderTraversal = function(root, res = []) { + if (!root) return res; + inorderTraversal(root.left, res); + res.push(root.val); + inorderTraversal(root.right, res); + return res; +}; + +后序遍历: + +var postorderTraversal = function(root, res = []) { + if (!root) return res; + postorderTraversal(root.left, res); + postorderTraversal(root.right, res); + res.push(root.val); + return res; +}; +``` +Javascript版本: + +前序遍历: +```Javascript +var preorderTraversal = function(root) { + let res=[]; + const dfs=function(root){ + if(root===null)return ; + //先序遍历所以从父节点开始 + res.push(root.val); + //递归左子树 + dfs(root.left); + //递归右子树 + dfs(root.right); + } + //只使用一个参数 使用闭包进行存储结果 + dfs(root); + return res; +}; +``` +中序遍历 +```javascript +var inorderTraversal = function(root) { + let res=[]; + const dfs=function(root){ + if(root===null){ + return ; + } + dfs(root.left); + res.push(root.val); + dfs(root.right); + } + dfs(root); + return res; +}; +``` + +后序遍历 +```javascript +var postorderTraversal = function(root) { + let res=[]; + const dfs=function(root){ + if(root===null){ + return ; + } + dfs(root.left); + dfs(root.right); + res.push(root.val); + } + dfs(root); + return res; +}; +``` + diff --git a/problems/关于时间复杂度,你不知道的都在这里!.md b/problems/关于时间复杂度,你不知道的都在这里!.md index b4ff89fa..fe378228 100644 --- a/problems/关于时间复杂度,你不知道的都在这里!.md +++ b/problems/关于时间复杂度,你不知道的都在这里!.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

相信每一位录友都接触过时间复杂度,「代码随想录」已经也讲了一百多道经典题目了,是时候对时间复杂度来一个深度的剖析了,很早之前就写过一篇,当时文章还没有人看,Carl感觉有价值的东西值得让更多的人看到,哈哈。 diff --git a/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md b/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md index f8598d3f..fbcdf970 100644 --- a/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md +++ b/problems/前序/BAT级别技术面试流程和注意事项都在这里了.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + 大型互联网企业一般通过几轮技术面试来考察大家的各项能力,一般流程如下: @@ -212,11 +213,12 @@ leetcode是专门针对算法练习的题库,leetcode现在也推出了中文 大家加油! ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md b/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md index f640abad..f6218777 100644 --- a/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md +++ b/problems/前序/On的算法居然超时了,此时的n究竟是多大?.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + 一些同学可能对计算机运行的速度还没有概念,就是感觉计算机运行速度应该会很快,那么在leetcode上做算法题目的时候为什么会超时呢? @@ -219,11 +220,12 @@ int main() { 就酱,如果感觉「代码随想录」很干货,就帮忙宣传一波吧,很多录友发现这里之后都感觉相见恨晚! ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/上海互联网公司总结.md b/problems/前序/上海互联网公司总结.md index 40478cb6..386a0a93 100644 --- a/problems/前序/上海互联网公司总结.md +++ b/problems/前序/上海互联网公司总结.md @@ -1,10 +1,11 @@ -

- + - +

+

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

+ # 上海互联网公司总结 @@ -124,11 +125,12 @@ 相对于北京和上海,深圳互联网公司断层很明显,腾讯一家独大,二线三线垂直行业的公司很少,所以说深圳腾讯的员工流动性相对是较低的,因为基本没得选。 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/什么是核心代码模式,什么又是ACM模式?.md b/problems/前序/什么是核心代码模式,什么又是ACM模式?.md index 70651817..5a19a72a 100644 --- a/problems/前序/什么是核心代码模式,什么又是ACM模式?.md +++ b/problems/前序/什么是核心代码模式,什么又是ACM模式?.md @@ -1,12 +1,12 @@ -

- + - +

+

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

+ --------------------------- 现在很多企业都在牛客上进行面试,**很多录友和我反馈说搞不懂牛客上输入代码的ACM模式**。 什么是ACM输入模式呢? 就是自己构造输入数据格式,把要需要处理的容器填充好,OJ不会给你任何代码,包括include哪些函数都要自己写,最后也要自己控制返回数据的格式。 @@ -115,10 +115,11 @@ int main() { 如果大家有精力的话,也可以去POJ上去刷刷题,POJ是ACM选手首选OJ,输入模式也是ACM模式。 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/代码风格.md b/problems/前序/代码风格.md index 2be1ac36..a9eabd0c 100644 --- a/problems/前序/代码风格.md +++ b/problems/前序/代码风格.md @@ -1,23 +1,25 @@ - -

- + - +

+

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

--------------------------- -# 看了这么多代码,谈一谈代码风格! -其实在交流群里经常能看到大家发出来的代码,可以看出很多录友对代码规范应该不甚了解,代码看起来并不舒服。 + +# 看了这么多代码,谈一谈代码风格! + +最近看了很多录友在[leetcode-master](https://mp.weixin.qq.com/s/wZRTrA9Rbvgq1yEkSw4vfQ)上提交的代码,发现很多录友的代码其实并不规范,这一点平时在交流群和知识星球里也能看出来。 + +很多录友对代码规范应该不甚了解,代码看起来并不舒服。 所以呢,我给大家讲一讲代码规范,我主要以C++代码为例。 需要强调一下,代码规范并不是仅仅是让代码看着舒服,这是一个很重要的习惯。 -# 题外话 +## 题外话 工作之后,**特别是在大厂,看谁的技术牛不牛逼,不用看谁写出多牛逼的代码,就代码风格扫一眼,立刻就能看出来是正规军还是野生程序员**。 @@ -25,15 +27,15 @@ 现在一些小公司,甚至大公司里的某些技术团队也不注重代码规范,赶进度撸出功能就完事,这种情况就要分两方面看: -* 第一种情况:这个项目在业务上赚到钱了,每年年终好几十万,那项目前期还关心啥代码风格,赶进度把功能撸出来,赚钱就完事了,例如15年的王者荣耀。 +* 第一种情况:这个项目在业务上具有巨大潜力,需要抢占市场,只要先站住市场就能赚到钱,每年年终好几十万,那项目前期还关心啥代码风格,赶进度把功能撸出来,赚钱就完事了,例如12年的微信,15年的王者荣耀。这些项目都是后期在不断优化的。 * 第二种情况:这个项目没赚到钱,半死不活的,代码还没有设计也没有规范,这样对技术人员的伤害就非常大了。 **而不注重代码风格的团队,99.99%都是第二种情况**,如果你赶上了第一种情况,那就恭喜你了,本文下面的内容可以不用看了,哈哈。 -# 代码规范 +## 代码规范 -## 变量命名 +### 变量命名 这里我简单说一说规范问题。 @@ -67,7 +69,7 @@ ![编程风格](https://img-blog.csdnimg.cn/20201119173039835.png) -## 水平留白(代码空格) +### 水平留白(代码空格) 经常看到有的同学的代码都堆在一起,看起来都费劲,或者是有的间隔有空格,有的没有空格,很不统一,有的同学甚至为了让代码精简,把所有空格都省略掉了。 @@ -89,7 +91,7 @@ int i, j; for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) ``` -花括号和函数保持同一行,并有一个空格例如: +大括号和函数保持同一行,并有一个空格例如: ``` while (n) { @@ -123,9 +125,13 @@ public: }; ``` -当然我并不是说一定要按照Google的规范来,代码风格其实统一就行,没有严格的说谁对谁错。 +这里关于大括号是否要重启一行? -# 总结 +Google规范是 大括号和 控制语句保持同一行的,我个人也很认可这种写法,因为可以缩短代码的行数,特别是项目中代码行数很多的情况下,这种写法是可以提高阅读代码的效率。 + +当然我并不是说一定要按照Google的规范来,**代码风格其实统一就行,没有严格的说谁对谁错**。 + +## 总结 如果还是学生,使用C++的话,可以按照题解中我的代码风格来,还是比较标准的。 @@ -136,10 +142,11 @@ public: 就酱,以后我还会陆续分享,关于代码,求职,学习工作之类的内容。 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/关于时间复杂度,你不知道的都在这里!.md b/problems/前序/关于时间复杂度,你不知道的都在这里!.md index bd3bd284..4ca9eede 100644 --- a/problems/前序/关于时间复杂度,你不知道的都在这里!.md +++ b/problems/前序/关于时间复杂度,你不知道的都在这里!.md @@ -1,12 +1,12 @@ -

- + - +

+

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

+ --------------------------- Carl大胆断言:这可能是你见过对时间复杂度分析最通透的一篇文章了。 @@ -117,7 +117,7 @@ O(2 * n^2 + 10 * n + 1000) < O(3 * n^2),所以说最后省略掉常数项系 ![时间复杂度1.png](https://img-blog.csdnimg.cn/20200728191447349.png) -假如有两个算法的时间复杂度,分别是log以2为底n的对数和log以10为底n的对数,那么这里如果还记得高中数学的话,应该不能理解`以2为底n的对数 = 以2为底10的对数 * 以10为底n的对数`。 +假如有两个算法的时间复杂度,分别是log以2为底n的对数和log以10为底n的对数,那么这里如果还记得高中数学的话,应该不难理解`以2为底n的对数 = 以2为底10的对数 * 以10为底n的对数`。 而以2为底10的对数是一个常数,在上文已经讲述了我们计算时间复杂度是忽略常数项系数的。 @@ -165,10 +165,11 @@ O(2 * n^2 + 10 * n + 1000) < O(3 * n^2),所以说最后省略掉常数项系 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/关于空间复杂度,可能有几个疑问?.md b/problems/前序/关于空间复杂度,可能有几个疑问?.md index 63940116..0208aa91 100644 --- a/problems/前序/关于空间复杂度,可能有几个疑问?.md +++ b/problems/前序/关于空间复杂度,可能有几个疑问?.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + # 空间复杂度分析 @@ -68,10 +69,11 @@ for (int i = 0; i < n; i++) { 至于如何求递归的空间复杂度,我会在专门写一篇文章来介绍的,敬请期待! ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md b/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md index df555c3b..6d3b4931 100644 --- a/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md +++ b/problems/前序/刷了这么多题,你了解自己代码的内存消耗么?.md @@ -1,12 +1,13 @@ - - -

- + - +

+

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

+ + + 理解代码的内存消耗,最关键是要知道自己所用编程语言的内存管理。 @@ -145,10 +146,11 @@ char型的数据和int型的数据挨在一起,该int数据从地址1开始, 之后也可以有意识的去学习自己所用的编程语言是如何管理内存的,这些也是程序员的内功。 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/力扣上的代码想在本地编译运行?.md b/problems/前序/力扣上的代码想在本地编译运行?.md index 04e19021..f4a9b0f3 100644 --- a/problems/前序/力扣上的代码想在本地编译运行?.md +++ b/problems/前序/力扣上的代码想在本地编译运行?.md @@ -1,10 +1,11 @@ -

- + - +

+

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

+ 很多录友都问过我一个问题,就是力扣上的代码如何在本地编译运行? @@ -62,10 +63,11 @@ int main() { ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/北京互联网公司总结.md b/problems/前序/北京互联网公司总结.md index f1c9ad9a..4dcaa691 100644 --- a/problems/前序/北京互联网公司总结.md +++ b/problems/前序/北京互联网公司总结.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + # 北京互联网公司总结 @@ -110,11 +111,12 @@ 就酱,我也会陆续整理其他城市的互联网公司,希望对大家有所帮助。 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/广州互联网公司总结.md b/problems/前序/广州互联网公司总结.md index 47b7bf77..e9b2af00 100644 --- a/problems/前序/广州互联网公司总结.md +++ b/problems/前序/广州互联网公司总结.md @@ -1,10 +1,11 @@ -

- + - +

+

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

+ # 广州互联网公司总结 @@ -73,11 +74,12 @@ ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/成都互联网公司总结.md b/problems/前序/成都互联网公司总结.md index 2f32849e..2435ccb2 100644 --- a/problems/前序/成都互联网公司总结.md +++ b/problems/前序/成都互联网公司总结.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + # 成都互联网公司总结 @@ -71,11 +72,12 @@ ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/杭州互联网公司总结.md b/problems/前序/杭州互联网公司总结.md index 23cd4183..e2691469 100644 --- a/problems/前序/杭州互联网公司总结.md +++ b/problems/前序/杭州互联网公司总结.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + # 杭州互联网公司总结 @@ -82,11 +83,12 @@ ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/深圳互联网公司总结.md b/problems/前序/深圳互联网公司总结.md index b7d15686..4b68dad6 100644 --- a/problems/前序/深圳互联网公司总结.md +++ b/problems/前序/深圳互联网公司总结.md @@ -1,10 +1,11 @@ -

- + - +

+

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

+ # 深圳互联网公司总结 @@ -76,11 +77,12 @@ * 广发证券,深交所 * 珍爱网(珍爱网是国内知名的婚恋服务网站之一) ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/程序员写文档工具.md b/problems/前序/程序员写文档工具.md index 34f2f777..5530e30f 100644 --- a/problems/前序/程序员写文档工具.md +++ b/problems/前序/程序员写文档工具.md @@ -1,12 +1,13 @@ - - -

- + - +

+

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

+ + + # 程序员应该用什么用具来写文档? @@ -132,10 +133,11 @@ Markdown支持部分html,例如这样 如果还没有掌握markdown的你还在等啥,赶紧使用markdown记录起来吧 ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/前序/程序员简历.md b/problems/前序/程序员简历.md index af52df8e..f9a226df 100644 --- a/problems/前序/程序员简历.md +++ b/problems/前序/程序员简历.md @@ -1,12 +1,12 @@ -

- + - +

+

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

+ --------------------------- # 程序员的简历应该这么写!!(附简历模板) @@ -131,9 +131,10 @@ Carl校招社招都拿过大厂的offer,同时也看过很多应聘者的简 就酱,「代码随想录」就是这么干货,Carl多年积累的简历技巧都毫不保留的写出来了,如果感觉对你有帮助,就宣传一波「代码随想录」吧,值得大家的关注! ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - +
diff --git a/problems/前序/递归算法的时间与空间复杂度分析.md b/problems/前序/递归算法的时间与空间复杂度分析.md index c8ef4723..f1501e8a 100644 --- a/problems/前序/递归算法的时间与空间复杂度分析.md +++ b/problems/前序/递归算法的时间与空间复杂度分析.md @@ -1,10 +1,11 @@ -

- + - +

+

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

+ # 递归算法的时间与空间复杂度分析! @@ -263,11 +264,12 @@ int binary_search( int arr[], int l, int r, int x) { ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - - +
diff --git a/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md b/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md index cb6aa604..16ba8361 100644 --- a/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md +++ b/problems/前序/通过一道面试题目,讲一讲递归算法的时间复杂度!.md @@ -1,11 +1,12 @@ - -

- + - +

+

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

+ + # 通过一道面试题目,讲一讲递归算法的时间复杂度! @@ -149,10 +150,11 @@ int function3(int x, int n) { 如果认真读完本篇,相信大家对递归算法的有一个新的认识的,同一道题目,同样是递归,效率可是不一样的! ------------------------- -* 微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站:[代码随想录](https://space.bilibili.com/525438321) + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) * 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) - - +
diff --git a/problems/剑指Offer05.替换空格.md b/problems/剑指Offer05.替换空格.md index 5f89241b..a4c0149f 100644 --- a/problems/剑指Offer05.替换空格.md +++ b/problems/剑指Offer05.替换空格.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 题目:剑指Offer 05.替换空格 @@ -154,7 +154,54 @@ Python: Go: +```go +// 遍历添加 +func replaceSpace(s string) string { + b := []byte(s) + result := make([]byte, 0) + for i := 0; i < len(b); i++ { + if b[i] == ' ' { + result = append(result, []byte("%20")...) + } else { + result = append(result, b[i]) + } + } + return string(result) +} +// 原地修改 +func replaceSpace(s string) string { + b := []byte(s) + length := len(b) + spaceCount := 0 + // 计算空格数量 + for _, v := range b { + if v == ' ' { + spaceCount++ + } + } + // 扩展原有切片 + resizeCount := spaceCount * 2 + tmp := make([]byte, resizeCount) + b = append(b, tmp...) + i := length - 1 + j := len(b) - 1 + for i >= 0 { + if b[i] != ' ' { + b[j] = b[i] + i-- + j-- + } else { + b[j] = '0' + b[j-1] = '2' + b[j-2] = '%' + i-- + j = j - 3 + } + } + return string(b) +} +``` diff --git a/problems/剑指Offer58-II.左旋转字符串.md b/problems/剑指Offer58-II.左旋转字符串.md index 3e9ab11f..39c8382c 100644 --- a/problems/剑指Offer58-II.左旋转字符串.md +++ b/problems/剑指Offer58-II.左旋转字符串.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 反转个字符串还有这么多用处? @@ -23,7 +23,7 @@ https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/ 示例 2: 输入: s = "lrloseumgh", k = 6 输出: "umghlrlose" -  + 限制: 1 <= k < s.length <= 10000 @@ -96,15 +96,54 @@ public: ## 其他语言版本 - Java: - - +```java +class Solution { + public String reverseLeftWords(String s, int n) { + int len=s.length(); + StringBuilder sb=new StringBuilder(s); + reverseString(sb,0,n-1); + reverseString(sb,n,len-1); + return sb.reverse().toString(); + } + public void reverseString(StringBuilder sb, int start, int end) { + while (start < end) { + char temp = sb.charAt(start); + sb.setCharAt(start, sb.charAt(end)); + sb.setCharAt(end, temp); + start++; + end--; + } + } +} +``` Python: - Go: +```go +func reverseLeftWords(s string, n int) string { + b := []byte(s) + // 1. 反转前n个字符 + // 2. 反转第n到end字符 + // 3. 反转整个字符 + reverse(b, 0, n-1) + reverse(b, n, len(b)-1) + reverse(b, 0, len(b)-1) + return string(b) +} +// 切片是引用传递 +func reverse(b []byte, left, right int){ + for left < right{ + b[left], b[right] = b[right],b[left] + left++ + right-- + } +} +``` + + + @@ -112,4 +151,4 @@ Go: * 作者微信:[程序员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 370eea58..cac77b65 100644 --- a/problems/动态规划-股票问题总结篇.md +++ b/problems/动态规划-股票问题总结篇.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

之前我们已经把力扣上股票系列的题目都讲过的,但没有来一篇股票总结,来帮大家高屋建瓴,所以总结篇这就来了! diff --git a/problems/动态规划总结篇.md b/problems/动态规划总结篇.md new file mode 100644 index 00000000..797f426a --- /dev/null +++ b/problems/动态规划总结篇.md @@ -0,0 +1,140 @@ + +

+ + + + +

+

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

+ + +如今动态规划已经讲解了42道经典题目,共50篇文章,是时候做一篇总结了。 + +关于动态规划,在专题第一篇[关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag)就说了动规五部曲,**而且强调了五部对解动规题目至关重要!** + +这是Carl做过一百多道动规题目总结出来的经验结晶啊,如果大家跟着「代码随想哦」刷过动规专题,一定会对这动规五部曲的作用感受极其深刻。 + +动规五部曲分别为: + +1. 确定dp数组(dp table)以及下标的含义 +2. 确定递推公式 +3. dp数组如何初始化 +4. 确定遍历顺序 +5. 举例推导dp数组 + +动规专题刚开始的时候,讲的题目比较简单,不少录友和我反应:这么简单的题目 讲的复杂了,不用那么多步骤分析,想出递推公式直接就AC这道题目了。 + +**Carl的观点一直都是 简单题是用来 巩固方法论的**。 简单题目是可以靠感觉,但后面稍稍难一点的题目,估计感觉就不好使了。 + +在动规专题讲解中,也充分体现出,这动规五部曲的重要性。 + +还有不少录友对动规的理解是:递推公式是才是最难最重要的,只要想出递归公式,其他都好办。 + +**其实这么想的同学基本对动规理解的不到位的**。 + +动规五部曲里,哪一部没想清楚,这道题目基本就做不出来,即使做出来了也没有想清楚,而是朦朦胧胧的就把题目过了。 + +* 如果想不清楚dp数组的具体含义,递归公式从何谈起,甚至初始化的时候就写错了。 +* 例如[动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww) 在这道题目中,初始化才是重头戏 +* 如果看过背包系列,特别是完全背包,那么两层for循环先后顺序绝对可以搞懵很多人,反而递归公式是简单的。 +* 至于推导dp数组的重要性,动规专题里几乎每篇Carl都反复强调,当程序结果不对的时候,一定要自己推导公式,看看和程序打印的日志是否一样。 + +好啦,我们再一起回顾一下,动态规划专题中我们都讲了哪些内容。 + +## 动划基础 + +* [关于动态规划,你该了解这些!](https://mp.weixin.qq.com/s/ocZwfPlCWrJtVGACqFNAag) +* [动态规划:斐波那契数](https://mp.weixin.qq.com/s/ko0zLJplF7n_4TysnPOa_w) +* [动态规划:爬楼梯](https://mp.weixin.qq.com/s/Ohop0jApSII9xxOMiFhGIw) +* [动态规划:使用最小花费爬楼梯](https://mp.weixin.qq.com/s/djZB9gkyLFAKcQcSvKDorA) +* [动态规划:不同路径](https://mp.weixin.qq.com/s/MGgGIt4QCpFMROE9X9he_A) +* [动态规划:不同路径还不够,要有障碍!](https://mp.weixin.qq.com/s/lhqF0O4le9-wvalptOVOww) +* [动态规划:整数拆分,你要怎么拆?](https://mp.weixin.qq.com/s/cVbyHrsWH_Rfzlj-ESr01A) +* [动态规划:不同的二叉搜索树](https://mp.weixin.qq.com/s/8VE8pDrGxTf8NEVYBDwONw) + +## 背包问题系列 + +背包问题大纲 + +* [动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w) +* [动态规划:关于01背包问题,你该了解这些!(滚动数组)](https://mp.weixin.qq.com/s/M4uHxNVKRKm5HPjkNZBnFA) +* [动态规划:分割等和子集可以用01背包!](https://mp.weixin.qq.com/s/sYw3QtPPQ5HMZCJcT4EaLQ) +* [动态规划:最后一块石头的重量 II](https://mp.weixin.qq.com/s/WbwAo3jaUaNJjvhHgq0BGg) +* [动态规划:目标和!](https://mp.weixin.qq.com/s/2pWmaohX75gwxvBENS-NCw) +* [动态规划:一和零!](https://mp.weixin.qq.com/s/x-u3Dsp76DlYqtCe0xEKJw) +* [动态规划:关于完全背包,你该了解这些!](https://mp.weixin.qq.com/s/akwyxlJ4TLvKcw26KB9uJw) +* [动态规划:给你一些零钱,你要怎么凑?](https://mp.weixin.qq.com/s/PlowDsI4WMBOzf3q80AksQ) +* [动态规划:Carl称它为排列总和!](https://mp.weixin.qq.com/s/Iixw0nahJWQgbqVNk8k6gA) +* [动态规划:以前我没得选,现在我选择再爬一次!](https://mp.weixin.qq.com/s/e_wacnELo-2PG76EjrUakA) +* [动态规划: 给我个机会,我再兑换一次零钱](https://mp.weixin.qq.com/s/dyk-xNilHzNtVdPPLObSeQ) +* [动态规划:一样的套路,再求一次完全平方数](https://mp.weixin.qq.com/s/VfJT78p7UGpDZsapKF_QJQ) +* [动态规划:单词拆分](https://mp.weixin.qq.com/s/3Spx1B6MbIYjS8YkVbByzA) +* [动态规划:关于多重背包,你该了解这些!](https://mp.weixin.qq.com/s/b-UUUmbvG7URWyCjQkiuuQ) +* [听说背包问题很难? 这篇总结篇来拯救你了](https://mp.weixin.qq.com/s/ZOehl3U1mDiyOQjFG1wNJA) + +## 打家劫舍系列 + +* [动态规划:开始打家劫舍!](https://mp.weixin.qq.com/s/UZ31WdLEEFmBegdgLkJ8Dw) +* [动态规划:继续打家劫舍!](https://mp.weixin.qq.com/s/kKPx4HpH3RArbRcxAVHbeQ) +* [动态规划:还要打家劫舍!](https://mp.weixin.qq.com/s/BOJ1lHsxbQxUZffXlgglEQ) + +## 股票系列 + +股票问题总结 + +* [动态规划:买卖股票的最佳时机](https://mp.weixin.qq.com/s/keWo5qYJY4zmHn3amfXdfQ) +* [动态规划:本周我们都讲了这些(系列六)](https://mp.weixin.qq.com/s/GVu-6eF0iNkpVDKRXTPOTA) +* [动态规划:买卖股票的最佳时机II](https://mp.weixin.qq.com/s/d4TRWFuhaY83HPa6t5ZL-w) +* [动态规划:买卖股票的最佳时机III](https://mp.weixin.qq.com/s/Sbs157mlVDtAR0gbLpdKzg) +* [动态规划:买卖股票的最佳时机IV](https://mp.weixin.qq.com/s/jtxZJWAo2y5sUsW647Z5cw) +* [动态规划:最佳买卖股票时机含冷冻期](https://mp.weixin.qq.com/s/TczJGFAPnkjH9ET8kwH1OA) +* [动态规划:本周我们都讲了这些(系列七)](https://mp.weixin.qq.com/s/vdzDlrEvhXWRzblTnOnzKg) +* [动态规划:买卖股票的最佳时机含手续费](https://mp.weixin.qq.com/s/2Cd_uINjerZ25VHH0K2IBQ) +* [动态规划:股票系列总结篇](https://mp.weixin.qq.com/s/sC5XyEtDQWkonKnbCvZhDw) + +## 子序列系列 + + + +* [动态规划:最长递增子序列](https://mp.weixin.qq.com/s/f8nLO3JGfgriXep_gJQpqQ) +* [动态规划:最长连续递增序列](https://mp.weixin.qq.com/s/c0Nn0TtjkTISVdqRsyMmyA) +* [动态规划:最长重复子数组](https://mp.weixin.qq.com/s/U5WaWqBwdoxzQDotOdWqZg) +* [动态规划:最长公共子序列](https://mp.weixin.qq.com/s/Qq0q4HaE4TyasCTj2WGFOg) +* [动态规划:不相交的线](https://mp.weixin.qq.com/s/krfYzSYEO8jIoVfyHzR0rw) +* [动态规划:最大子序和](https://mp.weixin.qq.com/s/2Xtyi2L4r8sM-BcxgUKmcA) +* [动态规划:判断子序列](https://mp.weixin.qq.com/s/2pjT4B4fjfOx5iB6N6xyng) +* [动态规划:不同的子序列](https://mp.weixin.qq.com/s/1SULY2XVSROtk_hsoVLu8A) +* [动态规划:两个字符串的删除操作](https://mp.weixin.qq.com/s/a8BerpqSf76DCqkPDJrpYg) +* [动态规划:编辑距离](https://mp.weixin.qq.com/s/8aG71XjSgZG6kZbiAdkJnQ) +* [为了绝杀编辑距离,我做了三步铺垫,你都知道么?](https://mp.weixin.qq.com/s/kbs4kCUzg8gPFttF9H3Yyw) +* [动态规划:回文子串](https://mp.weixin.qq.com/s/2WetyP6IYQ6VotegepVpEw) +* [动态规划:最长回文子序列](https://mp.weixin.qq.com/s/jbd3p4QPm5Kh1s2smTzWag) + + +## 动规结束语 + +关于动规,还有 树形DP(打家劫舍系列里有一道),数位DP,区间DP ,概率型DP,博弈型DP,状态压缩dp等等等,这些我就不去做讲解了,面试中出现的概率非常低。 + +能把本篇中列举的题目都研究通透的话,你的动规水平就已经非常高了。 对付面试已经足够! + +这已经是全网对动规最深刻的讲解系列了。 + +**其实大家去网上搜一搜也可以发现,能把动态规划讲清楚的资料挺少的,因为动规确实很难!要给别人讲清楚更难!** + +《剑指offer》上 动规的题目很少,经典的算法书籍《算法4》 没有讲 动规,而《算法导论》讲的动规基本属于劝退级别的。 + +讲清楚一道题容易,讲清楚两道题也容易,但把整个动态规划的各个分支讲清楚,每道题目讲通透,并用一套方法论把整个动规贯彻始终就非常难了。 + +所以Carl花费的这么大精力,把自己对动规算法理解 一五一十的全部分享给了录友们,帮助大家少走弯路! + +**至于动态规划PDF,即将在公众号「代码随想录」全网首发!** + +最后感谢录友们的一路支持,Carl才有继续更下去的动力[玫瑰],[撒花] + + +----------------------- +* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) +* B站视频:[代码随想录](https://space.bilibili.com/525438321) +* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) +
+ diff --git a/problems/动态规划理论基础.md b/problems/动态规划理论基础.md index ae02e69a..250fa57d 100644 --- a/problems/动态规划理论基础.md +++ b/problems/动态规划理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 什么是动态规划 diff --git a/problems/双指针总结.md b/problems/双指针总结.md index 5ee1b4f6..13f2f174 100644 --- a/problems/双指针总结.md +++ b/problems/双指针总结.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 又是一波总结 diff --git a/problems/哈希表总结.md b/problems/哈希表总结.md index d10f934a..c3fbde2b 100644 --- a/problems/哈希表总结.md +++ b/problems/哈希表总结.md @@ -1,19 +1,18 @@

- + - +

-

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

+

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

> 哈希表总结篇如约而至 -哈希表系列也是早期讲解的时候没有写总结篇,所以选个周末给补上,毕竟「代码随想录」的系列怎么能没有总结篇呢[机智]。 # 哈希表理论基础 -在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)中,我们介绍了哈希表的基础理论知识,不同于枯燥的讲解,这里介绍了都是对刷题有帮助的理论知识点。 +在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中,我们介绍了哈希表的基础理论知识,不同于枯燥的讲解,这里介绍了都是对刷题有帮助的理论知识点。 **一般来说哈希表都是用来快速判断一个元素是否出现集合里**。 @@ -29,7 +28,7 @@ * set(集合) * map(映射) -在C++语言中,set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同,在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)中我给出了详细分析,这一知识点很重要! +在C++语言中,set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同,在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中我给出了详细分析,这一知识点很重要! 例如什么时候用std::set,什么时候用std::multiset,什么时候用std::unordered_set,都是很有考究的。 @@ -41,13 +40,13 @@ 一些应用场景就是为数组量身定做的。 -在[哈希表:有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的! +在[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的! 这道题目包含小写字母,那么使用数组来做哈希最合适不过。 -在[哈希表:赎金信](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组! +在[383.赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组! -本题和[哈希表:有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)很像,[哈希表:有效的字母异位词](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)是求 字符串a 和 字符串b 是否可以相互组成,在[哈希表:赎金信](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ)中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 +本题和[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)很像,[242.有效的字母异位词](https://mp.weixin.qq.com/s/ffS8jaVFNUWyfn_8T31IdA)是求 字符串a 和 字符串b 是否可以相互组成,在[383.赎金信](https://mp.weixin.qq.com/s/qAXqv--UERmiJNNpuphOUQ)中是求字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。 一些同学可能想,用数组干啥,都用map不就完事了。 @@ -56,7 +55,7 @@ ## set作为哈希表 -在[哈希表:两个数组的交集](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA)中我们给出了什么时候用数组就不行了,需要用set。 +在[349. 两个数组的交集](https://mp.weixin.qq.com/s/aMSA5zrp3jJcLjuSB0Es2Q)中我们给出了什么时候用数组就不行了,需要用set。 这道题目没有限制数值的大小,就无法使用数组来做哈希表了。 @@ -67,7 +66,7 @@ 所以此时一样的做映射的话,就可以使用set了。 -关于set,C++ 给提供了如下三种可用的数据结构:(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)) +关于set,C++ 给提供了如下三种可用的数据结构:(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)) * std::set * std::multiset @@ -75,12 +74,12 @@ std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希, 使用unordered_set 读写效率是最高的,本题并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。 -在[哈希表:快乐数](https://mp.weixin.qq.com/s/G4Q2Zfpfe706gLK7HpZHpA)中,我们再次使用了unordered_set来判断一个数是否重复出现过。 +在[202.快乐数](https://mp.weixin.qq.com/s/n5q0ujxxrjQS3xuh3dgqBQ)中,我们再次使用了unordered_set来判断一个数是否重复出现过。 ## map作为哈希表 -在[哈希表:两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)中map正式登场。 +在[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)中map正式登场。 来说一说:使用数组和set来做哈希法的局限。 @@ -89,7 +88,7 @@ std::set和std::multiset底层实现都是红黑树,std::unordered_set的底 map是一种``的结构,本题可以用key保存数值,用value在保存数值所在的下表。所以使用map最为合适。 -C++提供如下三种map::(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)) +C++提供如下三种map::(详情请看[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)) * std::map * std::multimap @@ -97,21 +96,21 @@ C++提供如下三种map::(详情请看[关于哈希表,你该了解这 std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层实现是红黑树。 -同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解),[哈希表:两数之和](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ)中并不需要key有序,选择std::unordered_map 效率更高! +同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解),[1.两数之和](https://mp.weixin.qq.com/s/vaMsLnH-f7_9nEK4Cuu3KQ)中并不需要key有序,选择std::unordered_map 效率更高! -在[哈希表:四数相加II](https://mp.weixin.qq.com/s/Ue8pKKU5hw_m-jPgwlHcbA)中我们提到了其实需要哈希的地方都能找到map的身影。 +在[454.四数相加](https://mp.weixin.qq.com/s/12g_w6RzHuEpFts1pT6BWw)中我们提到了其实需要哈希的地方都能找到map的身影。 -本题咋眼一看好像和[18. 四数之](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A),[15.三数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)差不多,其实差很多! +本题咋眼一看好像和[18. 四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q),[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)差不多,其实差很多! -**关键差别是本题为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而[18. 四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g),[15.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)是一个数组(集合)里找到和为0的组合,可就难很多了!** +**关键差别是本题为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而[18. 四数之和](https://mp.weixin.qq.com/s/SBU3THi1Kv6Sar7htqCB2Q),[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)是一个数组(集合)里找到和为0的组合,可就难很多了!** 用哈希法解决了两数之和,很多同学会感觉用哈希法也可以解决三数之和,四数之和。 其实是可以解决,但是非常麻烦,需要去重导致代码效率很低。 -在[哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。 +在[15.三数之和](https://mp.weixin.qq.com/s/QfTNEByq1YlNSXRKEumwHg)中我给出了哈希法和双指针两个解法,大家就可以体会到,使用哈希法还是比较麻烦的。 -所以[18. 四数之](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A),[15.三数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)都推荐使用双指针法! +所以18. 四数之和,15.三数之和都推荐使用双指针法! # 总结 @@ -127,19 +126,6 @@ std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层 -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - - ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/哈希表理论基础.md b/problems/哈希表理论基础.md index ba097239..f78dc241 100644 --- a/problems/哈希表理论基础.md +++ b/problems/哈希表理论基础.md @@ -1,17 +1,18 @@

- + - +

-

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

+

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

-# 哈希表 + +## 哈希表 首先什么是 哈希表,哈希表(英文名字为Hash table,国内也有一些算法书籍翻译为散列表,大家看到这两个名称知道都是指hash table就可以了)。 -> 哈希表是根据关键码的值而直接进行访问的数据结构。 +> 哈希表是根据关键码的值而直接进行访问的数据结构。 这么这官方的解释可能有点懵,其实直白来讲其实数组就是一张哈希表。 @@ -19,17 +20,17 @@ ![哈希表1](https://img-blog.csdnimg.cn/20210104234805168.png) -那么哈希表能解决什么问题呢,**一般哈希表都是用来快速判断一个元素是否出现集合里。** +那么哈希表能解决什么问题呢,**一般哈希表都是用来快速判断一个元素是否出现集合里。** -例如要查询一个名字是否在这所学校里。 +例如要查询一个名字是否在这所学校里。 -要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1) 就可以做到。 +要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1) 就可以做到。 我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。 将学生姓名映射到哈希表上就涉及到了**hash function ,也就是哈希函数**。 -# 哈希函数 +## 哈希函数 哈希函数,把学生的姓名直接映射为哈希表上的索引,然后就可以通过查询索引下表快速知道这位同学是否在这所学校里了。 @@ -37,7 +38,7 @@ ![哈希表2](https://img-blog.csdnimg.cn/2021010423484818.png) -如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢? +如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢? 此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。 @@ -47,7 +48,7 @@ 接下来**哈希碰撞**登场 -# 哈希碰撞 +### 哈希碰撞 如图所示,小李和小王都映射到了索引下表 1的位置,**这一现象叫做哈希碰撞**。 @@ -55,7 +56,7 @@ 一般哈希碰撞有两种解决方法, 拉链法和线性探测法。 -## 拉链法 +### 拉链法 刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了 @@ -65,7 +66,7 @@ 其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。 -## 线性探测法 +### 线性探测法 使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。 @@ -75,13 +76,13 @@ 其实关于哈希碰撞还有非常多的细节,感兴趣的同学可以再好好研究一下,这里我就不再赘述了。 -# 常见的三种哈希结构 +## 常见的三种哈希结构 当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。 * 数组 * set (集合) -* map(映射) +* map(映射) 这里数组就没啥可说的了,我们来看一下set。 @@ -117,7 +118,7 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底 ![哈希表6](https://img-blog.csdnimg.cn/20210104235134572.png) -# 总结 +## 总结 总结一下,**当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法**。 @@ -125,23 +126,6 @@ std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底 如果在做面试题目的时候遇到需要判断一个元素是否出现过的场景也应该第一时间想到哈希法! -预告下篇讲解一波哈希表面试题的解题套路,我们下期见! - -都看到这了,还有sei!sei没读懂单独找我! - - - -## 其他语言版本 - - -Java: - - -Python: - - -Go: - diff --git a/problems/回溯总结.md b/problems/回溯总结.md index db9c9c61..793df516 100644 --- a/problems/回溯总结.md +++ b/problems/回溯总结.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 20张树形结构图、14道精选回溯题目,21篇回溯法精讲文章,由浅入深,一气呵成,这是全网最强回溯算法总结! diff --git a/problems/回溯算法去重问题的另一种写法.md b/problems/回溯算法去重问题的另一种写法.md index be69b068..19a4ae3b 100644 --- a/problems/回溯算法去重问题的另一种写法.md +++ b/problems/回溯算法去重问题的另一种写法.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 回溯算法去重问题的另一种写法 diff --git a/problems/回溯算法理论基础.md b/problems/回溯算法理论基础.md index 8bfd101c..35e9db0f 100644 --- a/problems/回溯算法理论基础.md +++ b/problems/回溯算法理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 可以配合我的B站视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/) 一起学习! diff --git a/problems/字符串总结.md b/problems/字符串总结.md index 11e29c42..21fdc9bc 100644 --- a/problems/字符串总结.md +++ b/problems/字符串总结.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 字符串:总结篇 diff --git a/problems/数组总结篇.md b/problems/数组总结篇.md index d8daa866..2c679493 100644 --- a/problems/数组总结篇.md +++ b/problems/数组总结篇.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

diff --git a/problems/数组理论基础.md b/problems/数组理论基础.md index a2c86ac4..b2837375 100644 --- a/problems/数组理论基础.md +++ b/problems/数组理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

diff --git a/problems/栈与队列总结.md b/problems/栈与队列总结.md index 754e84aa..b3bb5c47 100644 --- a/problems/栈与队列总结.md +++ b/problems/栈与队列总结.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 栈与队列的理论基础 diff --git a/problems/栈与队列理论基础.md b/problems/栈与队列理论基础.md index 5230fa53..04f99981 100644 --- a/problems/栈与队列理论基础.md +++ b/problems/栈与队列理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

> 来看看栈和队列不为人知的一面 diff --git a/problems/根据身高重建队列(vector原理讲解).md b/problems/根据身高重建队列(vector原理讲解).md index e7dac2ad..c548f6d8 100644 --- a/problems/根据身高重建队列(vector原理讲解).md +++ b/problems/根据身高重建队列(vector原理讲解).md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 贪心算法:根据身高重建队列(续集) 在讲解[贪心算法:根据身高重建队列](https://mp.weixin.qq.com/s/-2TgZVdOwS-DvtbjjDEbfw)中,我们提到了使用vector(C++中的动态数组)来进行insert操作是费时的。 diff --git a/problems/算法模板.md b/problems/算法模板.md index 133c798d..888ae773 100644 --- a/problems/算法模板.md +++ b/problems/算法模板.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

## 二分查找法 @@ -82,13 +82,13 @@ void traversal(TreeNode* cur, vector& vec) { traversal(cur->right, vec); // 右 } ``` -前序遍历(中左右) +后序遍历(左右中) ``` void traversal(TreeNode* cur, vector& vec) { if (cur == NULL) return; - vec.push_back(cur->val); // 中 ,同时也是处理节点逻辑的地方 traversal(cur->left, vec); // 左 traversal(cur->right, vec); // 右 + vec.push_back(cur->val); // 中 ,同时也是处理节点逻辑的地方 } ``` diff --git a/problems/背包总结篇.md b/problems/背包总结篇.md index 16b84ba5..0dd407ac 100644 --- a/problems/背包总结篇.md +++ b/problems/背包总结篇.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 听说背包问题很难? 这篇总结篇来拯救你了 年前我们已经把背包问题都讲完了,那么现在我们要对背包问题进行总结一番。 @@ -95,18 +95,6 @@ -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - ----------------------- diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index 8055d481..1269d9c1 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:关于01背包问题,你该了解这些! 这周我们正式开始讲解背包问题! @@ -102,30 +102,13 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 代码如下: - -``` -// 倒叙遍历 -for (int j = bagWeight; j >= weight[0]; j--) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i为0时候的情况 -} -``` - -**大家应该发现,这个初始化为什么是倒叙的遍历的?正序遍历就不行么?** - -正序遍历还真就不行,dp[0][j]表示容量为j的背包存放物品0时候的最大价值,物品0的价值就是15,因为题目中说了**每个物品只有一个!**所以dp[0][j]如果不是初始值的话,就应该都是物品0的价值,也就是15。 - -但如果一旦正序遍历了,那么物品0就会被重复加入多次! 例如代码如下: ``` // 正序遍历 for (int j = weight[0]; j <= bagWeight; j++) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; + dp[0][j] = value[0]; } ``` -例如dp[0][1] 是15,到了dp[0][2] = dp[0][2 - 1] + 15; 也就是dp[0][2] = 30 了,那么就是物品0被重复放入了。 - -**所以一定要倒叙遍历,保证物品0只被放入一次!这一点对01背包很重要,后面在讲解滚动数组的时候,还会用到倒叙遍历来保证物品使用一次!** - 此时dp数组初始化情况如图所示: @@ -138,16 +121,23 @@ dp[i][j]在推导的时候一定是取价值最大的数,如果题目给的价 如果题目给的价值有负数,那么非0下标就要初始化为负无穷了。例如:一个物品的价值是-2,但对应的位置依然初始化为0,那么取最大值的时候,就会取0而不是-2了,所以要初始化为负无穷。 +而背包问题的物品价值都是正整数,所以初始化为0,就可以了。 + **这样才能让dp数组在递归公式的过程中取最大的价值,而不是被初始值覆盖了**。 +如图: + +![动态规划-背包问题10](https://code-thinking.cdn.bcebos.com/pics/动态规划-背包问题10.jpg) + 最后初始化代码如下: ``` // 初始化 dp vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); -for (int j = bagWeight; j >= weight[0]; j--) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; +for (int j = weight[0]; j <= bagWeight; j++) { + dp[0][j] = value[0]; } + ``` **费了这么大的功夫,才把如何初始化讲清楚,相信不少同学平时初始化dp数组是凭感觉来的,但有时候感觉是不靠谱的**。 @@ -239,8 +229,8 @@ void test_2_wei_bag_problem1() { vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); // 初始化 - for (int j = bagWeight; j >= weight[0]; j--) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; + for (int j = weight[0]; j <= bagWeight; j++) { + dp[0][j] = value[0]; } // weight数组的大小 就是物品个数 @@ -262,26 +252,6 @@ int main() { ``` -以上遍历的过程也可以这么写: - -``` -// 遍历过程 -for(int i = 1; i < weight.size(); i++) { // 遍历物品 - for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - if (j - weight[i] >= 0) { - dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); - } - } -} -``` - -这么写打印出来的dp数据这就是这样: - -![动态规划-背包问题8](https://img-blog.csdnimg.cn/2021011010344372.png) - -空出来的0其实是用不上的,版本一 能把完整的dp数组打印出来,出来我用版本一来讲解。 - - ## 总结 讲了这么多才刚刚把二维dp的01背包讲完,**这里大家其实可以发现最简单的是推导公式了,推导公式估计看一遍就记下来了,但难就难在如何初始化和遍历顺序上**。 @@ -295,17 +265,120 @@ for(int i = 1; i < weight.size(); i++) { // 遍历物品 ## 其他语言版本 - Java: +```java + public static void main(String[] args) { + int[] weight = {1, 3, 4}; + int[] value = {15, 20, 30}; + int bagSize = 4; + testWeightBagProblem(weight, value, bagSize); + } + + public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){ + int wLen = weight.length, value0 = 0; + //定义dp数组:dp[i][j]表示背包容量为j时,前i个物品能获得的最大价值 + int[][] dp = new int[wLen + 1][bagSize + 1]; + //初始化:背包容量为0时,能获得的价值都为0 + for (int i = 0; i <= wLen; i++){ + dp[i][0] = value0; + } + //遍历顺序:先遍历物品,再遍历背包容量 + for (int i = 1; i <= wLen; i++){ + for (int j = 1; j <= bagSize; j++){ + if (j < weight[i - 1]){ + dp[i][j] = dp[i - 1][j]; + }else{ + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i - 1]] + value[i - 1]); + } + } + } + //打印dp数组 + for (int i = 0; i <= wLen; i++){ + for (int j = 0; j <= bagSize; j++){ + System.out.print(dp[i][j] + " "); + } + System.out.print("\n"); + } + } +``` + + + Python: +```python +def test_2_wei_bag_problem1(bag_size, weight, value) -> int: + rows, cols = len(weight), bag_size + 1 + dp = [[0 for _ in range(cols)] for _ in range(rows)] + res = 0 + + # 初始化dp数组. + for i in range(rows): + dp[i][0] = 0 + first_item_weight, first_item_value = weight[0], value[0] + for j in range(1, cols): + if first_item_weight <= j: + dp[0][j] = first_item_value + + # 更新dp数组: 先遍历物品, 再遍历背包. + for i in range(1, len(weight)): + cur_weight, cur_val = weight[i], value[i] + for j in range(1, cols): + if cur_weight > j: # 说明背包装不下当前物品. + dp[i][j] = dp[i - 1][j] # 所以不装当前物品. + else: + # 定义dp数组: dp[i][j] 前i个物品里,放进容量为j的背包,价值总和最大是多少。 + dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cur_weight]+ cur_val) + if dp[i][j] > res: + res = dp[i][j] + + print(dp) + + +if __name__ == "__main__": + bag_size = 4 + weight = [1, 3, 4] + value = [15, 20, 30] + test_2_wei_bag_problem1(bag_size, weight, value) +``` Go: +```go +func test_2_wei_bag_problem1(weight, value []int, bagWeight int) int { + // 定义dp数组 + dp := make([][]int, len(weight)) + for i, _ := range dp { + dp[i] = make([]int, bagWeight+1) + } + // 初始化 + for j := bagWeight; j >= weight[0]; j-- { + dp[0][j] = dp[0][j-weight[0]] + value[0] + } + // 递推公式 + for i := 1; i < len(weight); i++ { + //正序,也可以倒序 + for j := weight[i];j<= bagWeight ; j++ { + dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]) + } + } + return dp[len(weight)-1][bagWeight] +} +func max(a,b int) int { + if a > b { + return a + } + return b +} - +func main() { + weight := []int{1,3,4} + value := []int{15,20,30} + test_2_wei_bag_problem1(weight,value,4) +} +``` ----------------------- * 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index f5c747c7..e85d31b4 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:关于01背包问题,你该了解这些!(滚动数组) 昨天[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中是用二维dp数组来讲解01背包。 @@ -55,7 +55,7 @@ dp[j]为 容量为j的背包所背的最大价值,那么如何推导dp[j]呢? -dp[j]可以通过dp[j - weight[j]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。 +dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。 dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j]) @@ -104,7 +104,7 @@ for(int i = 0; i < weight.size(); i++) { // 遍历物品 为什么呢? -**倒叙遍历是为了保证物品i只被放入一次!**,在[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中讲解二维dp数组初始化dp[0][j]时候已经讲解到过一次。 +**倒叙遍历是为了保证物品i只被放入一次!**。但如果一旦正序遍历了,那么物品0就会被重复加入多次! 举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15 @@ -152,7 +152,7 @@ dp[1] = dp[1 - weight[0]] + value[0] = 15 ## 一维dp01背包完整C++测试代码 -``` +```C++ void test_1_wei_bag_problem() { vector weight = {1, 3, 4}; vector value = {15, 20, 30}; @@ -211,15 +211,70 @@ int main() { ## 其他语言版本 - Java: +```java + public static void main(String[] args) { + int[] weight = {1, 3, 4}; + int[] value = {15, 20, 30}; + int bagWight = 4; + testWeightBagProblem(weight, value, bagWight); + } + + public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){ + int wLen = weight.length; + //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值 + int[] dp = new int[bagWeight + 1]; + //遍历顺序:先遍历物品,再遍历背包容量 + for (int i = 0; i < wLen; i++){ + for (int j = bagWeight; j >= weight[i]; j--){ + dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); + } + } + //打印dp数组 + for (int j = 0; j <= bagWeight; j++){ + System.out.print(dp[j] + " "); + } + } +``` + + + Python: Go: +```go +func test_1_wei_bag_problem(weight, value []int, bagWeight int) int { + // 定义 and 初始化 + dp := make([]int,bagWeight+1) + // 递推顺序 + for i := 0 ;i < len(weight) ; i++ { + // 这里必须倒序,区别二维,因为二维dp保存了i的状态 + for j:= bagWeight; j >= weight[i] ; j-- { + // 递推公式 + dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + } + } + //fmt.Println(dp) + return dp[bagWeight] +} +func max(a,b int) int { + if a > b { + return a + } + return b +} + + +func main() { + weight := []int{1,3,4} + value := []int{15,20,30} + test_1_wei_bag_problem(weight,value,4) +} +``` diff --git a/problems/背包理论基础01背包-一维DP.md b/problems/背包理论基础01背包-一维DP.md deleted file mode 100644 index 48be4182..00000000 --- a/problems/背包理论基础01背包-一维DP.md +++ /dev/null @@ -1,231 +0,0 @@ -

- - - - -

-

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

- -# 动态规划:关于01背包问题,你该了解这些!(滚动数组) - -昨天[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中是用二维dp数组来讲解01背包。 - -今天我们就来说一说滚动数组,其实在前面的题目中我们已经用到过滚动数组了,就是把二维dp降为一维dp,一些录友当时还表示比较困惑。 - -那么我们通过01背包,来彻底讲一讲滚动数组! - -接下来还是用如下这个例子来进行讲解 - -背包最大重量为4。 - -物品为: - -| | 重量 | 价值 | -| --- | --- | --- | -| 物品0 | 1 | 15 | -| 物品1 | 3 | 20 | -| 物品2 | 4 | 30 | - -问背包能背的物品最大价值是多少? - -## 一维dp数组(滚动数组) - -对于背包问题其实状态都是可以压缩的。 - -在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); - -**其实可以发现如果把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][j]里的i和j表达的是什么了,i是物品,j是背包容量。 - -**dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少**。 - -一定要时刻记住这里i和j的含义,要不然很容易看懵了。 - -动规五部曲分析如下: - -1. 确定dp数组的定义 - -在一维dp数组中,dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。 - -2. 一维dp数组的递推公式 - -dp[j]为 容量为j的背包所背的最大价值,那么如何推导dp[j]呢? - -dp[j]可以通过dp[j - weight[j]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。 - -dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j]) - -此时dp[j]有两个选择,一个是取自己dp[j],一个是取dp[j - weight[i]] + value[i],指定是取最大的,毕竟是求最大价值, - -所以递归公式为: - -``` -dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); -``` - -可以看出相对于二维dp数组的写法,就是把dp[i][j]中i的维度去掉了。 - -3. 一维dp数组如何初始化 - -**关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱**。 - -dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j],那么dp[0]就应该是0,因为背包容量为0所背的物品的最大价值就是0。 - -那么dp数组除了下标0的位置,初始为0,其他下标应该初始化多少呢? - -看一下递归公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); - -dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。 - -**这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了**。 - -那么我假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了。 - -4. 一维dp数组遍历顺序 - -代码如下: - -``` -for(int i = 0; i < weight.size(); i++) { // 遍历物品 - for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); - - } -} -``` - -**这里大家发现和二维dp的写法中,遍历背包的顺序是不一样的!** - -二维dp遍历的时候,背包容量是从小到大,而一维dp遍历的时候,背包是从大到小。 - -为什么呢? - -**倒叙遍历是为了保证物品i只被放入一次!**,在[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)中讲解二维dp数组初始化dp[0][j]时候已经讲解到过一次。 - -举一个例子:物品0的重量weight[0] = 1,价值value[0] = 15 - -如果正序遍历 - -dp[1] = dp[1 - weight[0]] + value[0] = 15 - -dp[2] = dp[2 - weight[0]] + value[0] = 30 - -此时dp[2]就已经是30了,意味着物品0,被放入了两次,所以不能正序遍历。 - -为什么倒叙遍历,就可以保证物品只放入一次呢? - -倒叙就是先算dp[2] - -dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp数组已经都初始化为0) - -dp[1] = dp[1 - weight[0]] + value[0] = 15 - -所以从后往前循环,每次取得状态不会和之前取得状态重合,这样每种物品就只取一次了。 - -**那么问题又来了,为什么二维dp数组历的时候不用倒叙呢?** - -因为对于二维dp,dp[i][j]都是通过上一层即dp[i - 1][j]计算而来,本层的dp[i][j]并不会被覆盖! - -(如何这里读不懂,大家就要动手试一试了,空想还是不靠谱的,实践出真知!) - -**再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?** - -不可以! - -因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。 - -(这里如果读不懂,就在回想一下dp[j]的定义,或者就把两个for循环顺序颠倒一下试试!) - -**所以一维dp数组的背包在遍历顺序上和二维其实是有很大差异的!**,这一点大家一定要注意。 - -5. 举例推导dp数组 - -一维dp,分别用物品0,物品1,物品2 来遍历背包,最终得到结果如下: - -![动态规划-背包问题9](https://img-blog.csdnimg.cn/20210110103614769.png) - - - -## 一维dp01背包完整C++测试代码 - -``` -void test_1_wei_bag_problem() { - vector weight = {1, 3, 4}; - vector value = {15, 20, 30}; - int bagWeight = 4; - - // 初始化 - vector dp(bagWeight + 1, 0); - for(int i = 0; i < weight.size(); i++) { // 遍历物品 - for(int j = bagWeight; j >= weight[i]; j--) { // 遍历背包容量 - dp[j] = max(dp[j], dp[j - weight[i]] + value[i]); - } - } - cout << dp[bagWeight] << endl; -} - -int main() { - test_1_wei_bag_problem(); -} - -``` - -可以看出,一维dp 的01背包,要比二维简洁的多! 初始化 和 遍历顺序相对简单了。 - -**所以我倾向于使用一维dp数组的写法,比较直观简洁,而且空间复杂度还降了一个数量级!** - -**在后面背包问题的讲解中,我都直接使用一维dp数组来进行推导**。 - -## 总结 - -以上的讲解可以开发一道面试题目(毕竟力扣上没原题)。 - -就是本文中的题目,要求先实现一个纯二维的01背包,如果写出来了,然后再问为什么两个for循环的嵌套顺序这么写?反过来写行不行?再讲一讲初始化的逻辑。 - -然后要求实现一个一维数组的01背包,最后再问,一维数组的01背包,两个for循环的顺序反过来写行不行?为什么? - -注意以上问题都是在候选人把代码写出来的情况下才问的。 - -就是纯01背包的题目,都不用考01背包应用类的题目就可以看出候选人对算法的理解程度了。 - -**相信大家读完这篇文章,应该对以上问题都有了答案!** - -此时01背包理论基础就讲完了,我用了两篇文章把01背包的dp数组定义、递推公式、初始化、遍历顺序从二维数组到一维数组统统深度剖析了一遍,没有放过任何难点。 - -大家可以发现其实信息量还是挺大的。 - -如果把[动态规划:关于01背包问题,你该了解这些!](https://mp.weixin.qq.com/s/FwIiPPmR18_AJO5eiidT6w)和本篇的内容都理解了,后面我们在做01背包的题目,就会发现非常简单了。 - -不用再凭感觉或者记忆去写背包,而是有自己的思考,了解其本质,代码的方方面面都在自己的掌控之中。 - -即使代码没有通过,也会有自己的逻辑去debug,这样就思维清晰了。 - -接下来就要开始用这两天的理论基础去做力扣上的背包面试题目了,录友们握紧扶手,我们要上高速啦! - -就酱,学算法,认准「代码随想录」,值得你推荐给身边每一位朋友同学们。 - - -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - - - ------------------------ -* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站视频:[代码随想录](https://space.bilibili.com/525438321) -* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
diff --git a/problems/背包理论基础01背包-二维DP.md b/problems/背包理论基础01背包-二维DP.md deleted file mode 100644 index b504daa6..00000000 --- a/problems/背包理论基础01背包-二维DP.md +++ /dev/null @@ -1,318 +0,0 @@ -

- - - - -

-

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

- -# 背包问题理论基础 - - -这周我们正式开始讲解背包问题! - -背包问题的经典资料当然是:背包九讲。在公众号「代码随想录」后台回复:背包九讲,就可以获得背包九讲的PDF。 - -但说实话,背包九讲对于小白来说确实不太友好,看起来还是有点费劲的,而且都是伪代码理解起来也吃力。 - -对于面试的话,其实掌握01背包,和完全背包,就够用了,最多可以再来一个多重背包。 - -如果这几种背包,分不清,我这里画了一个图,如下: - -![416.分割等和子集1](https://img-blog.csdnimg.cn/20210117171307407.png) - -至于背包九讲其其他背包,面试几乎不会问,都是竞赛级别的了,leetcode上连多重背包的题目都没有,所以题库也告诉我们,01背包和完全背包就够用了。 - -而完全背包又是也是01背包稍作变化而来,即:完全背包的物品数量是无限的。 - -**所以背包问题的理论基础重中之重是01背包,一定要理解透!** - -leetcode上没有纯01背包的问题,都是01背包应用方面的题目,也就是需要转化为01背包问题。 - -**所以我先通过纯01背包问题,把01背包原理讲清楚,后续再讲解leetcode题目的时候,重点就是讲解如何转化为01背包问题了**。 - -之前可能有些录友已经可以熟练写出背包了,但只要把这个文章仔细看完,相信你会意外收获! - -## 01 背包 - -有N件物品和一个最多能被重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。**每件物品只能用一次**,求解将哪些物品装入背包里物品价值总和最大。 - -![动态规划-背包问题](https://img-blog.csdnimg.cn/20210117175428387.jpg) - -这是标准的背包问题,以至于很多同学看了这个自然就会想到背包,甚至都不知道暴力的解法应该怎么解了。 - -这样其实是没有从底向上去思考,而是习惯性想到了背包,那么暴力的解法应该是怎么样的呢? - -每一件物品其实只有两个状态,取或者不取,所以可以使用回溯法搜索出所有的情况,那么时间复杂度就是O(2^n),这里的n表示物品数量。 - -**所以暴力的解法是指数级别的时间复杂度。进而才需要动态规划的解法来进行优化!** - -在下面的讲解中,我举一个例子: - -背包最大重量为4。 - -物品为: - -| | 重量 | 价值 | -| --- | --- | --- | -| 物品0 | 1 | 15 | -| 物品1 | 3 | 20 | -| 物品2 | 4 | 30 | - -问背包能背的物品最大价值是多少? - -以下讲解和图示中出现的数字都是以这个例子为例。 - -## 二维dp数组01背包 - -依然动规五部曲分析一波。 - -1. 确定dp数组以及下标的含义 - -对于背包问题,有一种写法, 是使用二维数组,即**dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少**。 - -只看这个二维数组的定义,大家一定会有点懵,看下面这个图: - -![动态规划-背包问题1](https://img-blog.csdnimg.cn/20210110103003361.png) - -**要时刻记着这个dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的**,如果哪里看懵了,就来回顾一下i代表什么,j又代表什么。 - -2. 确定递推公式 - -再回顾一下dp[i][j]的含义:从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。 - -那么可以有两个方向推出来dp[i][j], - -* 由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j] -* 由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]); - -3. dp数组如何初始化 - -**关于初始化,一定要和dp数组的定义吻合,否则到递推公式的时候就会越来越乱**。 - -首先从dp[i][j]的定义触发,如果背包容量j为0的话,即dp[i][0],无论是选取哪些物品,背包价值总和一定为0。如图: - -![动态规划-背包问题2](https://img-blog.csdnimg.cn/2021011010304192.png) - -在看其他情况。 - -状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。 - -dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 - -代码如下: - -``` -// 倒叙遍历 -for (int j = bagWeight; j >= weight[0]; j--) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; // 初始化i为0时候的情况 -} -``` - -**大家应该发现,这个初始化为什么是倒叙的遍历的?正序遍历就不行么?** - -正序遍历还真就不行,dp[0][j]表示容量为j的背包存放物品0时候的最大价值,物品0的价值就是15,因为题目中说了**每个物品只有一个!**所以dp[0][j]如果不是初始值的话,就应该都是物品0的价值,也就是15。 - -但如果一旦正序遍历了,那么物品0就会被重复加入多次! 例如代码如下: -``` -// 正序遍历 -for (int j = weight[0]; j <= bagWeight; j++) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; -} -``` - -例如dp[0][1] 是15,到了dp[0][2] = dp[0][2 - 1] + 15; 也就是dp[0][2] = 30 了,那么就是物品0被重复放入了。 - -**所以一定要倒叙遍历,保证物品0只被放入一次!这一点对01背包很重要,后面在讲解滚动数组的时候,还会用到倒叙遍历来保证物品使用一次!** - - -此时dp数组初始化情况如图所示: - -![动态规划-背包问题7](https://img-blog.csdnimg.cn/20210110103109140.png) - -dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢? - - -dp[i][j]在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,因为0就是最小的了,不会影响取最大价值的结果。 - -如果题目给的价值有负数,那么非0下标就要初始化为负无穷了。例如:一个物品的价值是-2,但对应的位置依然初始化为0,那么取最大值的时候,就会取0而不是-2了,所以要初始化为负无穷。 - -**这样才能让dp数组在递归公式的过程中取最大的价值,而不是被初始值覆盖了**。 - -最后初始化代码如下: - -``` -// 初始化 dp -vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); -for (int j = bagWeight; j >= weight[0]; j--) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; -} -``` - -**费了这么大的功夫,才把如何初始化讲清楚,相信不少同学平时初始化dp数组是凭感觉来的,但有时候感觉是不靠谱的**。 - -4. 确定遍历顺序 - - -在如下图中,可以看出,有两个遍历的维度:物品与背包重量 - -![动态规划-背包问题3](https://img-blog.csdnimg.cn/2021011010314055.png) - -那么问题来了,**先遍历 物品还是先遍历背包重量呢?** - -**其实都可以!! 但是先遍历物品更好理解**。 - -那么我先给出先遍历物品,然后遍历背包重量的代码。 - -``` -// 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数组里元素的变化 - else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); - - } -} -``` - -**先遍历背包,再遍历物品,也是可以的!(注意我这里使用的二维dp数组)** - -例如这样: - -``` -// weight数组的大小 就是物品个数 -for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - for(int i = 1; i < weight.size(); i++) { // 遍历物品 - 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]); - } -} -``` - -为什么也是可以的呢? - -**要理解递归的本质和递推的方向**。 - -dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 递归公式中可以看出dp[i][j]是靠dp[i-1][j]和dp[i - 1][j - weight[i]]推导出来的。 - -dp[i-1][j]和dp[i - 1][j - weight[i]] 都在dp[i][j]的左上角方向(包括正左和正上两个方向),那么先遍历物品,再遍历背包的过程如图所示: - -![动态规划-背包问题5](https://img-blog.csdnimg.cn/202101101032124.png) - -再来看看先遍历背包,再遍历物品呢,如图: - -![动态规划-背包问题6](https://img-blog.csdnimg.cn/20210110103244701.png) - -**大家可以看出,虽然两个for循环遍历的次序不同,但是dp[i][j]所需要的数据就是左上角,根本不影响dp[i][j]公式的推导!** - -但先遍历物品再遍历背包这个顺序更好理解。 - -**其实背包问题里,两个for循环的先后循序是非常有讲究的,理解遍历顺序其实比理解推导公式难多了**。 - -5. 举例推导dp数组 - -来看一下对应的dp数组的数值,如图: - -![动态规划-背包问题4](https://img-blog.csdnimg.cn/20210118163425129.jpg) - -最终结果就是dp[2][4]。 - -建议大家此时自己在纸上推导一遍,看看dp数组里每一个数值是不是这样的。 - -**做动态规划的题目,最好的过程就是自己在纸上举一个例子把对应的dp数组的数值推导一下,然后在动手写代码!** - -很多同学做dp题目,遇到各种问题,然后凭感觉东改改西改改,怎么改都不对,或者稀里糊涂就改过了。 - -主要就是自己没有动手推导一下dp数组的演变过程,如果推导明白了,代码写出来就算有问题,只要把dp数组打印出来,对比一下和自己推导的有什么差异,很快就可以发现问题了。 - - -## 完整C++测试代码 - -```C++ -void test_2_wei_bag_problem1() { - vector weight = {1, 3, 4}; - vector value = {15, 20, 30}; - int bagWeight = 4; - - // 二维数组 - vector> dp(weight.size() + 1, vector(bagWeight + 1, 0)); - - // 初始化 - for (int j = bagWeight; j >= weight[0]; j--) { - dp[0][j] = dp[0][j - weight[0]] + value[0]; - } - - // 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]; - else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); - - } - } - - cout << dp[weight.size() - 1][bagWeight] << endl; -} - -int main() { - test_2_wei_bag_problem1(); -} - -``` - - -以上遍历的过程也可以这么写: - -``` -// 遍历过程 -for(int i = 1; i < weight.size(); i++) { // 遍历物品 - for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - if (j - weight[i] >= 0) { - dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); - } - } -} -``` - -这么写打印出来的dp数据这就是这样: - -![动态规划-背包问题8](https://img-blog.csdnimg.cn/2021011010344372.png) - -空出来的0其实是用不上的,版本一 能把完整的dp数组打印出来,出来我用版本一来讲解。 - - -## 总结 - -讲了这么多才刚刚把二维dp的01背包讲完,**这里大家其实可以发现最简单的是推导公式了,推导公式估计看一遍就记下来了,但难就难在如何初始化和遍历顺序上**。 - -可能有的同学并没有注意到初始化 和 遍历顺序的重要性,我们后面做力扣上背包面试题目的时候,大家就会感受出来了。 - -下一篇 还是理论基础,我们再来讲一维dp数组实现的01背包(滚动数组),分析一下和二维有什么区别,在初始化和遍历顺序上又有什么差异,敬请期待! - -就酱,学算法,认准「代码随想录」,值得推荐给身边的朋友同学们,关注后都会发现相见恨晚! - - - - - -## 其他语言版本 - - -Java: - - -Python: - - -Go: - - - - ------------------------ -* 作者微信:[程序员Carl](https://mp.weixin.qq.com/s/b66DFkOp8OOxdZC_xLZxfw) -* B站视频:[代码随想录](https://space.bilibili.com/525438321) -* 知识星球:[代码随想录](https://mp.weixin.qq.com/s/QVF6upVMSbgvZy8lHZS3CQ) -
diff --git a/problems/背包问题理论基础多重背包.md b/problems/背包问题理论基础多重背包.md index 1f0ad4c0..26890a2b 100644 --- a/problems/背包问题理论基础多重背包.md +++ b/problems/背包问题理论基础多重背包.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:关于多重背包,你该了解这些! 之前我们已经体统的讲解了01背包和完全背包,如果没有看过的录友,建议先把如下三篇文章仔细阅读一波。 diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index b91c99fd..a5a708cf 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 动态规划:关于完全背包,你该了解这些! ## 完全背包 @@ -179,12 +179,99 @@ int main() { Java: - Python: +```python3 +# 先遍历物品,再遍历背包 +def test_complete_pack1(): + weight = [1, 3, 4] + value = [15, 20, 30] + bag_weight = 4 + + dp = [0]*(bag_weight + 1) + + for i in range(len(weight)): + for j in range(weight[i], bag_weight + 1): + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + + print(dp[bag_weight]) + +# 先遍历背包,再遍历物品 +def test_complete_pack2(): + weight = [1, 3, 4] + value = [15, 20, 30] + bag_weight = 4 + + dp = [0]*(bag_weight + 1) + + for j in range(bag_weight + 1): + for i in range(len(weight)): + if j >= weight[i]: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + + print(dp[bag_weight]) + + +if __name__ == '__main__': + test_complete_pack1() + test_complete_pack2() +``` + + + Go: +```go +// test_CompletePack1 先遍历物品, 在遍历背包 +func test_CompletePack1(weight, value []int, bagWeight int) int { + // 定义dp数组 和初始化 + dp := make([]int, bagWeight+1) + // 遍历顺序 + for i := 0; i < len(weight); i++ { + // 正序会多次添加 value[i] + for j := weight[i]; j <= bagWeight; j++ { + // 推导公式 + dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + // debug + //fmt.Println(dp) + } + } + return dp[bagWeight] +} + +// test_CompletePack2 先遍历背包, 在遍历物品 +func test_CompletePack2(weight, value []int, bagWeight int) int { + // 定义dp数组 和初始化 + dp := make([]int, bagWeight+1) + // 遍历顺序 + // j从0 开始 + for j := 0; j <= bagWeight; j++ { + for i := 0; i < len(weight); i++ { + if j >= weight[i] { + // 推导公式 + dp[j] = max(dp[j], dp[j-weight[i]]+value[i]) + } + // debug + //fmt.Println(dp) + } + } + return dp[bagWeight] +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func main() { + weight := []int{1, 3, 4} + price := []int{15, 20, 30} + fmt.Println(test_CompletePack1(weight, price, 4)) + fmt.Println(test_CompletePack2(weight, price, 4)) +} +``` diff --git a/problems/贪心算法总结篇.md b/problems/贪心算法总结篇.md index 01029d62..999797ad 100644 --- a/problems/贪心算法总结篇.md +++ b/problems/贪心算法总结篇.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

我刚刚开始讲解贪心系列的时候就说了,贪心系列并不打算严格的从简单到困难这么个顺序来讲解。 diff --git a/problems/贪心算法理论基础.md b/problems/贪心算法理论基础.md index 74efad83..5385aa60 100644 --- a/problems/贪心算法理论基础.md +++ b/problems/贪心算法理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 关于贪心算法,你该了解这些! diff --git a/problems/链表总结篇.md b/problems/链表总结篇.md index e709ec9b..6404dd6e 100644 --- a/problems/链表总结篇.md +++ b/problems/链表总结篇.md @@ -1,17 +1,16 @@

- + - +

-

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

+

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

-> 之前链表篇没有做总结,所以是时候总结一波 -# 链表的理论基础 +## 链表的理论基础 -在这篇文章[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ)中,介绍了如下几点: +在这篇文章[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw)中,介绍了如下几点: * 链表的种类主要为:单链表,双链表,循环链表 * 链表的存储方式:链表的节点在内存中是分散存储的,通过指针连在一起。 @@ -20,21 +19,21 @@ **可以说把链表基础的知识都概括了,但又不像教科书那样的繁琐**。 -# 链表经典题目 +## 链表经典题目 -## 虚拟头结点 +### 虚拟头结点 -在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)中,我们讲解了链表操作中一个非常总要的技巧:虚拟头节点。 +在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)中,我们讲解了链表操作中一个非常总要的技巧:虚拟头节点。 链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。 **每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题**。 -在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。 +在[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA)中,我给出了用虚拟头结点和没用虚拟头结点的代码,大家对比一下就会发现,使用虚拟头结点的好处。 -## 链表的基本操作 +### 链表的基本操作 -在[链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/Cf95Lc6brKL4g2j8YyF3Mg)中,我们通设计链表把链表常见的五个操作练习了一遍。 +在[链表:一道题目考察了常见的五个操作!](https://mp.weixin.qq.com/s/jnC_LAD0ZKCsj-FZc57F1g)中,我们通设计链表把链表常见的五个操作练习了一遍。 这是练习链表基础操作的非常好的一道题目,考察了: @@ -48,103 +47,49 @@ 这里我依然使用了虚拟头结点的技巧,大家复习的时候,可以去看一下代码。 -## 反转链表 +### 反转链表 -在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)中,讲解了如何反转链表。 +在[链表:听说过两天反转链表又写不出来了?](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,讲解了如何反转链表。 因为反转链表的代码相对简单,有的同学可能直接背下来了,但一写还是容易出问题。 反转链表是面试中高频题目,很考察面试者对链表操作的熟练程度。 -我在[文章](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg)中,给出了两种反转的方式,迭代法和递归法。 +我在[文章](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A)中,给出了两种反转的方式,迭代法和递归法。 建议大家先学透迭代法,然后再看递归法,因为递归法比较绕,如果迭代还写不明白,递归基本也写不明白了。 **可以先通过迭代法,彻底弄清楚链表反转的过程!** +### 删除倒数第N个节点 + +在[链表:删除链表倒数第N个节点,怎么删?](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg)中我们结合虚拟头结点 和 双指针法来移除链表倒数第N个节点。 + + +### 链表相交 + +[链表:链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw)使用双指针来找到两个链表的交点(引用完全相同,即:内存地址完全相同的交点) + ## 环形链表 -在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中,讲解了在链表如何找环,以及如何找环的入口位置。 +在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ)中,讲解了在链表如何找环,以及如何找环的入口位置。 -这道题目可以说是链表的比较难的题目了。 +这道题目可以说是链表的比较难的题目了。 但代码却十分简洁,主要在于一些数学证明。 -很多同学关注的问题是:为什么一定会相遇,快指针就不能跳过慢指针么? - -可以确定如下两点: - -* fast指针一定先进入环中,如果fast 指针和slow指针相遇的话,一定是在环中相遇,这是毋庸置疑的。 -* fast和slow都进入环里之后,fast相对于slow来说,fast是一个节点一个节点的靠近slow的,**注意是相对运动,所以fast一定可以和slow重合**。 - -如果fast是一次走三个节点,那么可能会跳过slow,因为相对于slow来说,fast是两个节点移动的。 - -确定有否有环比较容易,但是找到环的入口就不太容易了,需要点数学推理。 - -我在[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)中给出了详细的推理,兼顾易懂和简洁了。 - -这是一位录友在评论区有一个疑问,感觉这个问题很不错,但评论区根本说不清楚,我就趁着总结篇,补充一下这个证明。 - -在推理过程中,**为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y 呢?** - -了解这个问题一定要先把文章[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)看了,即文章中如下的地方: - - - - -首先slow进环的时候,fast一定是先进环来了。 - -如果slow进环入口,fast也在环入口,那么把这个环展开成直线,就是如下图的样子: - - - -可以看出如果slow 和 fast同时在环入口开始走,一定会在环入口3相遇,slow走了一圈,fast走了两圈。 - -重点来了,slow进环的时候,fast一定是在环的任意一个位置,如图: - - - -那么fast指针走到环入口3的时候,已经走了k + n 个节点,slow相应的应该走了(k + n) / 2 个节点。 - -因为k是小于n的(图中可以看出),所以(k + n) / 2 一定小于n。 - -**也就是说slow一定没有走到环入口3,而fast已经到环入口3了**。 - -这说明什么呢? - -**在slow开始走的那一环已经和fast相遇了**。 - -那有同学又说了,为什么fast不能跳过去呢? 在刚刚已经说过一次了,**fast相对于slow是一次移动一个节点,所以不可能跳过去**。 - -好了,这次把为什么第一次在环中相遇,slow的 步数 是 x+y 而不是 x + 若干环的长度 + y ,用数学推理了一下,算是对[链表:环找到了,那入口呢?](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA)的补充。 - -这次可以说把环形链表这道题目的各个细节,完完整整的证明了一遍,说这是全网最详细讲解不为过吧,哈哈。 - -# 总结 +## 总结 考察链表的操作其实就是考察指针的操作,是面试中的常见类型。 链表篇中开头介绍[链表理论知识](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA),然后分别通过经典题目介绍了如下知识点: -* [虚拟头结点的技巧](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA) -* [链表的增删改查](https://mp.weixin.qq.com/s/Cf95Lc6brKL4g2j8YyF3Mg) -* [反转一个链表](https://mp.weixin.qq.com/s/pnvVP-0ZM7epB8y3w_Njwg) -* [有否环形,以及环的入口](https://mp.weixin.qq.com/s/_QVP3IkRZWx9zIpQRgajzA) +1. [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/fDGMmLrW7ZHlzkzlf_dZkw) +2. [虚拟头结点的技巧](https://mp.weixin.qq.com/s/L5aanfALdLEwVWGvyXPDqA) +3. [链表的增删改查](https://mp.weixin.qq.com/s/jnC_LAD0ZKCsj-FZc57F1g) +4. [反转一个链表](https://mp.weixin.qq.com/s/ckEvIVGcNLfrz6OLOMoT0A) +5. [删除倒数第N个节点](https://mp.weixin.qq.com/s/gxu65X1343xW_sBrkTz0Eg) +6. [链表相交](https://mp.weixin.qq.com/s/BhfFfaGvt9Zs7UmH4YehZw) +7. [有否环形,以及环的入口](https://mp.weixin.qq.com/s/gt_VH3hQTqNxyWcl1ECSbQ) -虽然这几篇文章都是几个月前发的了,但在在文章留言区,可以看到很多录友都在从头打卡! - -如果希望从基础学起来的同学,也可以从头学起来,从头开始打卡,打卡的同时也总结自己的所学所思,一定进步飞快! - - - -## 其他语言版本 - - -Java: - - -Python: - - -Go: diff --git a/problems/链表理论基础.md b/problems/链表理论基础.md index 1dee1182..252247c7 100644 --- a/problems/链表理论基础.md +++ b/problems/链表理论基础.md @@ -1,10 +1,10 @@

- + - +

-

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

+

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

# 关于链表,你该了解这些! @@ -83,7 +83,7 @@ struct ListNode { 有同学说了,我不定义构造函数行不行,答案是可以的,C++默认生成一个构造函数。 -但是这个构造函数不会初始化任何成员变化,下面我来举两个例子: +但是这个构造函数不会初始化任何成员变量,下面我来举两个例子: 通过自己定义构造函数初始化节点: diff --git a/problems/面试题02.07.链表相交.md b/problems/面试题02.07.链表相交.md index 97045ed2..c6779427 100644 --- a/problems/面试题02.07.链表相交.md +++ b/problems/面试题02.07.链表相交.md @@ -1,11 +1,11 @@ -

- + - +

-

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

+

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

+ ## 面试题 02.07. 链表相交 @@ -32,11 +32,11 @@ 看如下两个链表,目前curA指向链表A的头结点,curB指向链表B的头结点: -![面试题02.07.链表相交_1](https://code-thinking.cdn.bcebos.com/pics/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4_1.png)v +![面试题02.07.链表相交_1](https://code-thinking.cdn.bcebos.com/pics/面试题02.07.链表相交_1.png) 我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图: -![面试题02.07.链表相交_2](https://code-thinking.cdn.bcebos.com/pics/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4_2.png) +![面试题02.07.链表相交_2](https://code-thinking.cdn.bcebos.com/pics/面试题02.07.链表相交_2.png) 此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到焦点。 @@ -92,13 +92,178 @@ public: Java: - +```Java +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode(int x) { + * val = x; + * next = null; + * } + * } + */ +public class Solution { + public ListNode getIntersectionNode(ListNode headA, ListNode headB) { + ListNode curA = headA; + ListNode curB = headB; + int lenA = 0, lenB = 0; + while (curA != null) { // 求链表A的长度 + lenA++; + curA = curA.next; + } + while (curB != null) { // 求链表B的长度 + lenB++; + curB = curB.next; + } + curA = headA; + curB = headB; + // 让curA为最长链表的头,lenA为其长度 + if (lenB > lenA) { + //1. swap (lenA, lenB); + int tmpLen = lenA; + lenA = lenB; + lenB = tmpLen; + //2. swap (curA, curB); + ListNode tmpNode = curA; + curA = curB; + curB = tmpNode; + } + // 求长度差 + int gap = lenA - lenB; + // 让curA和curB在同一起点上(末尾位置对齐) + while (gap-- > 0) { + curA = curA.next; + } + // 遍历curA 和 curB,遇到相同则直接返回 + while (curA != null) { + if (curA == curB) { + return curA; + } + curA = curA.next; + curB = curB.next; + } + return null; + } + +} +``` Python: +```python +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None +class Solution: + def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode: + lengthA,lengthB = 0,0 + curA,curB = headA,headB + while(curA!=None): #求链表A的长度 + curA = curA.next + lengthA +=1 + + while(curB!=None): #求链表B的长度 + curB = curB.next + lengthB +=1 + + curA, curB = headA, headB + + if lengthB>lengthA: #让curA为最长链表的头,lenA为其长度 + lengthA, lengthB = lengthB, lengthA + curA, curB = curB, curA + + gap = lengthA - lengthB #求长度差 + while(gap!=0): + curA = curA.next #让curA和curB在同一起点上 + gap -= 1 + + while(curA!=None): + if curA == curB: + return curA + else: + curA = curA.next + curB = curB.next + return None +``` Go: +```go +func getIntersectionNode(headA, headB *ListNode) *ListNode { + curA := headA + curB := headB + lenA, lenB := 0, 0 + // 求A,B的长度 + for curA != nil { + curA = curA.Next + lenA++ + } + for curB != nil { + curB = curB.Next + lenB++ + } + var step int + var fast, slow *ListNode + // 请求长度差,并且让更长的链表先走相差的长度 + if lenA > lenB { + step = lenA - lenB + fast, slow = headA, headB + } else { + step = lenB - lenA + fast, slow = headB, headA + } + for i:=0; i < step; i++ { + fast = fast.Next + } + // 遍历两个链表遇到相同则跳出遍历 + for fast != slow { + fast = fast.Next + slow = slow.Next + } + return fast +} +``` + +javaScript: + +```js +/** + * @param {ListNode} headA + * @param {ListNode} headB + * @return {ListNode} + */ +var getListLen = function(head) { + let len = 0, cur = head; + while(cur) { + len++; + cur = cur.next; + } + return len; +} +var getIntersectionNode = function(headA, headB) { + let curA = headA,curB = headB, + lenA = getListLen(headA), + lenB = getListLen(headB); + if(lenA < lenB) { + [curA, curB] = [curB, curA]; + [lenA, lenB] = [lenB, lenA]; + } + let i = lenA - lenB; + while(i-- > 0) { + curA = curA.next + } + while(curA && curA !== curB) { + curA = curA.next; + curB = curB.next; + } + return curA; +}; +``` +