diff --git a/README.md b/README.md index 38418322..620942dc 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,7 @@ 33. [二叉树:构造一棵搜索树](./problems/0108.将有序数组转换为二叉搜索树.md) 34. [二叉树:搜索树转成累加树](./problems/0538.把二叉搜索树转换为累加树.md) 35. [二叉树:总结篇!(需要掌握的二叉树技能都在这里了)](./problems/二叉树总结篇.md) - + ## 回溯算法 题目分类大纲如下: @@ -523,6 +523,10 @@ [点此这里](https://github.com/youngyangyang04/leetcode-master/graphs/contributors)查看LeetCode-Master的所有贡献者。感谢他们补充了LeetCode-Master的其他语言版本,让更多的读者收益于此项目。 +# Star 趋势 + +[![Star History Chart](https://api.star-history.com/svg?repos=youngyangyang04/leetcode-master&type=Date)](https://star-history.com/#youngyangyang04/leetcode-master&Date) + # 关于作者 大家好,我是程序员Carl,哈工大师兄,《代码随想录》作者,先后在腾讯和百度从事后端技术研发,CSDN博客专家。对算法和C++后端技术有一定的见解,利用工作之余重新刷leetcode。 diff --git a/problems/0001.两数之和.md b/problems/0001.两数之和.md index 459a66ad..a15e1349 100644 --- a/problems/0001.两数之和.md +++ b/problems/0001.两数之和.md @@ -7,7 +7,7 @@ ## 1. 两数之和 -[力扣题目链接](https://leetcode-cn.com/problems/two-sum/) +[力扣题目链接](https://leetcode.cn/problems/two-sum/) 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 @@ -24,7 +24,9 @@ ## 思路 -很明显暴力的解法是两层for循环查找,时间复杂度是$O(n^2)$。 +建议看一下我录的这期视频:[梦开始的地方,Leetcode:1.两数之和](https://www.bilibili.com/video/BV1aT41177mK),结合本题解来学习,事半功倍。 + +很明显暴力的解法是两层for循环查找,时间复杂度是O(n^2)。 建议大家做这道题目之前,先做一下这两道 * [242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html) @@ -32,7 +34,16 @@ [242. 有效的字母异位词](https://www.programmercarl.com/0242.有效的字母异位词.html) 这道题目是用数组作为哈希表来解决哈希问题,[349. 两个数组的交集](https://www.programmercarl.com/0349.两个数组的交集.html)这道题目是通过set作为哈希表来解决哈希问题。 -本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限。 + +首先我在强调一下 **什么时候使用哈希法**,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。 + +本题呢,我就需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。 + +那么我们就应该想到使用哈希法了。 + +因为本地,我们不仅要知道元素有没有遍历过,还有知道这个元素对应的下标,**需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适**。 + +再来看一下使用数组和set来做哈希法的局限。 * 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。 * set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。 @@ -43,20 +54,38 @@ C++中map,有三种类型: |映射 |底层实现 | 是否有序 |数值是否可以重复 | 能否更改数值|查询效率 |增删效率| |---|---| --- |---| --- | --- | ---| -|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | $O(\log n)$|$O(\log n)$ | -|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|$O(\log n)$ |$O(\log n)$ | -|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |$O(1)$ | $O(1)$| +|std::map |红黑树 |key有序 |key不可重复 |key不可修改 | O(log n)|O(log n) | +|std::multimap | 红黑树|key有序 | key可重复 | key不可修改|O(log n) |O(log n) | +|std::unordered_map |哈希表 | key无序 |key不可重复 |key不可修改 |O(1) | O(1)| std::unordered_map 底层实现为哈希表,std::map 和std::multimap 的底层实现是红黑树。 同理,std::map 和std::multimap 的key也是有序的(这个问题也经常作为面试题,考察对语言容器底层的理解)。 更多哈希表的理论知识请看[关于哈希表,你该了解这些!](https://www.programmercarl.com/哈希表理论基础.html)。 -**这道题目中并不需要key有序,选择std::unordered_map 效率更高!** +**这道题目中并不需要key有序,选择std::unordered_map 效率更高!** 使用其他语言的录友注意了解一下自己所用语言的数据结构就行。 -解题思路动画如下: +接下来需要明确两点: -![](https://code-thinking.cdn.bcebos.com/gifs/1.两数之和.gif) +* **map用来做什么** +* **map中key和value分别表示什么** +map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下表,这样才能找到与当前元素相匹配的(也就是相加等于target) + +接下来是map中key和value分别表示什么。 + +这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。 + +那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。 + +所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下表}。 + +在遍历数组的时候,只需要向map去查询是否有和目前遍历元素比配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。 + +过程如下: + +![过程一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220711202638.png) + +![过程二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220711202708.png) C++代码: @@ -66,18 +95,31 @@ public: vector twoSum(vector& nums, int target) { std::unordered_map map; for(int i = 0; i < nums.size(); i++) { - auto iter = map.find(target - nums[i]); + // 遍历当前元素,并在map中寻找是否有匹配的key + auto iter = map.find(target - nums[i]); if(iter != map.end()) { return {iter->second, i}; } - map.insert(pair(nums[i], i)); + // 如果没找到匹配对,就把访问过的元素和下标加入到map中 + map.insert(pair(nums[i], i)); } return {}; } }; ``` +## 总结 +本题其实有四个重点: + +* 为什么会想到用哈希表 +* 哈希表为什么用map +* 本题map是用来存什么的 +* map中的key和value用来存什么的 + +把这四点想清楚了,本题才算是理解透彻了。 + +很多录友把这道题目 通过了,但都没想清楚map是用来做什么的,以至于对代码的理解其实是 一知半解的。 ## 其他语言版本 @@ -250,30 +292,30 @@ func twoSum(_ nums: [Int], _ target: Int) -> [Int] { } ``` -PHP: -```php -class Solution { - /** - * @param Integer[] $nums - * @param Integer $target - * @return Integer[] - */ - function twoSum($nums, $target) { - if (count($nums) == 0) { - return []; - } - $table = []; - for ($i = 0; $i < count($nums); $i++) { - $temp = $target - $nums[$i]; - if (isset($table[$temp])) { - return [$table[$temp], $i]; - } - $table[$nums[$i]] = $i; - } - return []; + +Scala: +```scala +object Solution { + // 导入包 + import scala.collection.mutable + def twoSum(nums: Array[Int], target: Int): Array[Int] = { + // key存储值,value存储下标 + val map = new mutable.HashMap[Int, Int]() + for (i <- nums.indices) { + val tmp = target - nums(i) // 计算差值 + // 如果这个差值存在于map,则说明找到了结果 + if (map.contains(tmp)) { + return Array(map.get(tmp).get, i) + } + // 如果不包含把当前值与其下标放到map + map.put(nums(i), i) } + // 如果没有找到直接返回一个空的数组,return关键字可以省略 + new Array[Int](2) + } } ``` + C#: ```csharp public class Solution { @@ -293,5 +335,20 @@ public class Solution { } ``` +Dart: +```dart +List twoSum(List nums, int target) { + var tmp = []; + for (var i = 0; i < nums.length; i++) { + var rest = target - nums[i]; + if(tmp.contains(rest)){ + return [tmp.indexOf(rest), i]; + } + tmp.add(nums[i]); + } + return [0 , 0]; +} +``` + -----------------------
diff --git a/problems/0005.最长回文子串.md b/problems/0005.最长回文子串.md index eaebb5ab..d53acf63 100644 --- a/problems/0005.最长回文子串.md +++ b/problems/0005.最长回文子串.md @@ -8,7 +8,7 @@ # 5.最长回文子串 -[力扣题目链接](https://leetcode-cn.com/problems/longest-palindromic-substring/) +[力扣题目链接](https://leetcode.cn/problems/longest-palindromic-substring/) 给你一个字符串 s,找到 s 中最长的回文子串。 diff --git a/problems/0015.三数之和.md b/problems/0015.三数之和.md index bfde6b35..f336be5b 100644 --- a/problems/0015.三数之和.md +++ b/problems/0015.三数之和.md @@ -10,7 +10,7 @@ # 第15题. 三数之和 -[力扣题目链接](https://leetcode-cn.com/problems/3sum/) +[力扣题目链接](https://leetcode.cn/problems/3sum/) 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。 @@ -39,7 +39,7 @@ 去重的过程不好处理,有很多小细节,如果在面试中很难想到位。 -时间复杂度可以做到$O(n^2)$,但还是比较费时的,因为不好做剪枝操作。 +时间复杂度可以做到O(n^2),但还是比较费时的,因为不好做剪枝操作。 大家可以尝试使用哈希法写一写,就知道其困难的程度了。 @@ -85,7 +85,7 @@ public: **其实这道题目使用哈希法并不十分合适**,因为在去重的操作中有很多细节需要注意,在面试中很难直接写出没有bug的代码。 -而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是$O(n^2)$,也是可以在leetcode上通过,但是程序的执行时间依然比较长 。 +而且使用哈希法 在使用两层for循环的时候,能做的剪枝操作很有限,虽然时间复杂度是O(n^2),也是可以在leetcode上通过,但是程序的执行时间依然比较长 。 接下来我来介绍另一个解法:双指针法,**这道题目使用双指针法 要比哈希法高效一些**,那么来讲解一下具体实现的思路。 @@ -95,13 +95,13 @@ public: 拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。 -依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i] b = nums[left] c = nums[right]。 +依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。 接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。 如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。 -时间复杂度:$O(n^2)$。 +时间复杂度:O(n^2)。 C++代码代码如下: @@ -118,13 +118,13 @@ public: if (nums[i] > 0) { return result; } - // 错误去重方法,将会漏掉-1,-1,2 这种情况 + // 错误去重a方法,将会漏掉-1,-1,2 这种情况 /* if (nums[i] == nums[i + 1]) { continue; } */ - // 正确去重方法 + // 正确去重a方法 if (i > 0 && nums[i] == nums[i - 1]) { continue; } @@ -136,17 +136,11 @@ public: while (right > left && nums[right] == nums[right - 1]) right--; while (right > left && nums[left] == nums[left + 1]) left++; */ - if (nums[i] + nums[left] + nums[right] > 0) { - right--; - // 当前元素不合适了,可以去重 - while (left < right && nums[right] == nums[right + 1]) right--; - } else if (nums[i] + nums[left] + nums[right] < 0) { - left++; - // 不合适,去重 - while (left < right && nums[left] == nums[left - 1]) left++; - } else { + if (nums[i] + nums[left] + nums[right] > 0) right--; + else if (nums[i] + nums[left] + nums[right] < 0) left++; + else { result.push_back(vector{nums[i], nums[left], nums[right]}); - // 去重逻辑应该放在找到一个三元组之后 + // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重 while (right > left && nums[right] == nums[right - 1]) right--; while (right > left && nums[left] == nums[left + 1]) left++; @@ -162,6 +156,78 @@ public: }; ``` +## 去重逻辑的思考 + +### a的去重 + +说道去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right] + +a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。 + +但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。 + +有同学可能想,这不都一样吗。 + +其实不一样! + +都是和 nums[i]进行比较,是比较它的前一个,还是比较他的后一个。 + +如果我们的写法是 这样: + +```C++ +if (nums[i] == nums[i + 1]) { // 去重操作 + continue; +} +``` + +那就我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。 + +**我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!** + +所以这里是有两个重复的维度。 + +那么应该这么写: + +```C++ +if (i > 0 && nums[i] == nums[i - 1]) { + continue; +} +``` + +这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。 + +这是一个非常细节的思考过程。 + +### b与c的去重 + +很多同学写本题的时候,去重的逻辑多加了 对right 和left 的去重:(代码中注释部分) + +```C++ +while (right > left) { + if (nums[i] + nums[left] + nums[right] > 0) { + right--; + // 去重 right + while (left < right && nums[right] == nums[right + 1]) right--; + } else if (nums[i] + nums[left] + nums[right] < 0) { + left++; + // 去重 left + while (left < right && nums[left] == nums[left - 1]) left++; + } else { + } +} +``` + +但细想一下,这种去重其实对提升程序运行效率是没有帮助的。 + +拿right去重为例,即使不加这个去重逻辑,依然根据 `while (right > left) ` 和 `if (nums[i] + nums[left] + nums[right] > 0)` 去完成right-- 的操作。 + +多加了 ` while (left < right && nums[right] == nums[right + 1]) right--;` 这一行代码,其实就是把 需要执行的逻辑提前执行了,但并没有减少 判断的逻辑。 + +最直白的思考过程,就是right还是一个数一个数的减下去的,所以在哪里减的都是一样的。 + +所以这种去重 是可以不加的。 仅仅是 把去重的逻辑提前了而已。 + + # 思考题 @@ -312,57 +378,109 @@ func threeSum(nums []int)[][]int{ javaScript: +```js +var threeSum = function(nums) { + const res = [], len = nums.length + // 将数组排序 + nums.sort((a, b) => a - b) + for (let i = 0; i < len; i++) { + let l = i + 1, r = len - 1, iNum = nums[i] + // 数组排过序,如果第一个数大于0直接返回res + if (iNum > 0) return res + // 去重 + if (iNum == nums[i - 1]) continue + while(l < r) { + let lNum = nums[l], rNum = nums[r], threeSum = iNum + lNum + rNum + // 三数之和小于0,则左指针向右移动 + if (threeSum < 0) l++ + else if (threeSum > 0) r-- + else { + res.push([iNum, lNum, rNum]) + // 去重 + while(l < r && nums[l] == nums[l + 1]){ + l++ + } + while(l < r && nums[r] == nums[r - 1]) { + r-- + } + l++ + r-- + } + } + } + return res +}; +``` + +解法二:nSum通用解法。递归 + ```js /** + * nsum通用解法,支持2sum,3sum,4sum...等等 + * 时间复杂度分析: + * 1. n = 2时,时间复杂度O(NlogN),排序所消耗的时间。、 + * 2. n > 2时,时间复杂度为O(N^n-1),即N的n-1次方,至少是2次方,此时可省略排序所消耗的时间。举例:3sum为O(n^2),4sum为O(n^3) * @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--; +var threeSum = function (nums) { + // nsum通用解法核心方法 + function nSumTarget(nums, n, start, target) { + // 前提:nums要先排序好 + let res = []; + if (n === 2) { + res = towSumTarget(nums, start, target); + } else { + for (let i = start; i < nums.length; i++) { + // 递归求(n - 1)sum + let subRes = nSumTarget( + nums, + n - 1, + i + 1, + target - nums[i] + ); + for (let j = 0; j < subRes.length; j++) { + res.push([nums[i], ...subRes[j]]); + } + // 跳过相同元素 + while (nums[i] === nums[i + 1]) i++; + } } + return res; } - 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]); + function towSumTarget(nums, start, target) { + // 前提:nums要先排序好 + let res = []; + let len = nums.length; + let left = start; + let right = len - 1; + while (left < right) { + let sum = nums[left] + nums[right]; + if (sum < target) { + while (nums[left] === nums[left + 1]) left++; + left++; + } else if (sum > target) { + while (nums[right] === nums[right - 1]) right--; + right--; + } else { + // 相等 + res.push([nums[left], nums[right]]); + // 跳过相同元素 + while (nums[left] === nums[left + 1]) left++; + while (nums[right] === nums[right - 1]) right--; + left++; + right--; + } } + return res; } - return res; + nums.sort((a, b) => a - b); + // n = 3,此时求3sum之和 + return nSumTarget(nums, 3, 0, 0); }; ``` + TypeScript: ```typescript @@ -502,6 +620,71 @@ func threeSum(_ nums: [Int]) -> [[Int]] { } ``` +Rust: +```Rust +// 哈希解法 +use std::collections::HashSet; +impl Solution { + pub fn three_sum(nums: Vec) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut nums = nums; + nums.sort(); + let len = nums.len(); + for i in 0..len { + if nums[i] > 0 { break; } + if i > 0 && nums[i] == nums[i - 1] { continue; } + let mut set = HashSet::new(); + for j in (i + 1)..len { + if j > i + 2 && nums[j] == nums[j - 1] && nums[j] == nums[j - 2] { continue; } + let c = 0 - (nums[i] + nums[j]); + if set.contains(&c) { + result.push(vec![nums[i], nums[j], c]); + set.remove(&c); + } else { set.insert(nums[j]); } + } + } + result + } +} +``` + +```Rust +// 双指针法 +use std::collections::HashSet; +impl Solution { + pub fn three_sum(nums: Vec) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut nums = nums; + nums.sort(); + let len = nums.len(); + for i in 0..len { + if nums[i] > 0 { return result; } + if i > 0 && nums[i] == nums[i - 1] { continue; } + let (mut left, mut right) = (i + 1, len - 1); + while left < right { + if nums[i] + nums[left] + nums[right] > 0 { + right -= 1; + // 去重 + while left < right && nums[right] == nums[right + 1] { right -= 1; } + } else if nums[i] + nums[left] + nums[right] < 0 { + left += 1; + // 去重 + while left < right && nums[left] == nums[left - 1] { left += 1; } + } else { + result.push(vec![nums[i], nums[left], nums[right]]); + // 去重 + right -= 1; + left += 1; + while left < right && nums[right] == nums[right + 1] { right -= 1; } + while left < right && nums[left] == nums[left - 1] { left += 1; } + } + } + } + result + } +} +``` + C: ```C //qsort辅助cmp函数 @@ -634,6 +817,49 @@ public class Solution } } ``` +Scala: +```scala +object Solution { + // 导包 + import scala.collection.mutable.ListBuffer + import scala.util.control.Breaks.{break, breakable} + def threeSum(nums: Array[Int]): List[List[Int]] = { + // 定义结果集,最后需要转换为List + val res = ListBuffer[List[Int]]() + val nums_tmp = nums.sorted // 对nums进行排序 + for (i <- nums_tmp.indices) { + // 如果要排的第一个数字大于0,直接返回结果 + if (nums_tmp(i) > 0) { + return res.toList + } + // 如果i大于0并且和前一个数字重复,则跳过本次循环,相当于continue + breakable { + if (i > 0 && nums_tmp(i) == nums_tmp(i - 1)) { + break + } else { + var left = i + 1 + var right = nums_tmp.length - 1 + while (left < right) { + var sum = nums_tmp(i) + nums_tmp(left) + nums_tmp(right) // 求三数之和 + if (sum < 0) left += 1 + else if (sum > 0) right -= 1 + else { + res += List(nums_tmp(i), nums_tmp(left), nums_tmp(right)) // 如果等于0 添加进结果集 + // 为了避免重复,对left和right进行移动 + while (left < right && nums_tmp(left) == nums_tmp(left + 1)) left += 1 + while (left < right && nums_tmp(right) == nums_tmp(right - 1)) right -= 1 + left += 1 + right -= 1 + } + } + } + } + } + // 最终返回需要转换为List,return关键字可以省略 + res.toList + } +} +``` -----------------------
diff --git a/problems/0017.电话号码的字母组合.md b/problems/0017.电话号码的字母组合.md index 94136565..5778e903 100644 --- a/problems/0017.电话号码的字母组合.md +++ b/problems/0017.电话号码的字母组合.md @@ -7,7 +7,7 @@ # 17.电话号码的字母组合 -[力扣题目链接](https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/) +[力扣题目链接](https://leetcode.cn/problems/letter-combinations-of-a-phone-number/) 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。 @@ -454,6 +454,49 @@ function letterCombinations(digits: string): string[] { }; ``` +## Rust + +```Rust +impl Solution { + fn backtracking(result: &mut Vec, s: &mut String, map: &[&str; 10], digits: &String, index: usize) { + let len = digits.len(); + if len == index { + result.push(s.to_string()); + return; + } + // 在保证不会越界的情况下使用unwrap()将Some()中的值提取出来 + let digit= digits.chars().nth(index).unwrap().to_digit(10).unwrap() as usize; + let letters = map[digit]; + for i in letters.chars() { + s.push(i); + Self::backtracking(result, s, &map, &digits, index+1); + s.pop(); + } + } + pub fn letter_combinations(digits: String) -> Vec { + if digits.len() == 0 { + return vec![]; + } + const MAP: [&str; 10] = [ + "", + "", + "abc", + "def", + "ghi", + "jkl", + "mno", + "pqrs", + "tuv", + "wxyz" + ]; + let mut result: Vec = Vec::new(); + let mut s: String = String::new(); + Self::backtracking(&mut result, &mut s, &MAP, &digits, 0); + result + } +} +``` + ## C ```c @@ -557,6 +600,37 @@ func letterCombinations(_ digits: String) -> [String] { } ``` +## Scala: + +```scala +object Solution { + import scala.collection.mutable + def letterCombinations(digits: String): List[String] = { + var result = mutable.ListBuffer[String]() + if(digits == "") return result.toList // 如果参数为空,返回空结果集的List形式 + var path = mutable.ListBuffer[Char]() + // 数字和字符的映射关系 + val map = Array[String]("", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz") + + def backtracking(index: Int): Unit = { + if (index == digits.size) { + result.append(path.mkString) // mkString语法:将数组类型直接转换为字符串 + return + } + var digit = digits(index) - '0' // 这里使用toInt会报错!必须 -'0' + for (i <- 0 until map(digit).size) { + path.append(map(digit)(i)) + backtracking(index + 1) + path = path.take(path.size - 1) + } + } + + backtracking(0) + result.toList + } +} +``` + -----------------------
diff --git a/problems/0018.四数之和.md b/problems/0018.四数之和.md index ee70cb69..3813424a 100644 --- a/problems/0018.四数之和.md +++ b/problems/0018.四数之和.md @@ -10,7 +10,7 @@ # 第18题. 四数之和 -[力扣题目链接](https://leetcode-cn.com/problems/4sum/) +[力扣题目链接](https://leetcode.cn/problems/4sum/) 题意:给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。 @@ -35,11 +35,11 @@ [15.三数之和](https://programmercarl.com/0015.三数之和.html)的双指针解法是一层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)$ 。 +四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。 那么一样的道理,五数之和、六数之和等等都采用这种解法。 -对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力$O(n^3)$的解法,降为$O(n^2)$的解法,四数之和的双指针解法就是将原本暴力$O(n^4)$的解法,降为$O(n^3)$的解法。 +对于[15.三数之和](https://programmercarl.com/0015.三数之和.html)双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。 之前我们讲过哈希表的经典题目:[454.四数相加II](https://programmercarl.com/0454.四数相加II.html),相对于本题简单很多,因为本题是要求在一个集合中找出四个数相加等于target,同时四元组不能重复。 @@ -47,14 +47,13 @@ 我们来回顾一下,几道题目使用了双指针法。 -双指针法将时间复杂度:$O(n^2)$的解法优化为 $O(n)$的解法。也就是降一个数量级,题目如下: +双指针法将时间复杂度:O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级,题目如下: * [27.移除元素](https://programmercarl.com/0027.移除元素.html) * [15.三数之和](https://programmercarl.com/0015.三数之和.html) * [18.四数之和](https://programmercarl.com/0018.四数之和.html) - -操作链表: +链表相关双指针题目: * [206.反转链表](https://programmercarl.com/0206.翻转链表.html) * [19.删除链表的倒数第N个节点](https://programmercarl.com/0019.删除链表的倒数第N个节点.html) @@ -72,21 +71,21 @@ public: vector> result; sort(nums.begin(), nums.end()); for (int k = 0; k < nums.size(); k++) { - // 剪枝处理 - if (nums[k] > target && (nums[k] >= 0 || target >= 0)) { + // 剪枝处理 + if (nums[k] > target && nums[k] >= 0 && target >= 0) { break; // 这里使用break,统一通过最后的return返回 } - // 去重 + // 对nums[k]去重 if (k > 0 && nums[k] == nums[k - 1]) { continue; } for (int i = k + 1; i < nums.size(); i++) { - // 2级剪枝处理 - if (nums[k] + nums[i] > target && (nums[k] + nums[i] >= 0 || target >= 0)) { - break; - } - - // 正确去重方法 + // 2级剪枝处理 + if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0 && target >= 0) { + break; + } + + // 对nums[i]去重 if (i > k + 1 && nums[i] == nums[i - 1]) { continue; } @@ -94,18 +93,14 @@ public: int right = nums.size() - 1; while (right > left) { // nums[k] + nums[i] + nums[left] + nums[right] > target 会溢出 - if (nums[k] + nums[i] > target - (nums[left] + nums[right])) { + if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) { right--; - // 当前元素不合适了,可以去重 - while (left < right && nums[right] == nums[right + 1]) right--; // nums[k] + nums[i] + nums[left] + nums[right] < target 会溢出 - } else if (nums[k] + nums[i] < target - (nums[left] + nums[right])) { + } else if ((long) nums[k] + nums[i] + nums[left] + nums[right] < target) { left++; - // 不合适,去重 - while (left < right && nums[left] == nums[left - 1]) left++; } else { result.push_back(vector{nums[k], nums[i], nums[left], nums[right]}); - // 去重逻辑应该放在找到一个四元组之后 + // 对nums[left]和nums[right]去重 while (right > left && nums[right] == nums[right - 1]) right--; while (right > left && nums[left] == nums[left + 1]) left++; @@ -140,6 +135,11 @@ class Solution { for (int i = 0; i < nums.length; i++) { + // nums[i] > target 直接返回, 剪枝操作 + if (nums[i] > 0 && nums[i] > target) { + return result; + } + if (i > 0 && nums[i - 1] == nums[i]) { continue; } @@ -153,7 +153,7 @@ class Solution { int left = j + 1; int right = nums.length - 1; while (right > left) { - int sum = nums[i] + nums[j] + nums[left] + nums[right]; + long sum = (long) nums[i] + nums[j] + nums[left] + nums[right]; if (sum > target) { right--; } else if (sum < target) { @@ -523,5 +523,93 @@ public class Solution } ``` +Rust: +```Rust +impl Solution { + pub fn four_sum(nums: Vec, target: i32) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut nums = nums; + nums.sort(); + let len = nums.len(); + for k in 0..len { + // 剪枝 + if nums[k] > target && (nums[k] > 0 || target > 0) { break; } + // 去重 + if k > 0 && nums[k] == nums[k - 1] { continue; } + for i in (k + 1)..len { + // 剪枝 + if nums[k] + nums[i] > target && (nums[k] + nums[i] >= 0 || target >= 0) { break; } + // 去重 + if i > k + 1 && nums[i] == nums[i - 1] { continue; } + let (mut left, mut right) = (i + 1, len - 1); + while left < right { + if nums[k] + nums[i] > target - (nums[left] + nums[right]) { + right -= 1; + // 去重 + while left < right && nums[right] == nums[right + 1] { right -= 1; } + } else if nums[k] + nums[i] < target - (nums[left] + nums[right]) { + left += 1; + // 去重 + while left < right && nums[left] == nums[left - 1] { left += 1; } + } else { + result.push(vec![nums[k], nums[i], nums[left], nums[right]]); + // 去重 + while left < right && nums[right] == nums[right - 1] { right -= 1; } + while left < right && nums[left] == nums[left + 1] { left += 1; } + left += 1; + right -= 1; + } + } + } + } + result + } +} +``` + +Scala: +```scala +object Solution { + // 导包 + import scala.collection.mutable.ListBuffer + import scala.util.control.Breaks.{break, breakable} + def fourSum(nums: Array[Int], target: Int): List[List[Int]] = { + val res = ListBuffer[List[Int]]() + val nums_tmp = nums.sorted // 先排序 + for (i <- nums_tmp.indices) { + breakable { + if (i > 0 && nums_tmp(i) == nums_tmp(i - 1)) { + break // 如果该值和上次的值相同,跳过本次循环,相当于continue + } else { + for (j <- i + 1 until nums_tmp.length) { + breakable { + if (j > i + 1 && nums_tmp(j) == nums_tmp(j - 1)) { + break // 同上 + } else { + // 双指针 + var (left, right) = (j + 1, nums_tmp.length - 1) + while (left < right) { + var sum = nums_tmp(i) + nums_tmp(j) + nums_tmp(left) + nums_tmp(right) + if (sum == target) { + // 满足要求,直接加入到集合里面去 + res += List(nums_tmp(i), nums_tmp(j), nums_tmp(left), nums_tmp(right)) + while (left < right && nums_tmp(left) == nums_tmp(left + 1)) left += 1 + while (left < right && nums_tmp(right) == nums_tmp(right - 1)) right -= 1 + left += 1 + right -= 1 + } else if (sum < target) left += 1 + else right -= 1 + } + } + } + } + } + } + } + // 最终返回的res要转换为List,return关键字可以省略 + res.toList + } +} +``` -----------------------
diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index 813e9b02..3499ab9d 100644 --- a/problems/0019.删除链表的倒数第N个节点.md +++ b/problems/0019.删除链表的倒数第N个节点.md @@ -9,7 +9,7 @@ ## 19.删除链表的倒数第N个节点 -[力扣题目链接](https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/) +[力扣题目链接](https://leetcode.cn/problems/remove-nth-node-from-end-of-list/) 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 @@ -39,7 +39,7 @@ 分为如下几步: -* 首先这里我推荐大家使用虚拟头结点,这样方面处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html) +* 首先这里我推荐大家使用虚拟头结点,这样方便处理删除实际头结点的逻辑,如果虚拟头结点不清楚,可以看这篇: [链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html) * 定义fast指针和slow指针,初始值为虚拟头结点,如图: @@ -290,5 +290,51 @@ func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? { } ``` + +PHP: +```php +function removeNthFromEnd($head, $n) { + // 设置虚拟头节点 + $dummyHead = new ListNode(); + $dummyHead->next = $head; + + $slow = $fast = $dummyHead; + while($n-- && $fast != null){ + $fast = $fast->next; + } + // fast 再走一步,让 slow 指向删除节点的上一个节点 + $fast = $fast->next; + while ($fast != NULL) { + $fast = $fast->next; + $slow = $slow->next; + } + $slow->next = $slow->next->next; + return $dummyHead->next; + } +``` + +Scala: +```scala +object Solution { + def removeNthFromEnd(head: ListNode, n: Int): ListNode = { + val dummy = new ListNode(-1, head) // 定义虚拟头节点 + var fast = head // 快指针从头开始走 + var slow = dummy // 慢指针从虚拟头开始头 + // 因为参数 n 是不可变量,所以不能使用 while(n>0){n-=1}的方式 + for (i <- 0 until n) { + fast = fast.next + } + // 快指针和满指针一起走,直到fast走到null + while (fast != null) { + slow = slow.next + fast = fast.next + } + // 删除slow的下一个节点 + slow.next = slow.next.next + // 返回虚拟头节点的下一个 + dummy.next + } +} +``` -----------------------
diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index cdca70f9..eaf0a719 100644 --- a/problems/0020.有效的括号.md +++ b/problems/0020.有效的括号.md @@ -10,7 +10,7 @@ # 20. 有效的括号 -[力扣题目链接](https://leetcode-cn.com/problems/valid-parentheses/) +[力扣题目链接](https://leetcode.cn/problems/valid-parentheses/) 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 @@ -401,6 +401,7 @@ bool isValid(char * s){ } ``` + C#: ```csharp public class Solution { @@ -432,5 +433,58 @@ public class Solution { } } ``` + +PHP: +```php +// https://www.php.net/manual/zh/class.splstack.php +class Solution +{ + function isValid($s){ + $stack = new SplStack(); + for ($i = 0; $i < strlen($s); $i++) { + if ($s[$i] == "(") { + $stack->push(')'); + } else if ($s[$i] == "{") { + $stack->push('}'); + } else if ($s[$i] == "[") { + $stack->push(']'); + // 2、遍历匹配过程中,发现栈内没有要匹配的字符 return false + // 3、遍历匹配过程中,栈已为空,没有匹配的字符了,说明右括号没有找到对应的左括号 return false + } else if ($stack->isEmpty() || $stack->top() != $s[$i]) { + return false; + } else {//$stack->top() == $s[$i] + $stack->pop(); + } + } + // 1、遍历完,但是栈不为空,说明有相应的括号没有被匹配,return false + return $stack->isEmpty(); + } +} +``` + + +Scala: +```scala +object Solution { + import scala.collection.mutable + def isValid(s: String): Boolean = { + if(s.length % 2 != 0) return false // 如果字符串长度是奇数直接返回false + val stack = mutable.Stack[Char]() + // 循环遍历字符串 + for (i <- s.indices) { + val c = s(i) + if (c == '(' || c == '[' || c == '{') stack.push(c) + else if(stack.isEmpty) return false // 如果没有(、[、{则直接返回false + // 以下三种情况,不满足则直接返回false + else if(c==')' && stack.pop() != '(') return false + else if(c==']' && stack.pop() != '[') return false + else if(c=='}' && stack.pop() != '{') return false + } + // 如果为空则正确匹配,否则还有余孽就不匹配 + stack.isEmpty + } +} +``` + -----------------------
diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md index ce75e0d7..4b4b39e7 100644 --- a/problems/0024.两两交换链表中的节点.md +++ b/problems/0024.两两交换链表中的节点.md @@ -7,7 +7,7 @@ ## 24. 两两交换链表中的节点 -[力扣题目链接](https://leetcode-cn.com/problems/swap-nodes-in-pairs/) +[力扣题目链接](https://leetcode.cn/problems/swap-nodes-in-pairs/) 给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 @@ -18,6 +18,8 @@ ## 思路 +针对本题重点难点,我录制了B站讲解视频,[帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点](https://www.bilibili.com/video/BV1YT411g7br),相信结合视频在看本篇题解,更有助于大家对链表的理解。 + 这道题目正常模拟就可以了。 建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。 @@ -63,8 +65,8 @@ public: }; ``` -* 时间复杂度:$O(n)$ -* 空间复杂度:$O(1)$ +* 时间复杂度:O(n) +* 空间复杂度:O(1) ## 拓展 @@ -254,20 +256,19 @@ TypeScript: ```typescript function swapPairs(head: ListNode | null): ListNode | null { - const dummyHead: ListNode = new ListNode(0, head); - let cur: ListNode = dummyHead; - while(cur.next !== null && cur.next.next !== null) { - const tem: ListNode = cur.next; - const tem1: ListNode = cur.next.next.next; - - cur.next = cur.next.next; // step 1 - cur.next.next = tem; // step 2 - cur.next.next.next = tem1; // step 3 - - cur = cur.next.next; - } - return dummyHead.next; -} + const dummyNode: ListNode = new ListNode(0, head); + let curNode: ListNode | null = dummyNode; + while (curNode && curNode.next && curNode.next.next) { + let firstNode: ListNode = curNode.next, + secNode: ListNode = curNode.next.next, + thirdNode: ListNode | null = curNode.next.next.next; + curNode.next = secNode; + secNode.next = firstNode; + firstNode.next = thirdNode; + curNode = firstNode; + } + return dummyNode.next; +}; ``` Kotlin: @@ -311,7 +312,29 @@ func swapPairs(_ head: ListNode?) -> ListNode? { return dummyHead.next } ``` - +Scala: +```scala +// 虚拟头节点 +object Solution { + def swapPairs(head: ListNode): ListNode = { + var dummy = new ListNode(0, head) // 虚拟头节点 + var pre = dummy + var cur = head + // 当pre的下一个和下下个都不为空,才进行两两转换 + while (pre.next != null && pre.next.next != null) { + var tmp: ListNode = cur.next.next // 缓存下一次要进行转换的第一个节点 + pre.next = cur.next // 步骤一 + cur.next.next = cur // 步骤二 + cur.next = tmp // 步骤三 + // 下面是准备下一轮的交换 + pre = cur + cur = tmp + } + // 最终返回dummy虚拟头节点的下一个,return可以省略 + dummy.next + } +} +``` -----------------------
diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index 590cf0b9..e99fee91 100644 --- a/problems/0027.移除元素.md +++ b/problems/0027.移除元素.md @@ -7,7 +7,7 @@ ## 27. 移除元素 -[力扣题目链接](https://leetcode-cn.com/problems/remove-element/) +[力扣题目链接](https://leetcode.cn/problems/remove-element/) 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。 @@ -28,6 +28,8 @@ ## 思路 +[本题B站视频讲解](https://www.bilibili.com/video/BV12A4y1Z7LP) + 有的同学可能说了,多余的元素,删掉不就得了。 **要知道数组的元素在内存地址中是连续的,不能单独删除数组中的某个元素,只能覆盖。** @@ -75,13 +77,23 @@ public: 双指针法(快慢指针法): **通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。** +定义快慢指针 + +* 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组 +* 慢指针:指向更新 新数组下标的位置 + +很多同学这道题目做的很懵,就是不理解 快慢指针究竟都是什么含义,所以一定要明确含义,后面的思路就更容易理解了。 + 删除过程如下: ![27.移除元素-双指针法](https://tva1.sinaimg.cn/large/008eGmZEly1gntrds6r59g30du09mnpd.gif) +很多同学不了解 + + **双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。** -后序都会一一介绍到,本题代码如下: +后续都会一一介绍到,本题代码如下: ```CPP // 时间复杂度:O(n) @@ -104,8 +116,6 @@ public: * 时间复杂度:O(n) * 空间复杂度:O(1) -旧文链接:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html) - ```CPP /** * 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素 @@ -173,28 +183,24 @@ class Solution { Python: -```python +```python3 class Solution: - """双指针法 - 时间复杂度:O(n) - 空间复杂度:O(1) - """ - - @classmethod - def removeElement(cls, nums: List[int], val: int) -> int: - fast = slow = 0 - - while fast < len(nums): - - if nums[fast] != val: - nums[slow] = nums[fast] - slow += 1 - - # 当 fast 指针遇到要删除的元素时停止赋值 - # slow 指针停止移动, fast 指针继续前进 - fast += 1 - - return slow + def removeElement(self, nums: List[int], val: int) -> int: + if nums is None or len(nums)==0: + return 0 + l=0 + r=len(nums)-1 + while l diff --git a/problems/0028.实现strStr.md b/problems/0028.实现strStr.md index d67e5f70..271822dc 100644 --- a/problems/0028.实现strStr.md +++ b/problems/0028.实现strStr.md @@ -9,7 +9,7 @@ # 28. 实现 strStr() -[力扣题目链接](https://leetcode-cn.com/problems/implement-strstr/) +[力扣题目链接](https://leetcode.cn/problems/implement-strstr/) 实现 strStr() 函数。 @@ -685,7 +685,21 @@ class Solution { ``` Python3: - +```python +//暴力解法: +class Solution(object): + def strStr(self, haystack, needle): + """ + :type haystack: str + :type needle: str + :rtype: int + """ + m,n=len(haystack),len(needle) + for i in range(m): + if haystack[i:i+n]==needle: + return i + return -1 +``` ```python // 方法一 class Solution: @@ -1166,5 +1180,124 @@ func strStr(_ haystack: String, _ needle: String) -> Int { ``` +PHP: + +> 前缀表统一减一 +```php +function strStr($haystack, $needle) { + if (strlen($needle) == 0) return 0; + $next= []; + $this->getNext($next,$needle); + + $j = -1; + for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始 + while($j >= 0 && $haystack[$i] != $needle[$j + 1]) { + $j = $next[$j]; + } + if ($haystack[$i] == $needle[$j + 1]) { + $j++; + } + if ($j == (strlen($needle) - 1) ) { + return ($i - strlen($needle) + 1); + } + } + return -1; +} + +function getNext(&$next, $s){ + $j = -1; + $next[0] = $j; + for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始 + while ($j >= 0 && $s[$i] != $s[$j + 1]) { + $j = $next[$j]; + } + if ($s[$i] == $s[$j + 1]) { + $j++; + } + $next[$i] = $j; + } +} +``` + +> 前缀表统一不减一 +```php +function strStr($haystack, $needle) { + if (strlen($needle) == 0) return 0; + $next= []; + $this->getNext($next,$needle); + + $j = 0; + for ($i = 0;$i < strlen($haystack); $i++) { // 注意i就从0开始 + while($j > 0 && $haystack[$i] != $needle[$j]) { + $j = $next[$j-1]; + } + if ($haystack[$i] == $needle[$j]) { + $j++; + } + if ($j == strlen($needle)) { + return ($i - strlen($needle) + 1); + } + } + return -1; +} + +function getNext(&$next, $s){ + $j = 0; + $next[0] = $j; + for($i = 1; $i < strlen($s); $i++) { // 注意i从1开始 + while ($j > 0 && $s[$i] != $s[$j]) { + $j = $next[$j-1]; + } + if ($s[$i] == $s[$j]) { + $j++; + } + $next[$i] = $j; + } +} +``` + +Rust: + +> 前缀表统一不减一 +```Rust +impl Solution { + pub fn get_next(next: &mut Vec, s: &Vec) { + let len = s.len(); + let mut j = 0; + for i in 1..len { + while j > 0 && s[i] != s[j] { + j = next[j - 1]; + } + if s[i] == s[j] { + j += 1; + } + next[i] = j; + } + } + + pub fn str_str(haystack: String, needle: String) -> i32 { + let (haystack_len, needle_len) = (haystack.len(), needle.len()); + if haystack_len == 0 { return 0; } + if haystack_len < needle_len { return -1;} + let (haystack, needle) = (haystack.chars().collect::>(), needle.chars().collect::>()); + let mut next: Vec = vec![0; haystack_len]; + Self::get_next(&mut next, &needle); + let mut j = 0; + for i in 0..haystack_len { + while j > 0 && haystack[i] != needle[j] { + j = next[j - 1]; + } + if haystack[i] == needle[j] { + j += 1; + } + if j == needle_len { + return (i - needle_len + 1) as i32; + } + } + return -1; + } +} +``` + -----------------------
diff --git a/problems/0031.下一个排列.md b/problems/0031.下一个排列.md index 2219e24d..1a3641b0 100644 --- a/problems/0031.下一个排列.md +++ b/problems/0031.下一个排列.md @@ -9,7 +9,7 @@ # 31.下一个排列 -[力扣题目链接](https://leetcode-cn.com/problems/next-permutation/) +[力扣题目链接](https://leetcode.cn/problems/next-permutation/) 实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 @@ -171,13 +171,13 @@ class Solution(object): i = n-2 while i >= 0 and nums[i] >= nums[i+1]: i -= 1 - + if i > -1: // i==-1,不存在下一个更大的排列 j = n-1 while j >= 0 and nums[j] <= nums[i]: j -= 1 nums[i], nums[j] = nums[j], nums[i] - + start, end = i+1, n-1 while start < end: nums[start], nums[end] = nums[end], nums[start] @@ -190,6 +190,26 @@ class Solution(object): ## Go ```go +//卡尔的解法 +func nextPermutation(nums []int) { + for i:=len(nums)-1;i>=0;i--{ + for j:=len(nums)-1;j>i;j--{ + if nums[j]>nums[i]{ + //交换 + nums[j],nums[i]=nums[i],nums[j] + reverse(nums,0+i+1,len(nums)-1) + return + } + } + } + reverse(nums,0,len(nums)-1) +} +//对目标切片指定区间的反转方法 +func reverse(a []int,begin,end int){ + for i,j:=begin,end;i= target) { + // 左边界一定在mid左边(不含mid) + right = mid - 1; + leftBoard = right; + } else { + // 左边界在mid右边(含mid) + left = mid + 1; + } + } + return leftBoard; +} +``` + + +### Scala +```scala +object Solution { + def searchRange(nums: Array[Int], target: Int): Array[Int] = { + var left = getLeftBorder(nums, target) + var right = getRightBorder(nums, target) + if (left == -2 || right == -2) return Array(-1, -1) + if (right - left > 1) return Array(left + 1, right - 1) + Array(-1, -1) + } + + // 寻找左边界 + def getLeftBorder(nums: Array[Int], target: Int): Int = { + var leftBorder = -2 + var left = 0 + var right = nums.length - 1 + while (left <= right) { + var mid = left + (right - left) / 2 + if (nums(mid) >= target) { + right = mid - 1 + leftBorder = right + } else { + left = mid + 1 + } + } + leftBorder + } + + // 寻找右边界 + def getRightBorder(nums: Array[Int], target: Int): Int = { + var rightBorder = -2 + var left = 0 + var right = nums.length - 1 + while (left <= right) { + var mid = left + (right - left) / 2 + if (nums(mid) <= target) { + left = mid + 1 + rightBorder = left + } else { + right = mid - 1 + } + } + rightBorder + } +} +``` + + +### Kotlin +```kotlin +class Solution { + fun searchRange(nums: IntArray, target: Int): IntArray { + var index = binarySearch(nums, target) + // 没找到,返回[-1, -1] + if (index == -1) return intArrayOf(-1, -1) + var left = index + var right = index + // 寻找左边界 + while (left - 1 >=0 && nums[left - 1] == target){ + left-- + } + // 寻找右边界 + while (right + 1 target) { + right = middle - 1 + } + else { + if (nums[middle] < target) { + left = middle + 1 + } + else { + return middle + } + } + } + // 没找到,返回-1 + return -1 + } +} +``` + -----------------------
diff --git a/problems/0035.搜索插入位置.md b/problems/0035.搜索插入位置.md index 9a770703..f2bbb8ca 100644 --- a/problems/0035.搜索插入位置.md +++ b/problems/0035.搜索插入位置.md @@ -9,7 +9,7 @@ # 35.搜索插入位置 -[力扣题目链接](https://leetcode-cn.com/problems/search-insert-position/) +[力扣题目链接](https://leetcode.cn/problems/search-insert-position/) 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 @@ -73,8 +73,8 @@ public: }; ``` -* 时间复杂度:$O(n)$ -* 空间复杂度:$O(1)$ +* 时间复杂度:O(n) +* 空间复杂度:O(1) 效率如下: @@ -135,14 +135,14 @@ public: // 目标值在数组所有元素之前 [0, -1] // 目标值等于数组中某一个元素 return middle; // 目标值插入数组中的位置 [left, right],return right + 1 - // 目标值在数组所有元素之后的情况 [left, right], return right + 1 + // 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1 return right + 1; } }; ``` -* 时间复杂度:$O(\log n)$ -* 时间复杂度:$O(1)$ +* 时间复杂度:O(log n) +* 空间复杂度:O(1) 效率如下: ![35_搜索插入位置2](https://img-blog.csdnimg.cn/2020121623272877.png) @@ -178,7 +178,7 @@ public: // 目标值在数组所有元素之前 [0,0) // 目标值等于数组中某一个元素 return middle // 目标值插入数组中的位置 [left, right) ,return right 即可 - // 目标值在数组所有元素之后的情况 [left, right),return right 即可 + // 目标值在数组所有元素之后的情况 [left, right),因为是右开区间,所以 return right return right; } }; @@ -283,6 +283,28 @@ var searchInsert = function (nums, target) { }; ``` +### TypeScript + +```typescript +// 第一种二分法 +function searchInsert(nums: number[], target: number): number { + const length: number = nums.length; + let left: number = 0, + right: number = length - 1; + while (left <= right) { + const mid: number = Math.floor((left + right) / 2); + if (nums[mid] < target) { + left = mid + 1; + } else if (nums[mid] === target) { + return mid; + } else { + right = mid - 1; + } + } + return right + 1; +}; +``` + ### Swift ```swift @@ -316,8 +338,52 @@ func searchInsert(_ nums: [Int], _ target: Int) -> Int { return right + 1 } ``` +### Scala +```scala +object Solution { + def searchInsert(nums: Array[Int], target: Int): Int = { + var left = 0 + var right = nums.length - 1 + while (left <= right) { + var mid = left + (right - left) / 2 + if (target == nums(mid)) { + return mid + } else if (target > nums(mid)) { + left = mid + 1 + } else { + right = mid - 1 + } + } + right + 1 + } +} +``` +### PHP +```php +// 二分法(1):[左闭右闭] +function searchInsert($nums, $target) +{ + $n = count($nums); + $l = 0; + $r = $n - 1; + while ($l <= $r) { + $mid = floor(($l + $r) / 2); + if ($nums[$mid] > $target) { + // 下次搜索在左区间:[$l,$mid-1] + $r = $mid - 1; + } else if ($nums[$mid] < $target) { + // 下次搜索在右区间:[$mid+1,$r] + $l = $mid + 1; + } else { + // 命中返回 + return $mid; + } + } + return $r + 1; +} +``` ----------------------- diff --git a/problems/0037.解数独.md b/problems/0037.解数独.md index c1ac15af..626e9d97 100644 --- a/problems/0037.解数独.md +++ b/problems/0037.解数独.md @@ -9,7 +9,7 @@ # 37. 解数独 -[力扣题目链接](https://leetcode-cn.com/problems/sudoku-solver/) +[力扣题目链接](https://leetcode.cn/problems/sudoku-solver/) 编写一个程序,通过填充空格来解决数独问题。 @@ -602,5 +602,100 @@ func solveSudoku(_ board: inout [[Character]]) { } ``` +### Scala + +详细写法: +```scala +object Solution { + + def solveSudoku(board: Array[Array[Char]]): Unit = { + backtracking(board) + } + + def backtracking(board: Array[Array[Char]]): Boolean = { + for (i <- 0 until 9) { + for (j <- 0 until 9) { + if (board(i)(j) == '.') { // 必须是为 . 的数字才放数字 + for (k <- '1' to '9') { // 这个位置放k是否合适 + if (isVaild(i, j, k, board)) { + board(i)(j) = k + if (backtracking(board)) return true // 找到了立刻返回 + board(i)(j) = '.' // 回溯 + } + } + return false // 9个数都试完了,都不行就返回false + } + } + } + true // 遍历完所有的都没返回false,说明找到了 + } + + def isVaild(x: Int, y: Int, value: Char, board: Array[Array[Char]]): Boolean = { + // 行 + for (i <- 0 until 9 ) { + if (board(i)(y) == value) { + return false + } + } + + // 列 + for (j <- 0 until 9) { + if (board(x)(j) == value) { + return false + } + } + + // 宫 + var row = (x / 3) * 3 + var col = (y / 3) * 3 + for (i <- row until row + 3) { + for (j <- col until col + 3) { + if (board(i)(j) == value) { + return false + } + } + } + + true + } +} +``` + +遵循Scala至简原则写法: +```scala +object Solution { + + def solveSudoku(board: Array[Array[Char]]): Unit = { + backtracking(board) + } + + def backtracking(board: Array[Array[Char]]): Boolean = { + // 双重for循环 + 循环守卫 + for (i <- 0 until 9; j <- 0 until 9 if board(i)(j) == '.') { + // 必须是为 . 的数字才放数字,使用循环守卫判断该位置是否可以放置当前循环的数字 + for (k <- '1' to '9' if isVaild(i, j, k, board)) { // 这个位置放k是否合适 + board(i)(j) = k + if (backtracking(board)) return true // 找到了立刻返回 + board(i)(j) = '.' // 回溯 + } + return false // 9个数都试完了,都不行就返回false + } + true // 遍历完所有的都没返回false,说明找到了 + } + + def isVaild(x: Int, y: Int, value: Char, board: Array[Array[Char]]): Boolean = { + // 行,循环守卫进行判断 + for (i <- 0 until 9 if board(i)(y) == value) return false + // 列,循环守卫进行判断 + for (j <- 0 until 9 if board(x)(j) == value) return false + // 宫,循环守卫进行判断 + var row = (x / 3) * 3 + var col = (y / 3) * 3 + for (i <- row until row + 3; j <- col until col + 3 if board(i)(j) == value) return false + true // 最终没有返回false,就说明该位置可以填写true + } +} +``` + -----------------------
diff --git a/problems/0039.组合总和.md b/problems/0039.组合总和.md index 98b37b84..54e9f2e5 100644 --- a/problems/0039.组合总和.md +++ b/problems/0039.组合总和.md @@ -7,7 +7,7 @@ # 39. 组合总和 -[力扣题目链接](https://leetcode-cn.com/problems/combination-sum/) +[力扣题目链接](https://leetcode.cn/problems/combination-sum/) 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 @@ -291,7 +291,7 @@ class Solution: for i in range(start_index, len(candidates)): sum_ += candidates[i] self.path.append(candidates[i]) - self.backtracking(candidates, target, sum_, i) # 因为无限制重复选取,所以不是i-1 + self.backtracking(candidates, target, sum_, i) # 因为无限制重复选取,所以不是i+1 sum_ -= candidates[i] # 回溯 self.path.pop() # 回溯 ``` @@ -370,18 +370,17 @@ func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int) ```js var combinationSum = function(candidates, target) { const res = [], path = []; - candidates.sort(); // 排序 + candidates.sort((a,b)=>a-b); // 排序 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; + if(n > target - sum) break; path.push(n); sum += n; backtracking(i, sum); @@ -503,5 +502,35 @@ func combinationSum(_ candidates: [Int], _ target: Int) -> [[Int]] { } ``` +## Scala + +```scala +object Solution { + import scala.collection.mutable + def combinationSum(candidates: Array[Int], target: Int): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + + def backtracking(sum: Int, index: Int): Unit = { + if (sum == target) { + result.append(path.toList) // 如果正好等于target,就添加到结果集 + return + } + // 应该是从当前索引开始的,而不是从0 + // 剪枝优化:添加循环守卫,当sum + c(i) <= target的时候才循环,才可以进入下一次递归 + for (i <- index until candidates.size if sum + candidates(i) <= target) { + path.append(candidates(i)) + backtracking(sum + candidates(i), i) + path = path.take(path.size - 1) + } + } + + backtracking(0, 0) + result.toList + } +} +``` + + -----------------------
diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md index de13e031..557eb855 100644 --- a/problems/0040.组合总和II.md +++ b/problems/0040.组合总和II.md @@ -9,7 +9,7 @@ # 40.组合总和II -[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-ii/) +[力扣题目链接](https://leetcode.cn/problems/combination-sum-ii/) 给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 @@ -508,22 +508,27 @@ func backtracking(startIndex,sum,target int,candidates,trcak []int,res *[][]int) */ var combinationSum2 = function(candidates, target) { const res = []; path = [], len = candidates.length; - candidates.sort(); + candidates.sort((a,b)=>a-b); 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; + if(j > i && candidates[j] === candidates[j-1]){ + //若当前元素和前一个元素相等 + //则本次循环结束,防止出现重复组合 + continue; + } + //如果当前元素值大于目标值-总和的值 + //由于数组已排序,那么该元素之后的元素必定不满足条件 + //直接终止当前层的递归 + if(n > target - sum) break; path.push(n); sum += n; - f = n; backtracking(sum, j + 1); path.pop(); sum -= n; @@ -688,5 +693,37 @@ func combinationSum2(_ candidates: [Int], _ target: Int) -> [[Int]] { } ``` + +## Scala + +```scala +object Solution { + import scala.collection.mutable + def combinationSum2(candidates: Array[Int], target: Int): List[List[Int]] = { + var res = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + var candidate = candidates.sorted + + def backtracking(sum: Int, startIndex: Int): Unit = { + if (sum == target) { + res.append(path.toList) + return + } + + for (i <- startIndex until candidate.size if sum + candidate(i) <= target) { + if (!(i > startIndex && candidate(i) == candidate(i - 1))) { + path.append(candidate(i)) + backtracking(sum + candidate(i), i + 1) + path = path.take(path.size - 1) + } + } + } + + backtracking(0, 0) + res.toList + } +} +``` + -----------------------
diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index b232ce22..448d6d51 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -9,7 +9,7 @@ # 42. 接雨水 -[力扣题目链接](https://leetcode-cn.com/problems/trapping-rain-water/) +[力扣题目链接](https://leetcode.cn/problems/trapping-rain-water/) 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 @@ -640,8 +640,44 @@ func min(a,b int)int{ } ``` +单调栈解法 +```go +func trap(height []int) int { + if len(height) <= 2 { + return 0 + } + st := make([]int, 1, len(height)) // 切片模拟单调栈,st存储的是高度数组下标 + var res int + for i := 1; i < len(height); i++ { + if height[i] < height[st[len(st)-1]] { + st = append(st, i) + } else if height[i] == height[st[len(st)-1]] { + st = st[:len(st)-1] // 比较的新元素和栈顶的元素相等,去掉栈中的,入栈新元素下标 + st = append(st, i) + } else { + for len(st) != 0 && height[i] > height[st[len(st)-1]] { + top := st[len(st)-1] + st = st[:len(st)-1] + if len(st) != 0 { + tmp := (min(height[i], height[st[len(st)-1]]) - height[top]) * (i - st[len(st)-1] - 1) + res += tmp + } + } + st = append(st, i) + } + } + return res +} +func min(x, y int) int { + if x >= y { + return y + } + return x +} +``` + ### JavaScript: ```javascript @@ -744,6 +780,91 @@ var trap = function(height) { }; ``` +### TypeScript + +双指针法: + +```typescript +function trap(height: number[]): number { + const length: number = height.length; + let resVal: number = 0; + for (let i = 0; i < length; i++) { + let leftMaxHeight: number = height[i], + rightMaxHeight: number = height[i]; + let leftIndex: number = i - 1, + rightIndex: number = i + 1; + while (leftIndex >= 0) { + if (height[leftIndex] > leftMaxHeight) + leftMaxHeight = height[leftIndex]; + leftIndex--; + } + while (rightIndex < length) { + if (height[rightIndex] > rightMaxHeight) + rightMaxHeight = height[rightIndex]; + rightIndex++; + } + resVal += Math.min(leftMaxHeight, rightMaxHeight) - height[i]; + } + return resVal; +}; +``` + +动态规划: + +```typescript +function trap(height: number[]): number { + const length: number = height.length; + const leftMaxHeightDp: number[] = [], + rightMaxHeightDp: number[] = []; + leftMaxHeightDp[0] = height[0]; + rightMaxHeightDp[length - 1] = height[length - 1]; + for (let i = 1; i < length; i++) { + leftMaxHeightDp[i] = Math.max(height[i], leftMaxHeightDp[i - 1]); + } + for (let i = length - 2; i >= 0; i--) { + rightMaxHeightDp[i] = Math.max(height[i], rightMaxHeightDp[i + 1]); + } + let resVal: number = 0; + for (let i = 0; i < length; i++) { + resVal += Math.min(leftMaxHeightDp[i], rightMaxHeightDp[i]) - height[i]; + } + return resVal; +}; +``` + +单调栈: + +```typescript +function trap(height: number[]): number { + const length: number = height.length; + const stack: number[] = []; + stack.push(0); + let resVal: number = 0; + for (let i = 1; i < length; i++) { + let top = stack[stack.length - 1]; + if (height[top] > height[i]) { + stack.push(i); + } else if (height[top] === height[i]) { + stack.pop(); + stack.push(i); + } else { + while (stack.length > 0 && height[top] < height[i]) { + let mid = stack.pop(); + if (stack.length > 0) { + let left = stack[stack.length - 1]; + let h = Math.min(height[left], height[i]) - height[mid]; + let w = i - left - 1; + resVal += h * w; + top = stack[stack.length - 1]; + } + } + stack.push(i); + } + } + return resVal; +}; +``` + ### C: 一种更简便的双指针方法: diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index 4caff042..648355fd 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -9,7 +9,7 @@ # 45.跳跃游戏II -[力扣题目链接](https://leetcode-cn.com/problems/jump-game-ii/) +[力扣题目链接](https://leetcode.cn/problems/jump-game-ii/) 给定一个非负整数数组,你最初位于数组的第一个位置。 @@ -217,18 +217,26 @@ class Solution: ### Go ```Go func jump(nums []int) int { - dp:=make([]int ,len(nums)) - dp[0]=0 + dp := make([]int, len(nums)) + dp[0] = 0//初始第一格跳跃数一定为0 - for i:=1;ii{ - dp[i]=min(dp[j]+1,dp[i]) - } - } - } - return dp[len(nums)-1] + for i := 1; i < len(nums); i++ { + dp[i] = i + for j := 0; j < i; j++ { + if nums[j] + j >= i {//nums[j]为起点,j为往右跳的覆盖范围,这行表示从j能跳到i + dp[i] = min(dp[j] + 1, dp[i])//更新最小能到i的跳跃次数 + } + } + } + return dp[len(nums)-1] +} + +func min(a, b int) int { + if a < b { + return a + } else { + return b + } } ``` @@ -271,7 +279,31 @@ function jump(nums: number[]): number { }; ``` +### Scala +```scala +object Solution { + def jump(nums: Array[Int]): Int = { + if (nums.length == 0) return 0 + var result = 0 // 记录走的最大步数 + var curDistance = 0 // 当前覆盖最远距离下标 + var nextDistance = 0 // 下一步覆盖最远距离下标 + for (i <- nums.indices) { + nextDistance = math.max(nums(i) + i, nextDistance) // 更新下一步覆盖最远距离下标 + if (i == curDistance) { + if (curDistance != nums.length - 1) { + result += 1 + curDistance = nextDistance + if (nextDistance >= nums.length - 1) return result + } else { + return result + } + } + } + result + } +} +``` diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index 836c3646..ce07395a 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -7,7 +7,7 @@ # 46.全排列 -[力扣题目链接](https://leetcode-cn.com/problems/permutations/) +[力扣题目链接](https://leetcode.cn/problems/permutations/) 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 @@ -341,7 +341,7 @@ function permute(nums: number[]): number[][] { return resArr; function backTracking(nums: number[], route: number[]): void { if (route.length === nums.length) { - resArr.push(route.slice()); + resArr.push([...route]); return; } let tempVal: number; @@ -359,6 +359,36 @@ function permute(nums: number[]): number[][] { }; ``` +### Rust + +```Rust +impl Solution { + fn backtracking(result: &mut Vec>, path: &mut Vec, nums: &Vec, used: &mut Vec) { + let len = nums.len(); + if path.len() == len { + result.push(path.clone()); + return; + } + for i in 0..len { + if used[i] == true { continue; } + used[i] = true; + path.push(nums[i]); + Self::backtracking(result, path, nums, used); + path.pop(); + used[i] = false; + } + } + + pub fn permute(nums: Vec) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut path: Vec = Vec::new(); + let mut used = vec![false; nums.len()]; + Self::backtracking(&mut result, &mut path, &nums, &mut used); + result + } +} +``` + ### C ```c @@ -456,6 +486,36 @@ func permute(_ nums: [Int]) -> [[Int]] { } ``` +### Scala + +```scala +object Solution { + import scala.collection.mutable + def permute(nums: Array[Int]): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + + def backtracking(used: Array[Boolean]): Unit = { + if (path.size == nums.size) { + // 如果path的长度和nums相等,那么可以添加到结果集 + result.append(path.toList) + return + } + // 添加循环守卫,只有当当前数字没有用过的情况下才进入回溯 + for (i <- nums.indices if used(i) == false) { + used(i) = true + path.append(nums(i)) + backtracking(used) // 回溯 + path.remove(path.size - 1) + used(i) = false + } + } + + backtracking(new Array[Boolean](nums.size)) // 调用方法 + result.toList // 最终返回结果集的List形式 + } +} +``` -----------------------
diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index cce25cd9..88680c8c 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -8,7 +8,7 @@ ## 47.全排列 II -[力扣题目链接](https://leetcode-cn.com/problems/permutations-ii/) +[力扣题目链接](https://leetcode.cn/problems/permutations-ii/) 给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。 @@ -268,7 +268,7 @@ var permuteUnique = function (nums) { function backtracing( used) { if (path.length === nums.length) { - result.push(path.slice()) + result.push([...path]) return } for (let i = 0; i < nums.length; i++) { @@ -303,7 +303,7 @@ function permuteUnique(nums: number[]): number[][] { return resArr; function backTracking(nums: number[], route: number[]): void { if (route.length === nums.length) { - resArr.push(route.slice()); + resArr.push([...route]); return; } for (let i = 0, length = nums.length; i < length; i++) { @@ -422,5 +422,43 @@ int** permuteUnique(int* nums, int numsSize, int* returnSize, int** returnColumn } ``` +### Scala + +```scala +object Solution { + import scala.collection.mutable + def permuteUnique(nums: Array[Int]): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + var num = nums.sorted // 首先对数据进行排序 + + def backtracking(used: Array[Boolean]): Unit = { + if (path.size == num.size) { + // 如果path的size等于num了,那么可以添加到结果集 + result.append(path.toList) + return + } + // 循环守卫,当前元素没被使用过就进入循环体 + for (i <- num.indices if used(i) == false) { + // 当前索引为0,不存在和前一个数字相等可以进入回溯 + // 当前索引值和上一个索引不相等,可以回溯 + // 前一个索引对应的值没有被选,可以回溯 + // 因为Scala没有continue,只能将逻辑反过来写 + if (i == 0 || (i > 0 && num(i) != num(i - 1)) || used(i-1) == false) { + used(i) = true + path.append(num(i)) + backtracking(used) + path.remove(path.size - 1) + used(i) = false + } + } + } + + backtracking(new Array[Boolean](nums.length)) + result.toList + } +} +``` + -----------------------
diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index c03e48c2..9ae1f975 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -7,7 +7,7 @@ # 第51题. N皇后 -[力扣题目链接](https://leetcode-cn.com/problems/n-queens/) +[力扣题目链接](https://leetcode.cn/problems/n-queens/) n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 @@ -455,7 +455,7 @@ var solveNQueens = function(n) { }; ``` -## TypeScript +### TypeScript ```typescript function solveNQueens(n: number): string[][] { @@ -683,5 +683,77 @@ char *** solveNQueens(int n, int* returnSize, int** returnColumnSizes){ } ``` +### Scala + +```scala +object Solution { + import scala.collection.mutable + def solveNQueens(n: Int): List[List[String]] = { + var result = mutable.ListBuffer[List[String]]() + + def judge(x: Int, y: Int, maze: Array[Array[Boolean]]): Boolean = { + // 正上方 + var xx = x + while (xx >= 0) { + if (maze(xx)(y)) return false + xx -= 1 + } + // 左边 + var yy = y + while (yy >= 0) { + if (maze(x)(yy)) return false + yy -= 1 + } + // 左上方 + xx = x + yy = y + while (xx >= 0 && yy >= 0) { + if (maze(xx)(yy)) return false + xx -= 1 + yy -= 1 + } + xx = x + yy = y + // 右上方 + while (xx >= 0 && yy < n) { + if (maze(xx)(yy)) return false + xx -= 1 + yy += 1 + } + true + } + + def backtracking(row: Int, maze: Array[Array[Boolean]]): Unit = { + if (row == n) { + // 将结果转换为题目所需要的形式 + var path = mutable.ListBuffer[String]() + for (x <- maze) { + var tmp = mutable.ListBuffer[String]() + for (y <- x) { + if (y == true) tmp.append("Q") + else tmp.append(".") + } + path.append(tmp.mkString) + } + result.append(path.toList) + return + } + + for (j <- 0 until n) { + // 判断这个位置是否可以放置皇后 + if (judge(row, j, maze)) { + maze(row)(j) = true + backtracking(row + 1, maze) + maze(row)(j) = false + } + } + } + + backtracking(0, Array.ofDim[Boolean](n, n)) + result.toList + } +} +``` + -----------------------
diff --git a/problems/0052.N皇后II.md b/problems/0052.N皇后II.md index 67e439ca..d39e9f2a 100644 --- a/problems/0052.N皇后II.md +++ b/problems/0052.N皇后II.md @@ -8,7 +8,7 @@ # 52. N皇后II -题目链接:https://leetcode-cn.com/problems/n-queens-ii/ +题目链接:https://leetcode.cn/problems/n-queens-ii/ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。 @@ -44,7 +44,7 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并 # 思路 -想看:[51.N皇后](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg) ,基本没有区别 +详看:[51.N皇后](https://mp.weixin.qq.com/s/lU_QwCMj6g60nh8m98GAWg) ,基本没有区别 # C++代码 @@ -144,7 +144,61 @@ var totalNQueens = function(n) { }; ``` +TypeScript: + +```typescript +// 0-该格为空,1-该格有皇后 +type GridStatus = 0 | 1; +function totalNQueens(n: number): number { + let resCount: number = 0; + const chess: GridStatus[][] = new Array(n).fill(0) + .map(_ => new Array(n).fill(0)); + backTracking(chess, n, 0); + return resCount; + function backTracking(chess: GridStatus[][], n: number, startRowIndex: number): void { + if (startRowIndex === n) { + resCount++; + return; + } + for (let j = 0; j < n; j++) { + if (checkValid(chess, startRowIndex, j, n) === true) { + chess[startRowIndex][j] = 1; + backTracking(chess, n, startRowIndex + 1); + chess[startRowIndex][j] = 0; + } + } + } +}; +function checkValid(chess: GridStatus[][], i: number, j: number, n: number): boolean { + // 向上纵向检查 + let tempI: number = i - 1, + tempJ: number = j; + while (tempI >= 0) { + if (chess[tempI][tempJ] === 1) return false; + tempI--; + } + // 斜向左上检查 + tempI = i - 1; + tempJ = j - 1; + while (tempI >= 0 && tempJ >= 0) { + if (chess[tempI][tempJ] === 1) return false; + tempI--; + tempJ--; + } + // 斜向右上检查 + tempI = i - 1; + tempJ = j + 1; + while (tempI >= 0 && tempJ < n) { + if (chess[tempI][tempJ] === 1) return false; + tempI--; + tempJ++; + } + return true; +} +``` + C + ```c //path[i]为在i行,path[i]列上存在皇后 int *path; diff --git a/problems/0053.最大子序和.md b/problems/0053.最大子序和.md index d0680a3b..17b9d31e 100644 --- a/problems/0053.最大子序和.md +++ b/problems/0053.最大子序和.md @@ -7,7 +7,7 @@ # 53. 最大子序和 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-subarray/) +[力扣题目链接](https://leetcode.cn/problems/maximum-subarray/) 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 @@ -140,7 +140,7 @@ public: ## 其他语言版本 -### Java +### Java ```java class Solution { public int maxSubArray(int[] nums) { @@ -180,7 +180,7 @@ class Solution { } ``` -### Python +### Python ```python class Solution: def maxSubArray(self, nums: List[int]) -> int: @@ -195,7 +195,7 @@ class Solution: return result ``` -### Go +### Go ```go func maxSubArray(nums []int) int { @@ -212,6 +212,20 @@ func maxSubArray(nums []int) int { } ``` +### Rust +```rust +pub fn max_sub_array(nums: Vec) -> i32 { + let mut max_sum = i32::MIN; + let mut curr = 0; + for n in nums.iter() { + curr += n; + max_sum = max_sum.max(curr); + curr = curr.max(0); + } + max_sum +} +``` + ### Javascript: ```Javascript var maxSubArray = function(nums) { @@ -319,8 +333,41 @@ function maxSubArray(nums: number[]): number { }; ``` +### Scala +**贪心** +```scala +object Solution { + def maxSubArray(nums: Array[Int]): Int = { + var result = Int.MinValue + var count = 0 + for (i <- nums.indices) { + count += nums(i) // count累加 + if (count > result) result = count // 记录最大值 + if (count <= 0) count = 0 // 一旦count为负,则count归0 + } + result + } +} +``` + +**动态规划** + +```scala +object Solution { + def maxSubArray(nums: Array[Int]): Int = { + var dp = new Array[Int](nums.length) + var result = nums(0) + dp(0) = nums(0) + for (i <- 1 until nums.length) { + dp(i) = math.max(nums(i), dp(i - 1) + nums(i)) + result = math.max(result, dp(i)) // 更新最大值 + } + result + } +} +``` -----------------------
diff --git a/problems/0053.最大子序和(动态规划).md b/problems/0053.最大子序和(动态规划).md index 4c883cb6..228ceade 100644 --- a/problems/0053.最大子序和(动态规划).md +++ b/problems/0053.最大子序和(动态规划).md @@ -6,7 +6,7 @@ ## 53. 最大子序和 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-subarray/) +[力扣题目链接](https://leetcode.cn/problems/maximum-subarray/) 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 @@ -187,6 +187,41 @@ const maxSubArray = nums => { ``` +Scala: + +```scala +object Solution { + def maxSubArray(nums: Array[Int]): Int = { + var dp = new Array[Int](nums.length) + var result = nums(0) + dp(0) = nums(0) + for (i <- 1 until nums.length) { + dp(i) = math.max(nums(i), dp(i - 1) + nums(i)) + result = math.max(result, dp(i)) // 更新最大值 + } + result + } +} +``` + +TypeScript: + +```typescript +function maxSubArray(nums: number[]): number { + /** + dp[i]:以nums[i]结尾的最大和 + */ + const dp: number[] = [] + dp[0] = nums[0]; + let resMax: number = 0; + for (let i = 1; i < nums.length; i++) { + dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]); + resMax = Math.max(resMax, dp[i]); + } + return resMax; +}; +``` + -----------------------
diff --git a/problems/0054.螺旋矩阵.md b/problems/0054.螺旋矩阵.md index ccf6f471..8f2691fe 100644 --- a/problems/0054.螺旋矩阵.md +++ b/problems/0054.螺旋矩阵.md @@ -8,7 +8,7 @@ ## 54.螺旋矩阵 -[力扣题目链接](https://leetcode-cn.com/problems/spiral-matrix/) +[力扣题目链接](https://leetcode.cn/problems/spiral-matrix/) 给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。 @@ -128,8 +128,8 @@ public: ## 类似题目 -* [59.螺旋矩阵II](https://leetcode-cn.com/problems/spiral-matrix-ii/) -* [剑指Offer 29.顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) +* [59.螺旋矩阵II](https://leetcode.cn/problems/spiral-matrix-ii/) +* [剑指Offer 29.顺时针打印矩阵](https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/) ## 其他语言版本 Python: @@ -171,6 +171,30 @@ class Solution: return res ``` - +```python3 +class Solution: + def spiralOrder(self, matrix: List[List[int]]) -> List[int]: + r=len(matrix) + if r == 0 or len(matrix[0])==0: + return [] + c=len(matrix[0]) + res=matrix[0] + + if r>1: + for i in range (1,r): + res.append(matrix[i][c-1]) + for j in range(c-2, -1, -1): + res.append(matrix[r-1][j]) + if c>1: + for i in range(r-2, 0, -1): + res.append(matrix[i][0]) + + M=[] + for k in range(1, r-1): + e=matrix[k][1:-1] + M.append(e) + + return res+self.spiralOrder(M) +``` -----------------------
diff --git a/problems/0055.跳跃游戏.md b/problems/0055.跳跃游戏.md index 17a3b4f4..e7c4f5a2 100644 --- a/problems/0055.跳跃游戏.md +++ b/problems/0055.跳跃游戏.md @@ -7,7 +7,7 @@ # 55. 跳跃游戏 -[力扣题目链接](https://leetcode-cn.com/problems/jump-game/) +[力扣题目链接](https://leetcode.cn/problems/jump-game/) 给定一个非负整数数组,你最初位于数组的第一个位置。 @@ -193,7 +193,22 @@ function canJump(nums: number[]): boolean { }; ``` - +### Scala +```scala +object Solution { + def canJump(nums: Array[Int]): Boolean = { + var cover = 0 + if (nums.length == 1) return true // 如果只有一个元素,那么必定到达 + var i = 0 + while (i <= cover) { // i表示下标,当前只能够走cover步 + cover = math.max(i + nums(i), cover) + if (cover >= nums.length - 1) return true // 说明可以覆盖到终点,直接返回 + i += 1 + } + false // 如果上面没有返回就是跳不到 + } +} +``` ----------------------- diff --git a/problems/0056.合并区间.md b/problems/0056.合并区间.md index a9caeaf0..627ca5b1 100644 --- a/problems/0056.合并区间.md +++ b/problems/0056.合并区间.md @@ -7,7 +7,7 @@ # 56. 合并区间 -[力扣题目链接](https://leetcode-cn.com/problems/merge-intervals/) +[力扣题目链接](https://leetcode.cn/problems/merge-intervals/) 给出一个区间的集合,请合并所有重叠的区间。 @@ -22,9 +22,6 @@ * 解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。 * 注意:输入类型已于2019年4月15日更改。 请重置默认代码定义以获取新方法签名。 -提示: - -* intervals[i][0] <= intervals[i][1] ## 思路 @@ -96,7 +93,7 @@ public: vector> merge(vector>& intervals) { vector> result; if (intervals.size() == 0) return result; - // 排序的参数使用了lamda表达式 + // 排序的参数使用了lambda表达式 sort(intervals.begin(), intervals.end(), [](const vector& a, const vector& b){return a[0] < b[0];}); result.push_back(intervals[0]); @@ -112,8 +109,8 @@ public: }; ``` -* 时间复杂度:$O(n\log n)$ ,有一个快排 -* 空间复杂度:$O(1)$,我没有算result数组(返回值所需容器占的空间) +* 时间复杂度:O(nlog n) ,有一个快排 +* 空间复杂度:O(n),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间 ## 总结 @@ -136,24 +133,38 @@ public: ### Java ```java + +/** +时间复杂度 : O(NlogN) 排序需要O(NlogN) +空间复杂度 : O(logN) java 的内置排序是快速排序 需要 O(logN)空间 + +*/ class Solution { public int[][] merge(int[][] intervals) { List res = new LinkedList<>(); - Arrays.sort(intervals, (o1, o2) -> Integer.compare(o1[0], o2[0])); - + //按照左边界排序 + Arrays.sort(intervals, (x, y) -> Integer.compare(x[0], y[0])); + //initial start 是最小左边界 int start = intervals[0][0]; + int rightmostRightBound = intervals[0][1]; for (int i = 1; i < intervals.length; i++) { - if (intervals[i][0] > intervals[i - 1][1]) { - res.add(new int[]{start, intervals[i - 1][1]}); + //如果左边界大于最大右边界 + if (intervals[i][0] > rightmostRightBound) { + //加入区间 并且更新start + res.add(new int[]{start, rightmostRightBound}); start = intervals[i][0]; + rightmostRightBound = intervals[i][1]; } else { - intervals[i][1] = Math.max(intervals[i][1], intervals[i - 1][1]); + //更新最大右边界 + rightmostRightBound = Math.max(rightmostRightBound, intervals[i][1]); } } - res.add(new int[]{start, intervals[intervals.length - 1][1]}); + res.add(new int[]{start, rightmostRightBound}); return res.toArray(new int[res.size()][]); } } + +} ``` ```java // 版本2 @@ -266,7 +277,57 @@ var merge = function(intervals) { }; ``` +### TypeScript +```typescript +function merge(intervals: number[][]): number[][] { + const resArr: number[][] = []; + intervals.sort((a, b) => a[0] - b[0]); + resArr[0] = [...intervals[0]]; // 避免修改原intervals + for (let i = 1, length = intervals.length; i < length; i++) { + let interval: number[] = intervals[i]; + let last: number[] = resArr[resArr.length - 1]; + if (interval[0] <= last[1]) { + last[1] = Math.max(interval[1], last[1]); + } else { + resArr.push([...intervals[i]]); + } + } + return resArr; +}; +``` + +### Scala + +```scala +object Solution { + import scala.collection.mutable + def merge(intervals: Array[Array[Int]]): Array[Array[Int]] = { + var res = mutable.ArrayBuffer[Array[Int]]() + + // 排序 + var interval = intervals.sortWith((a, b) => { + a(0) < b(0) + }) + + var left = interval(0)(0) + var right = interval(0)(1) + + for (i <- 1 until interval.length) { + if (interval(i)(0) <= right) { + left = math.min(left, interval(i)(0)) + right = math.max(right, interval(i)(1)) + } else { + res.append(Array[Int](left, right)) + left = interval(i)(0) + right = interval(i)(1) + } + } + res.append(Array[Int](left, right)) + res.toArray // 返回res的Array形式 + } +} +``` -----------------------
diff --git a/problems/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md index a7b19a34..9690abb6 100644 --- a/problems/0059.螺旋矩阵II.md +++ b/problems/0059.螺旋矩阵II.md @@ -8,7 +8,7 @@ ## 59.螺旋矩阵II -[力扣题目链接](https://leetcode-cn.com/problems/spiral-matrix-ii/) +[力扣题目链接](https://leetcode.cn/problems/spiral-matrix-ii/) 给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。 @@ -24,13 +24,15 @@ ## 思路 +为了利于录友们理解,我特意录制了视频,[拿下螺旋矩阵,《代码随想录》第五题!](https://www.bilibili.com/video/BV1SL4y1N7mV),结合本篇文章一起看,效果更佳。 + 这道题目可以说在面试中出现频率较高的题目,**本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。** 要如何画出这个螺旋排列的正方形矩阵呢? 相信很多同学刚开始做这种题目的时候,上来就是一波判断猛如虎。 -结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里哪里有问题,改了那里这里又跑不起来了。 +结果运行的时候各种问题,然后开始各种修修补补,最后发现改了这里那里有问题,改了那里这里又跑不起来了。 大家还记得我们在这篇文章[数组:每次遇到二分法,都是一看就会,一写就废](https://programmercarl.com/0704.二分查找.html)中讲解了二分法,提到如果要写出正确的二分法一定要坚持**循环不变量原则**。 @@ -47,7 +49,7 @@ 可以发现这里的边界条件非常多,在一个循环中,如此多的边界条件,如果不按照固定规则来遍历,那就是**一进循环深似海,从此offer是路人**。 -这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开又闭的原则,这样这一圈才能按照统一的规则画下来。 +这里一圈下来,我们要画每四条边,这四条边怎么画,每画一条边都要坚持一致的左闭右开,或者左开右闭的原则,这样这一圈才能按照统一的规则画下来。 那么我按照左闭右开的原则,来画一圈,大家看一下: @@ -59,7 +61,7 @@ 一些同学做这道题目之所以一直写不好,代码越写越乱。 -就是因为在画每一条边的时候,一会左开又闭,一会左闭右闭,一会又来左闭右开,岂能不乱。 +就是因为在画每一条边的时候,一会左开右闭,一会左闭右闭,一会又来左闭右开,岂能不乱。 代码如下,已经详细注释了每一步的目的,可以看出while循环里判断的情况是很多的,代码里处理的原则也是统一的左闭右开。 @@ -74,7 +76,7 @@ public: int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理 int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2) int count = 1; // 用来给矩阵中每一个空格赋值 - int offset = 1; // 每一圈循环,需要控制每一条边遍历的长度 + int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位 int i,j; while (loop --) { i = startx; @@ -82,11 +84,11 @@ public: // 下面开始的四个for就是模拟转了一圈 // 模拟填充上行从左到右(左闭右开) - for (j = starty; j < starty + n - offset; j++) { + for (j = starty; j < n - offset; j++) { res[startx][j] = count++; } // 模拟填充右列从上到下(左闭右开) - for (i = startx; i < startx + n - offset; i++) { + for (i = startx; i < n - offset; i++) { res[i][j] = count++; } // 模拟填充下行从右到左(左闭右开) @@ -103,7 +105,7 @@ public: starty++; // offset 控制每一圈里每一条边遍历的长度 - offset += 2; + offset += 1; } // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值 @@ -130,57 +132,37 @@ Java: ```Java class Solution { public int[][] generateMatrix(int n) { + int loop = 0; // 控制循环次数 int[][] res = new int[n][n]; + int start = 0; // 每次循环的开始点(start, start) + int count = 1; // 定义填充数字 + int i, j; - // 循环次数 - int loop = n / 2; - - // 定义每次循环起始位置 - int startX = 0; - int startY = 0; - - // 定义偏移量 - int offset = 1; - - // 定义填充数字 - int count = 1; - - // 定义中间位置 - int mid = n / 2; - while (loop > 0) { - int i = startX; - int j = startY; - + while (loop++ < n / 2) { // 判断边界后,loop从1开始 // 模拟上侧从左到右 - for (; j startY; j--) { + for (; j >= loop; j--) { res[i][j] = count++; } // 模拟左侧从下到上 - for (; i > startX; i--) { + for (; i >= loop; i--) { res[i][j] = count++; } - - loop--; - - startX += 1; - startY += 1; - - offset += 2; + start++; } if (n % 2 == 1) { - res[mid][mid] = count; + res[start][start] = count; } return res; @@ -564,6 +546,82 @@ int** generateMatrix(int n, int* returnSize, int** returnColumnSizes){ return ans; } ``` +Scala: +```scala +object Solution { + def generateMatrix(n: Int): Array[Array[Int]] = { + var res = Array.ofDim[Int](n, n) // 定义一个n*n的二维矩阵 + var num = 1 // 标志当前到了哪个数字 + var i = 0 // 横坐标 + var j = 0 // 竖坐标 + + while (num <= n * n) { + // 向右:当j不越界,并且下一个要填的数字是空白时 + while (j < n && res(i)(j) == 0) { + res(i)(j) = num // 当前坐标等于num + num += 1 // num++ + j += 1 // 竖坐标+1 + } + i += 1 // 下移一行 + j -= 1 // 左移一列 + + // 剩下的都同上 + + // 向下 + while (i < n && res(i)(j) == 0) { + res(i)(j) = num + num += 1 + i += 1 + } + i -= 1 + j -= 1 + + // 向左 + while (j >= 0 && res(i)(j) == 0) { + res(i)(j) = num + num += 1 + j -= 1 + } + i -= 1 + j += 1 + + // 向上 + while (i >= 0 && res(i)(j) == 0) { + res(i)(j) = num + num += 1 + i -= 1 + } + i += 1 + j += 1 + } + res + } +} +``` +C#: +```csharp +public class Solution { + public int[][] GenerateMatrix(int n) { + int[][] answer = new int[n][]; + for(int i = 0; i < n; i++) + answer[i] = new int[n]; + int start = 0; + int end = n - 1; + int tmp = 1; + while(tmp < n * n) + { + for(int i = start; i < end; i++) answer[start][i] = tmp++; + for(int i = start; i < end; i++) answer[i][end] = tmp++; + for(int i = end; i > start; i--) answer[end][i] = tmp++; + for(int i = end; i > start; i--) answer[i][start] = tmp++; + start++; + end--; + } + if(n % 2 == 1) answer[n / 2][n / 2] = tmp; + return answer; + } +} +``` -----------------------
diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index 4a9af129..5790df69 100644 --- a/problems/0062.不同路径.md +++ b/problems/0062.不同路径.md @@ -6,7 +6,7 @@ # 62.不同路径 -[力扣题目链接](https://leetcode-cn.com/problems/unique-paths/) +[力扣题目链接](https://leetcode.cn/problems/unique-paths/) 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 @@ -273,7 +273,7 @@ public: return dp[m-1][n-1]; } -``` +``` ### Python ```python @@ -347,7 +347,59 @@ var uniquePaths = function(m, n) { }; ``` +### TypeScript + +```typescript +function uniquePaths(m: number, n: number): number { + /** + dp[i][j]: 到达(i, j)的路径数 + dp[0][*]: 1; + dp[*][0]: 1; + ... + dp[i][j]: dp[i - 1][j] + dp[i][j - 1]; + */ + const dp: number[][] = new Array(m).fill(0).map(_ => []); + 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]; +}; +``` + +### Rust + +```Rust +impl Solution { + pub fn unique_paths(m: i32, n: i32) -> i32 { + let m = m as usize; + let n = n as usize; + let mut dp = vec![vec![0; n]; m]; + for i in 0..m { + dp[i][0] = 1; + } + for j in 0..n { + dp[0][j] = 1; + } + for i in 1..m { + for j in 1..n { + dp[i][j] = dp[i-1][j] + dp[i][j-1]; + } + } + dp[m-1][n-1] + } +} +``` + ### C + ```c //初始化dp数组 int **initDP(int m, int n) { @@ -384,5 +436,21 @@ int uniquePaths(int m, int n){ } ``` +### Scala + +```scala +object Solution { + def uniquePaths(m: Int, n: Int): Int = { + var dp = Array.ofDim[Int](m, n) + for (i <- 0 until m) dp(i)(0) = 1 + for (j <- 1 until n) dp(0)(j) = 1 + for (i <- 1 until m; j <- 1 until n) { + dp(i)(j) = dp(i - 1)(j) + dp(i)(j - 1) + } + dp(m - 1)(n - 1) + } +} +``` + -----------------------
diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index a40cceda..c7326b63 100644 --- a/problems/0063.不同路径II.md +++ b/problems/0063.不同路径II.md @@ -6,7 +6,7 @@ # 63. 不同路径 II -[力扣题目链接](https://leetcode-cn.com/problems/unique-paths-ii/) +[力扣题目链接](https://leetcode.cn/problems/unique-paths-ii/) 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。 @@ -66,7 +66,7 @@ dp[i][j] :表示从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路 所以代码为: -``` +```cpp if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i][j] dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; } @@ -76,7 +76,7 @@ if (obstacleGrid[i][j] == 0) { // 当(i, j)没有障碍的时候,再推导dp[i 在[62.不同路径](https://programmercarl.com/0062.不同路径.html)不同路径中我们给出如下的初始化: -``` +```cpp vector> dp(m, vector(n, 0)); // 初始值为0 for (int i = 0; i < m; i++) dp[i][0] = 1; for (int j = 0; j < n; j++) dp[0][j] = 1; @@ -138,6 +138,8 @@ public: int uniquePathsWithObstacles(vector>& obstacleGrid) { int m = obstacleGrid.size(); int n = obstacleGrid[0].size(); + if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) //如果在起点或终点出现了障碍,直接返回0 + return 0; vector> dp(m, vector(n, 0)); for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) dp[i][0] = 1; for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) dp[0][j] = 1; @@ -352,7 +354,74 @@ var uniquePathsWithObstacles = function(obstacleGrid) { }; ``` -C +### TypeScript + +```typescript +function uniquePathsWithObstacles(obstacleGrid: number[][]): number { + /** + dp[i][j]: 到达(i, j)的路径数 + dp[0][*]: 用u表示第一个障碍物下标,则u之前为1,u之后(含u)为0 + dp[*][0]: 同上 + ... + dp[i][j]: obstacleGrid[i][j] === 1 ? 0 : dp[i-1][j] + dp[i][j-1]; + */ + const m: number = obstacleGrid.length; + const n: number = obstacleGrid[0].length; + const dp: number[][] = new Array(m).fill(0).map(_ => new 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++) { + if (obstacleGrid[i][j] === 1) continue; + dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; + } + } + return dp[m - 1][n - 1]; +}; +``` + +### Rust + +```Rust +impl Solution { + pub fn unique_paths_with_obstacles(obstacle_grid: Vec>) -> i32 { + let m: usize = obstacle_grid.len(); + let n: usize = obstacle_grid[0].len(); + if obstacle_grid[0][0] == 1 || obstacle_grid[m-1][n-1] == 1 { + return 0; + } + let mut dp = vec![vec![0; n]; m]; + for i in 0..m { + if obstacle_grid[i][0] == 1 { + break; + } + else { dp[i][0] = 1; } + } + for j in 0..n { + if obstacle_grid[0][j] == 1 { + break; + } + else { dp[0][j] = 1; } + } + for i in 1..m { + for j in 1..n { + if obstacle_grid[i][j] == 1 { + continue; + } + dp[i][j] = dp[i-1][j] + dp[i][j-1]; + } + } + dp[m-1][n-1] + } +} +``` + +### C + ```c //初始化dp数组 int **initDP(int m, int n, int** obstacleGrid) { @@ -407,5 +476,37 @@ int uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize, int* obst } ``` +### Scala + +```scala +object Solution { + import scala.util.control.Breaks._ + def uniquePathsWithObstacles(obstacleGrid: Array[Array[Int]]): Int = { + var (m, n) = (obstacleGrid.length, obstacleGrid(0).length) + var dp = Array.ofDim[Int](m, n) + + // 比如break、continue这些流程控制需要使用breakable + breakable( + for (i <- 0 until m) { + if (obstacleGrid(i)(0) != 1) dp(i)(0) = 1 + else break() + } + ) + breakable( + for (j <- 0 until n) { + if (obstacleGrid(0)(j) != 1) dp(0)(j) = 1 + else break() + } + ) + + for (i <- 1 until m; j <- 1 until n; if obstacleGrid(i)(j) != 1) { + dp(i)(j) = dp(i - 1)(j) + dp(i)(j - 1) + } + + dp(m - 1)(n - 1) + } +} +``` + -----------------------
diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index da19ea0e..30c3642f 100644 --- a/problems/0070.爬楼梯.md +++ b/problems/0070.爬楼梯.md @@ -5,7 +5,7 @@

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

# 70. 爬楼梯 -[力扣题目链接](https://leetcode-cn.com/problems/climbing-stairs/) +[力扣题目链接](https://leetcode.cn/problems/climbing-stairs/) 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 @@ -308,7 +308,58 @@ var climbStairs = function(n) { }; ``` +TypeScript + +> 爬2阶 + +```typescript +function climbStairs(n: number): number { + /** + dp[i]: i阶楼梯的方法种数 + dp[1]: 1; + dp[2]: 2; + ... + dp[i]: dp[i - 1] + dp[i - 2]; + */ + const dp: number[] = []; + dp[1] = 1; + dp[2] = 2; + for (let i = 3; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +}; +``` + +> 爬m阶 + +```typescript +function climbStairs(n: number): number { + /** + 一次可以爬m阶 + dp[i]: i阶楼梯的方法种数 + dp[1]: 1; + dp[2]: 2; + dp[3]: dp[2] + dp[1]; + ... + dp[i]: dp[i - 1] + dp[i - 2] + ... + dp[max(i - m, 1)]; 从i-1加到max(i-m, 1) + */ + const m: number = 2; // 本题m为2 + const dp: number[] = new Array(n + 1).fill(0); + dp[1] = 1; + dp[2] = 2; + for (let i = 3; i <= n; i++) { + const end: number = Math.max(i - m, 1); + for (let j = i - 1; j >= end; j--) { + dp[i] += dp[j]; + } + } + return dp[n]; +}; +``` + ### C + ```c int climbStairs(int n){ //若n<=2,返回n @@ -350,6 +401,38 @@ int climbStairs(int n){ } ``` +### Scala + +```scala +object Solution { + def climbStairs(n: Int): Int = { + if (n <= 2) return n + var dp = new Array[Int](n + 1) + dp(1) = 1 + dp(2) = 2 + for (i <- 3 to n) { + dp(i) = dp(i - 1) + dp(i - 2) + } + dp(n) + } +} +``` + +优化空间复杂度: +```scala +object Solution { + def climbStairs(n: Int): Int = { + if (n <= 2) return n + var (a, b) = (1, 2) + for (i <- 3 to n) { + var tmp = a + b + a = b + b = tmp + } + b // 最终返回b + } +} +``` -----------------------
diff --git a/problems/0070.爬楼梯完全背包版本.md b/problems/0070.爬楼梯完全背包版本.md index 2286de2d..ec019e57 100644 --- a/problems/0070.爬楼梯完全背包版本.md +++ b/problems/0070.爬楼梯完全背包版本.md @@ -11,7 +11,7 @@ ## 70. 爬楼梯 -[力扣题目链接](https://leetcode-cn.com/problems/climbing-stairs/) +[力扣题目链接](https://leetcode.cn/problems/climbing-stairs/) 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 @@ -199,6 +199,28 @@ var climbStairs = function(n) { }; ``` +TypeScript: + +```typescript +function climbStairs(n: number): number { + const m: number = 2; // 本题m为2 + const dp: number[] = new Array(n + 1).fill(0); + dp[0] = 1; + // 遍历背包 + for (let i = 1; i <= n; i++) { + // 遍历物品 + for (let j = 1; j <= m; j++) { + if (j <= i) { + dp[i] += dp[i - j]; + } + } + } + return dp[n]; +}; +``` + + + -----------------------
diff --git a/problems/0072.编辑距离.md b/problems/0072.编辑距离.md index 3802c228..641d3128 100644 --- a/problems/0072.编辑距离.md +++ b/problems/0072.编辑距离.md @@ -6,7 +6,7 @@ ## 72. 编辑距离 -[力扣题目链接](https://leetcode-cn.com/problems/edit-distance/) +[力扣题目链接](https://leetcode.cn/problems/edit-distance/) 给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 @@ -327,5 +327,42 @@ const minDistance = (word1, word2) => { }; ``` +TypeScript: + +```typescript +function minDistance(word1: string, word2: string): number { + /** + dp[i][j]: word1前i个字符,word2前j个字符,最少操作数 + dp[0][0]=0:表示word1前0个字符为'', word2前0个字符为'' + */ + const length1: number = word1.length, + length2: number = word2.length; + const dp: number[][] = new Array(length1 + 1).fill(0) + .map(_ => new Array(length2 + 1).fill(0)); + for (let i = 0; i <= length1; i++) { + dp[i][0] = i; + } + for (let i = 0; i <= length2; i++) { + dp[0][i] = i; + } + for (let i = 1; i <= length1; i++) { + for (let j = 1; j <= length2; j++) { + if (word1[i - 1] === word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min( + dp[i - 1][j], + dp[i][j - 1], + dp[i - 1][j - 1] + ) + 1; + } + } + } + return dp[length1][length2]; +}; +``` + + + -----------------------
diff --git a/problems/0077.组合.md b/problems/0077.组合.md index 4560c5b7..5a4811ba 100644 --- a/problems/0077.组合.md +++ b/problems/0077.组合.md @@ -9,7 +9,7 @@ # 第77题. 组合 -[力扣题目链接](https://leetcode-cn.com/problems/combinations/ ) +[力扣题目链接](https://leetcode.cn/problems/combinations/ ) 给定两个整数 n 和 k,返回 1 ... n 中所有可能的 k 个数的组合。 @@ -27,7 +27,7 @@ 也可以直接看我的B站视频:[带你学透回溯算法-组合问题(对应力扣题目:77.组合)](https://www.bilibili.com/video/BV1ti4y1L7cv#reply3733925949) -# 思路 +## 思路 本题这是回溯法的经典题目。 @@ -232,7 +232,7 @@ void backtracking(参数) { **对比一下本题的代码,是不是发现有点像!** 所以有了这个模板,就有解题的大体方向,不至于毫无头绪。 -# 总结 +## 总结 组合问题是回溯法解决的经典问题,我们开始的时候给大家列举一个很形象的例子,就是n为100,k为50的话,直接想法就需要50层for循环。 @@ -242,7 +242,7 @@ void backtracking(参数) { 接着用回溯法三部曲,逐步分析了函数参数、终止条件和单层搜索的过程。 -# 剪枝优化 +## 剪枝优化 我们说过,回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。 @@ -324,7 +324,7 @@ public: }; ``` -# 剪枝总结 +## 剪枝总结 本篇我们准对求组合问题的回溯法代码做了剪枝优化,这个优化如果不画图的话,其实不好理解,也不好讲清楚。 @@ -334,10 +334,10 @@ public: -# 其他语言版本 +## 其他语言版本 -## Java: +### Java: ```java class Solution { List> result = new ArrayList<>(); @@ -366,6 +366,8 @@ class Solution { } ``` +### Python + Python2: ```python class Solution(object): @@ -395,7 +397,6 @@ class Solution(object): return result ``` -## Python ```python class Solution: def combine(self, n: int, k: int) -> List[List[int]]: @@ -432,7 +433,7 @@ class Solution: ``` -## javascript +### javascript 剪枝: ```javascript @@ -456,7 +457,7 @@ const combineHelper = (n, k, startIndex) => { } ``` -## TypeScript +### TypeScript ```typescript function combine(n: number, k: number): number[][] { @@ -479,7 +480,7 @@ function combine(n: number, k: number): number[][] { -## Go +### Go ```Go var res [][]int func combine(n int, k int) [][]int { @@ -534,7 +535,57 @@ func backtrack(n,k,start int,track []int){ } ``` -## C +### Rust + +```Rust +impl Solution { + fn backtracking(result: &mut Vec>, path: &mut Vec, n: i32, k: i32, startIndex: i32) { + let len= path.len() as i32; + if len == k{ + result.push(path.to_vec()); + return; + } + for i in startIndex..= n { + path.push(i); + Self::backtracking(result, path, n, k, i+1); + path.pop(); + } + } + pub fn combine(n: i32, k: i32) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut path: Vec = Vec::new(); + Self::backtracking(&mut result, &mut path, n, k, 1); + result + } +} +``` + +剪枝 +```Rust +impl Solution { + fn backtracking(result: &mut Vec>, path: &mut Vec, n: i32, k: i32, startIndex: i32) { + let len= path.len() as i32; + if len == k{ + result.push(path.to_vec()); + return; + } + // 此处剪枝 + for i in startIndex..= n - (k - len) + 1 { + path.push(i); + Self::backtracking(result, path, n, k, i+1); + path.pop(); + } + } + pub fn combine(n: i32, k: i32) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut path: Vec = Vec::new(); + Self::backtracking(&mut result, &mut path, n, k, 1); + result + } +} +``` + +### C ```c int* path; int pathTop; @@ -642,7 +693,7 @@ int** combine(int n, int k, int* returnSize, int** returnColumnSizes){ } ``` -## Swift +### Swift ```swift func combine(_ n: Int, _ k: Int) -> [[Int]] { @@ -672,5 +723,63 @@ func combine(_ n: Int, _ k: Int) -> [[Int]] { } ``` +### Scala + +暴力: +```scala +object Solution { + import scala.collection.mutable // 导包 + def combine(n: Int, k: Int): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() // 存放结果集 + var path = mutable.ListBuffer[Int]() //存放符合条件的结果 + + def backtracking(n: Int, k: Int, startIndex: Int): Unit = { + if (path.size == k) { + // 如果path的size == k就达到题目要求,添加到结果集,并返回 + result.append(path.toList) + return + } + for (i <- startIndex to n) { // 遍历从startIndex到n + path.append(i) // 先把数字添加进去 + backtracking(n, k, i + 1) // 进行下一步回溯 + path = path.take(path.size - 1) // 回溯完再删除掉刚刚添加的数字 + } + } + + backtracking(n, k, 1) // 执行回溯 + result.toList // 最终返回result的List形式,return关键字可以省略 + } +} +``` + +剪枝: + +```scala +object Solution { + import scala.collection.mutable // 导包 + def combine(n: Int, k: Int): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() // 存放结果集 + var path = mutable.ListBuffer[Int]() //存放符合条件的结果 + + def backtracking(n: Int, k: Int, startIndex: Int): Unit = { + if (path.size == k) { + // 如果path的size == k就达到题目要求,添加到结果集,并返回 + result.append(path.toList) + return + } + // 剪枝优化 + for (i <- startIndex to (n - (k - path.size) + 1)) { + path.append(i) // 先把数字添加进去 + backtracking(n, k, i + 1) // 进行下一步回溯 + path = path.take(path.size - 1) // 回溯完再删除掉刚刚添加的数字 + } + } + + backtracking(n, k, 1) // 执行回溯 + result.toList // 最终返回result的List形式,return关键字可以省略 + } +} +``` + -----------------------
diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md index 94608ec1..e336fb75 100644 --- a/problems/0077.组合优化.md +++ b/problems/0077.组合优化.md @@ -14,7 +14,7 @@ 文中的回溯法是可以剪枝优化的,本篇我们继续来看一下题目77. 组合。 -链接:https://leetcode-cn.com/problems/combinations/ +链接:https://leetcode.cn/problems/combinations/ **看本篇之前,需要先看[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)**。 @@ -261,6 +261,32 @@ function combine(n: number, k: number): number[][] { }; ``` +Rust: + +```Rust +impl Solution { + fn backtracking(result: &mut Vec>, path: &mut Vec, n: i32, k: i32, startIndex: i32) { + let len= path.len() as i32; + if len == k{ + result.push(path.to_vec()); + return; + } + // 此处剪枝 + for i in startIndex..= n - (k - len) + 1 { + path.push(i); + Self::backtracking(result, path, n, k, i+1); + path.pop(); + } + } + pub fn combine(n: i32, k: i32) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut path: Vec = Vec::new(); + Self::backtracking(&mut result, &mut path, n, k, 1); + result + } +} +``` + C: ```c @@ -346,5 +372,34 @@ func combine(_ n: Int, _ k: Int) -> [[Int]] { } ``` +Scala: + +```scala +object Solution { + import scala.collection.mutable // 导包 + def combine(n: Int, k: Int): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() // 存放结果集 + var path = mutable.ListBuffer[Int]() //存放符合条件的结果 + + def backtracking(n: Int, k: Int, startIndex: Int): Unit = { + if (path.size == k) { + // 如果path的size == k就达到题目要求,添加到结果集,并返回 + result.append(path.toList) + return + } + // 剪枝优化 + for (i <- startIndex to (n - (k - path.size) + 1)) { + path.append(i) // 先把数字添加进去 + backtracking(n, k, i + 1) // 进行下一步回溯 + path = path.take(path.size - 1) // 回溯完再删除掉刚刚添加的数字 + } + } + + backtracking(n, k, 1) // 执行回溯 + result.toList // 最终返回result的List形式,return关键字可以省略 + } +} +``` + -----------------------
diff --git a/problems/0078.子集.md b/problems/0078.子集.md index e1c52b5b..e6cc668b 100644 --- a/problems/0078.子集.md +++ b/problems/0078.子集.md @@ -7,7 +7,7 @@ # 78.子集 -[力扣题目链接](https://leetcode-cn.com/problems/subsets/) +[力扣题目链接](https://leetcode.cn/problems/subsets/) 给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 @@ -260,7 +260,7 @@ var subsets = function(nums) { let result = [] let path = [] function backtracking(startIndex) { - result.push(path.slice()) + result.push([...path]) for(let i = startIndex; i < nums.length; i++) { path.push(nums[i]) backtracking(i + 1) @@ -280,7 +280,7 @@ function subsets(nums: number[]): number[][] { backTracking(nums, 0, []); return resArr; function backTracking(nums: number[], startIndex: number, route: number[]): void { - resArr.push(route.slice()); + resArr.push([...route]); let length = nums.length; if (startIndex === length) return; for (let i = startIndex; i < length; i++) { @@ -373,6 +373,60 @@ func subsets(_ nums: [Int]) -> [[Int]] { } ``` +## Scala + +思路一: 使用本题解思路 + +```scala +object Solution { + import scala.collection.mutable + def subsets(nums: Array[Int]): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + + def backtracking(startIndex: Int): Unit = { + result.append(path.toList) // 存放结果 + if (startIndex >= nums.size) { + return + } + for (i <- startIndex until nums.size) { + path.append(nums(i)) // 添加元素 + backtracking(i + 1) + path.remove(path.size - 1) // 删除 + } + } + + backtracking(0) + result.toList + } +} +``` + +思路二: 将原问题转换为二叉树,针对每一个元素都有**选或不选**两种选择,直到遍历到最后,所有的叶子节点即为本题的答案: + +```scala +object Solution { + import scala.collection.mutable + def subsets(nums: Array[Int]): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + + def backtracking(path: mutable.ListBuffer[Int], startIndex: Int): Unit = { + if (startIndex == nums.length) { + result.append(path.toList) + return + } + path.append(nums(startIndex)) + backtracking(path, startIndex + 1) // 选择元素 + path.remove(path.size - 1) + backtracking(path, startIndex + 1) // 不选择元素 + } + + backtracking(mutable.ListBuffer[Int](), 0) + result.toList + } +} +``` + -----------------------
diff --git a/problems/0084.柱状图中最大的矩形.md b/problems/0084.柱状图中最大的矩形.md index 439a3bc5..e085e455 100644 --- a/problems/0084.柱状图中最大的矩形.md +++ b/problems/0084.柱状图中最大的矩形.md @@ -7,7 +7,7 @@ # 84.柱状图中最大的矩形 -[力扣题目链接](https://leetcode-cn.com/problems/largest-rectangle-in-histogram/) +[力扣题目链接](https://leetcode.cn/problems/largest-rectangle-in-histogram/) 给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。 @@ -486,5 +486,95 @@ var largestRectangleArea = function(heights) { return maxArea; }; ``` +TypeScript: + +> 双指针法(会超时): + +```typescript +function largestRectangleArea(heights: number[]): number { + let resMax: number = 0; + for (let i = 0, length = heights.length; i < length; i++) { + // 左开右开 + let left: number = i - 1, + right: number = i + 1; + while (left >= 0 && heights[left] >= heights[i]) { + left--; + } + while (right < length && heights[right] >= heights[i]) { + right++; + } + resMax = Math.max(resMax, heights[i] * (right - left - 1)); + } + return resMax; +}; +``` + +> 动态规划预处理: + +```typescript +function largestRectangleArea(heights: number[]): number { + const length: number = heights.length; + const leftHeightDp: number[] = [], + rightHeightDp: number[] = []; + leftHeightDp[0] = -1; + rightHeightDp[length - 1] = length; + for (let i = 1; i < length; i++) { + let j = i - 1; + while (j >= 0 && heights[i] <= heights[j]) { + j = leftHeightDp[j]; + } + leftHeightDp[i] = j; + } + for (let i = length - 2; i >= 0; i--) { + let j = i + 1; + while (j < length && heights[i] <= heights[j]) { + j = rightHeightDp[j]; + } + rightHeightDp[i] = j; + } + let resMax: number = 0; + for (let i = 0; i < length; i++) { + let area = heights[i] * (rightHeightDp[i] - leftHeightDp[i] - 1); + resMax = Math.max(resMax, area); + } + return resMax; +}; +``` + +> 单调栈: + +```typescript +function largestRectangleArea(heights: number[]): number { + heights.push(0); + const length: number = heights.length; + // 栈底->栈顶:严格单调递增 + const stack: number[] = []; + stack.push(0); + let resMax: number = 0; + for (let i = 1; i < length; i++) { + let top = stack[stack.length - 1]; + if (heights[top] < heights[i]) { + stack.push(i); + } else if (heights[top] === heights[i]) { + stack.pop(); + stack.push(i); + } else { + while (stack.length > 0 && heights[top] > heights[i]) { + let mid = stack.pop(); + let left = stack.length > 0 ? stack[stack.length - 1] : -1; + let w = i - left - 1; + let h = heights[mid]; + resMax = Math.max(resMax, w * h); + top = stack[stack.length - 1]; + } + stack.push(i); + } + } + return resMax; +}; +``` + + + -----------------------
diff --git a/problems/0090.子集II.md b/problems/0090.子集II.md index 74ce000b..e85ec66d 100644 --- a/problems/0090.子集II.md +++ b/problems/0090.子集II.md @@ -8,7 +8,7 @@ ## 90.子集II -[力扣题目链接](https://leetcode-cn.com/problems/subsets-ii/) +[力扣题目链接](https://leetcode.cn/problems/subsets-ii/) 给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 @@ -299,7 +299,7 @@ var subsetsWithDup = function(nums) { return a - b }) function backtracing(startIndex, sortNums) { - result.push(path.slice(0)) + result.push([...path]) if(startIndex > nums.length - 1) { return } @@ -327,7 +327,7 @@ function subsetsWithDup(nums: number[]): number[][] { backTraking(nums, 0, []); return resArr; function backTraking(nums: number[], startIndex: number, route: number[]): void { - resArr.push(route.slice()); + resArr.push([...route]); let length: number = nums.length; if (startIndex === length) return; for (let i = startIndex; i < length; i++) { @@ -434,6 +434,63 @@ func subsetsWithDup(_ nums: [Int]) -> [[Int]] { } ``` +### Scala + +不使用userd数组: + +```scala +object Solution { + import scala.collection.mutable + def subsetsWithDup(nums: Array[Int]): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + var num = nums.sorted // 排序 + + def backtracking(startIndex: Int): Unit = { + result.append(path.toList) + if (startIndex >= num.size){ + return + } + for (i <- startIndex until num.size) { + // 同一树层重复的元素不进入回溯 + if (!(i > startIndex && num(i) == num(i - 1))) { + path.append(num(i)) + backtracking(i + 1) + path.remove(path.size - 1) + } + } + } + + backtracking(0) + result.toList + } +} +``` + +使用Set去重: +```scala +object Solution { + import scala.collection.mutable + def subsetsWithDup(nums: Array[Int]): List[List[Int]] = { + var result = mutable.Set[List[Int]]() + var num = nums.sorted + def backtracking(path: mutable.ListBuffer[Int], startIndex: Int): Unit = { + if (startIndex == num.length) { + result.add(path.toList) + return + } + path.append(num(startIndex)) + backtracking(path, startIndex + 1) // 选择 + path.remove(path.size - 1) + backtracking(path, startIndex + 1) // 不选择 + } + + backtracking(mutable.ListBuffer[Int](), 0) + + result.toList + } +} +``` -----------------------
diff --git a/problems/0093.复原IP地址.md b/problems/0093.复原IP地址.md index 6401824b..11ca2d03 100644 --- a/problems/0093.复原IP地址.md +++ b/problems/0093.复原IP地址.md @@ -8,7 +8,7 @@ # 93.复原IP地址 -[力扣题目链接](https://leetcode-cn.com/problems/restore-ip-addresses/) +[力扣题目链接](https://leetcode.cn/problems/restore-ip-addresses/) 给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。 @@ -444,7 +444,7 @@ var restoreIpAddresses = function(s) { return; } for(let j = i; j < s.length; j++) { - const str = s.substr(i, j - i + 1); + const str = s.slice(i, j + 1); if(str.length > 3 || +str > 255) break; if(str.length > 1 && str[0] === "0") break; path.push(str); @@ -659,6 +659,48 @@ func restoreIpAddresses(_ s: String) -> [String] { } ``` +## Scala + +```scala +object Solution { + import scala.collection.mutable + def restoreIpAddresses(s: String): List[String] = { + var result = mutable.ListBuffer[String]() + if (s.size < 4 || s.length > 12) return result.toList + var path = mutable.ListBuffer[String]() + + // 判断IP中的一个字段是否为正确的 + def isIP(sub: String): Boolean = { + if (sub.size > 1 && sub(0) == '0') return false + if (sub.toInt > 255) return false + true + } + + def backtracking(startIndex: Int): Unit = { + if (startIndex >= s.size) { + if (path.size == 4) { + result.append(path.mkString(".")) // mkString方法可以把集合里的数据以指定字符串拼接 + return + } + return + } + // subString + for (i <- startIndex until startIndex + 3 if i < s.size) { + var subString = s.substring(startIndex, i + 1) + if (isIP(subString)) { // 如果合法则进行下一轮 + path.append(subString) + backtracking(i + 1) + path = path.take(path.size - 1) + } + } + } + + backtracking(0) + result.toList + } +} +``` + -----------------------
diff --git a/problems/0096.不同的二叉搜索树.md b/problems/0096.不同的二叉搜索树.md index 41fcb8fe..9adc0457 100644 --- a/problems/0096.不同的二叉搜索树.md +++ b/problems/0096.不同的二叉搜索树.md @@ -6,7 +6,7 @@ # 96.不同的二叉搜索树 -[力扣题目链接](https://leetcode-cn.com/problems/unique-binary-search-trees/) +[力扣题目链接](https://leetcode.cn/problems/unique-binary-search-trees/) 给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? @@ -227,7 +227,33 @@ const numTrees =(n) => { }; ``` -C: +### TypeScript + +```typescript +function numTrees(n: number): number { + /** + dp[i]: i个节点对应的种树 + dp[0]: -1; 无意义; + dp[1]: 1; + ... + dp[i]: 2 * dp[i - 1] + + (dp[1] * dp[i - 2] + dp[2] * dp[i - 3] + ... + dp[i - 2] * dp[1]); 从1加到i-2 + */ + const dp: number[] = []; + dp[0] = -1; // 表示无意义 + dp[1] = 1; + for (let i = 2; i <= n; i++) { + dp[i] = 2 * dp[i - 1]; + for (let j = 1, end = i - 1; j < end; j++) { + dp[i] += dp[j] * dp[end - j]; + } + } + return dp[n]; +}; +``` + +### C + ```c //开辟dp数组 int *initDP(int n) { @@ -256,5 +282,22 @@ int numTrees(int n){ } ``` +### Scala + +```scala +object Solution { + def numTrees(n: Int): Int = { + var dp = new Array[Int](n + 1) + dp(0) = 1 + for (i <- 1 to n) { + for (j <- 1 to i) { + dp(i) += dp(j - 1) * dp(i - j) + } + } + dp(n) + } +} +``` + -----------------------
diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md index a8f3c324..cba450e5 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -7,7 +7,7 @@ # 98.验证二叉搜索树 -[力扣题目链接](https://leetcode-cn.com/problems/validate-binary-search-tree/) +[力扣题目链接](https://leetcode.cn/problems/validate-binary-search-tree/) 给定一个二叉树,判断其是否是一个有效的二叉搜索树。 @@ -589,7 +589,50 @@ function isValidBST(root: TreeNode | null): boolean { }; ``` +## Scala +辅助数组解决: +```scala +object Solution { + import scala.collection.mutable + def isValidBST(root: TreeNode): Boolean = { + var arr = new mutable.ArrayBuffer[Int]() + // 递归中序遍历二叉树,将节点添加到arr + def traversal(node: TreeNode): Unit = { + if (node == null) return + traversal(node.left) + arr.append(node.value) + traversal(node.right) + } + traversal(root) + // 这个数组如果是升序就代表是二叉搜索树 + for (i <- 1 until arr.size) { + if (arr(i) <= arr(i - 1)) return false + } + true + } +} +``` + +递归中解决: +```scala +object Solution { + def isValidBST(root: TreeNode): Boolean = { + var flag = true + var preValue:Long = Long.MinValue // 这里要使用Long类型 + + def traversal(node: TreeNode): Unit = { + if (node == null || flag == false) return + traversal(node.left) + if (node.value > preValue) preValue = node.value + else flag = false + traversal(node.right) + } + traversal(root) + flag + } +} +``` -----------------------
diff --git a/problems/0100.相同的树.md b/problems/0100.相同的树.md index 5e805d01..210418f7 100644 --- a/problems/0100.相同的树.md +++ b/problems/0100.相同的树.md @@ -8,7 +8,7 @@ # 100. 相同的树 -[力扣题目链接](https://leetcode-cn.com/problems/same-tree/) +[力扣题目链接](https://leetcode.cn/problems/same-tree/) 给定两个二叉树,编写一个函数来检验它们是否相同。 @@ -240,6 +240,46 @@ Go: JavaScript: +TypeScript: + +> 递归法-先序遍历 + +```typescript +function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean { + if (p === null && q === null) return true; + if (p === null || q === null) return false; + if (p.val !== q.val) return false; + return isSameTree(p.left, q.left) && isSameTree(p.right, q.right); +}; +``` + +> 迭代法-层序遍历 + +```typescript +function isSameTree(p: TreeNode | null, q: TreeNode | null): boolean { + const queue1: (TreeNode | null)[] = [], + queue2: (TreeNode | null)[] = []; + queue1.push(p); + queue2.push(q); + while (queue1.length > 0 && queue2.length > 0) { + const node1 = queue1.shift(), + node2 = queue2.shift(); + if (node1 === null && node2 === null) continue; + if ( + (node1 === null || node2 === null) || + node1!.val !== node2!.val + ) return false; + queue1.push(node1!.left); + queue1.push(node1!.right); + queue2.push(node2!.left); + queue2.push(node2!.right); + } + return true; +}; +``` + + + -----------------------
diff --git a/problems/0101.对称二叉树.md b/problems/0101.对称二叉树.md index e4e232c8..fd2d1987 100644 --- a/problems/0101.对称二叉树.md +++ b/problems/0101.对称二叉树.md @@ -7,7 +7,7 @@ # 101. 对称二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/symmetric-tree/) +[力扣题目链接](https://leetcode.cn/problems/symmetric-tree/) 给定一个二叉树,检查它是否是镜像对称的。 @@ -238,7 +238,7 @@ public: }; ``` -# 总结 +## 总结 这次我们又深度剖析了一道二叉树的“简单题”,大家会发现,真正的把题目搞清楚其实并不简单,leetcode上accept了和真正掌握了还是有距离的。 @@ -248,7 +248,7 @@ public: 如果已经做过这道题目的同学,读完文章可以再去看看这道题目,思考一下,会有不一样的发现! -# 相关题目推荐 +## 相关题目推荐 这两道题目基本和本题是一样的,只要稍加修改就可以AC。 @@ -437,6 +437,31 @@ class Solution: return True ``` +层次遍历 +```python +class Solution: + def isSymmetric(self, root: Optional[TreeNode]) -> bool: + if not root: + return True + + que = [root] + while que: + this_level_length = len(que) + for i in range(this_level_length // 2): + # 要么其中一个是None但另外一个不是 + if (not que[i] and que[this_level_length - 1 - i]) or (que[i] and not que[this_level_length - 1 - i]): + return False + # 要么两个都不是None + if que[i] and que[i].val != que[this_level_length - 1 - i].val: + return False + for i in range(this_level_length): + if not que[i]: continue + que.append(que[i].left) + que.append(que[i].right) + que = que[this_level_length:] + return True +``` + ## Go ```go @@ -725,5 +750,25 @@ func isSymmetric3(_ root: TreeNode?) -> Bool { } ``` +## Scala + +递归: +```scala +object Solution { + def isSymmetric(root: TreeNode): Boolean = { + if (root == null) return true // 如果等于空直接返回true + def compare(left: TreeNode, right: TreeNode): Boolean = { + if (left == null && right == null) return true // 如果左右都为空,则为true + if (left == null && right != null) return false // 如果左空右不空,不对称,返回false + if (left != null && right == null) return false // 如果左不空右空,不对称,返回false + // 如果左右的值相等,并且往下递归 + left.value == right.value && compare(left.left, right.right) && compare(left.right, right.left) + } + // 分别比较左子树和右子树 + compare(root.left, root.right) + } +} +``` + -----------------------
diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index ab8f2e57..5ef23944 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -26,7 +26,7 @@ # 102.二叉树的层序遍历 -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-level-order-traversal/) 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 @@ -82,6 +82,26 @@ public: } }; ``` +```CPP +# 递归法 +class Solution { +public: + void order(TreeNode* cur, vector>& result, int depth) + { + if (cur == nullptr) return; + if (result.size() == depth) result.push_back(vector()); + result[depth].push_back(cur->val); + order(cur->left, result, depth + 1); + order(cur->right, result, depth + 1); + } + vector> levelOrder(TreeNode* root) { + vector> result; + int depth = 0; + order(root, result, depth); + return result; + } +}; +``` python3代码: @@ -298,13 +318,68 @@ func levelOrder(_ root: TreeNode?) -> [[Int]] { return result } ``` +Scala: +```scala +// 102.二叉树的层序遍历 +object Solution { + import scala.collection.mutable + def levelOrder(root: TreeNode): List[List[Int]] = { + val res = mutable.ListBuffer[List[Int]]() + if (root == null) return res.toList + val queue = mutable.Queue[TreeNode]() // 声明一个队列 + queue.enqueue(root) // 把根节点加入queue + while (!queue.isEmpty) { + val tmp = mutable.ListBuffer[Int]() + val len = queue.size // 求出len的长度 + for (i <- 0 until len) { // 从0到当前队列长度的所有节点都加入到结果集 + val curNode = queue.dequeue() + tmp.append(curNode.value) + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + res.append(tmp.toList) + } + res.toList + } +} +``` + +Rust: + +```rust +pub fn level_order(root: Option>>) -> Vec> { + let mut ans = Vec::new(); + let mut stack = Vec::new(); + if root.is_none(){ + return ans; + } + stack.push(root.unwrap()); + while stack.is_empty()!= true{ + let num = stack.len(); + let mut level = Vec::new(); + for _i in 0..num{ + let tmp = stack.remove(0); + level.push(tmp.borrow_mut().val); + if tmp.borrow_mut().left.is_some(){ + stack.push(tmp.borrow_mut().left.take().unwrap()); + } + if tmp.borrow_mut().right.is_some(){ + stack.push(tmp.borrow_mut().right.take().unwrap()); + } + } + ans.push(level); + } + ans +} +``` + **此时我们就掌握了二叉树的层序遍历了,那么如下九道力扣上的题目,只需要修改模板的两三行代码(不能再多了),便可打倒!** # 107.二叉树的层次遍历 II -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) @@ -528,9 +603,64 @@ func levelOrderBottom(_ root: TreeNode?) -> [[Int]] { } ``` + +Scala: +```scala +// 107.二叉树的层次遍历II +object Solution { + import scala.collection.mutable + def levelOrderBottom(root: TreeNode): List[List[Int]] = { + val res = mutable.ListBuffer[List[Int]]() + if (root == null) return res.toList + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + val tmp = mutable.ListBuffer[Int]() + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + tmp.append(curNode.value) + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + res.append(tmp.toList) + } + // 最后翻转一下 + res.reverse.toList + } + +Rust: + +```rust +pub fn level_order(root: Option>>) -> Vec> { + let mut ans = Vec::new(); + let mut stack = Vec::new(); + if root.is_none(){ + return ans; + } + stack.push(root.unwrap()); + while stack.is_empty()!= true{ + let num = stack.len(); + let mut level = Vec::new(); + for _i in 0..num{ + let tmp = stack.remove(0); + level.push(tmp.borrow_mut().val); + if tmp.borrow_mut().left.is_some(){ + stack.push(tmp.borrow_mut().left.take().unwrap()); + } + if tmp.borrow_mut().right.is_some(){ + stack.push(tmp.borrow_mut().right.take().unwrap()); + } + } + ans.push(level); + } + ans +} +``` + # 199.二叉树的右视图 -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-right-side-view/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-right-side-view/) 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 @@ -750,9 +880,34 @@ func rightSideView(_ root: TreeNode?) -> [Int] { } ``` +Scala: +```scala +// 199.二叉树的右视图 +object Solution { + import scala.collection.mutable + def rightSideView(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + if (root == null) return res.toList + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + var curNode: TreeNode = null + for (i <- 0 until len) { + curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + res.append(curNode.value) // 把最后一个节点的值加入解集 + } + res.toList // 最后需要把res转换为List,return关键字可以省略 + } +} +``` + # 637.二叉树的层平均值 -[力扣题目链接](https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/average-of-levels-in-binary-tree/) 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。 @@ -981,10 +1136,34 @@ func averageOfLevels(_ root: TreeNode?) -> [Double] { return result } ``` +Scala: +```scala +// 637.二叉树的层平均值 +object Solution { + import scala.collection.mutable + def averageOfLevels(root: TreeNode): Array[Double] = { + val res = mutable.ArrayBuffer[Double]() + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + var sum = 0.0 + var len = queue.size + for (i <- 0 until len) { + var curNode = queue.dequeue() + sum += curNode.value // 累加该层的值 + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + res.append(sum / len) // 平均值即为sum/len + } + res.toArray // 最后需要转换为Array,return关键字可以省略 + } +} +``` # 429.N叉树的层序遍历 -[力扣题目链接](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/) +[力扣题目链接](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。 @@ -1225,9 +1404,37 @@ func levelOrder(_ root: Node?) -> [[Int]] { } ``` +Scala: +```scala +// 429.N叉树的层序遍历 +object Solution { + import scala.collection.mutable + def levelOrder(root: Node): List[List[Int]] = { + val res = mutable.ListBuffer[List[Int]]() + if (root == null) return res.toList + val queue = mutable.Queue[Node]() + queue.enqueue(root) // 根节点入队 + while (!queue.isEmpty) { + val tmp = mutable.ListBuffer[Int]() // 存储每层节点 + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + tmp.append(curNode.value) // 将该节点的值加入tmp + // 循环遍历该节点的子节点,加入队列 + for (child <- curNode.children) { + queue.enqueue(child) + } + } + res.append(tmp.toList) // 将该层的节点放到结果集 + } + res.toList + } +} +``` + # 515.在每个树行中找最大值 -[力扣题目链接](https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/) +[力扣题目链接](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) 您需要在二叉树的每一行中找到最大的值。 @@ -1433,9 +1640,35 @@ func largestValues(_ root: TreeNode?) -> [Int] { } ``` +Scala: +```scala +// 515.在每个树行中找最大值 +object Solution { + import scala.collection.mutable + def largestValues(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + if (root == null) return res.toList + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + var max = Int.MinValue // 初始化max为系统最小值 + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + max = math.max(max, curNode.value) // 对比求解最大值 + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + res.append(max) // 将最大值放入结果集 + } + res.toList + } +} +``` + # 116.填充每个节点的下一个右侧节点指针 -[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) +[力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) 给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: @@ -1692,9 +1925,38 @@ func connect(_ root: Node?) -> Node? { } ``` +Scala: +```scala +// 116.填充每个节点的下一个右侧节点指针 +object Solution { + import scala.collection.mutable + + def connect(root: Node): Node = { + if (root == null) return root + val queue = mutable.Queue[Node]() + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + val tmp = mutable.ListBuffer[Node]() + for (i <- 0 until len) { + val curNode = queue.dequeue() + tmp.append(curNode) + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + // 处理next指针 + for (i <- 0 until tmp.size - 1) { + tmp(i).next = tmp(i + 1) + } + tmp(tmp.size-1).next = null + } + root + } +} +``` # 117.填充每个节点的下一个右侧节点指针II -[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/) +[力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node-ii/) 思路: @@ -1943,9 +2205,38 @@ func connect(_ root: Node?) -> Node? { } ``` +Scala: +```scala +// 117.填充每个节点的下一个右侧节点指针II +object Solution { + import scala.collection.mutable + + def connect(root: Node): Node = { + if (root == null) return root + val queue = mutable.Queue[Node]() + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + val tmp = mutable.ListBuffer[Node]() + for (i <- 0 until len) { + val curNode = queue.dequeue() + tmp.append(curNode) + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + // 处理next指针 + for (i <- 0 until tmp.size - 1) { + tmp(i).next = tmp(i + 1) + } + tmp(tmp.size-1).next = null + } + root + } +} +``` # 104.二叉树的最大深度 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) 给定一个二叉树,找出其最大深度。 @@ -2160,9 +2451,33 @@ func maxDepth(_ root: TreeNode?) -> Int { } ``` +Scala: +```scala +// 104.二叉树的最大深度 +object Solution { + import scala.collection.mutable + def maxDepth(root: TreeNode): Int = { + if (root == null) return 0 + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + var depth = 0 + while (!queue.isEmpty) { + val len = queue.length + depth += 1 + for (i <- 0 until len) { + val curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + } + depth + } +} +``` + # 111.二叉树的最小深度 -[力扣题目链接](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) 相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。 @@ -2314,21 +2629,21 @@ JavaScript: var minDepth = function(root) { if (root === null) return 0; let queue = [root]; - let deepth = 0; + let depth = 0; while (queue.length) { let n = queue.length; - deepth++; + depth++; for (let i=0; i Int { } ``` +Scala: +```scala +// 111.二叉树的最小深度 +object Solution { + import scala.collection.mutable + def minDepth(root: TreeNode): Int = { + if (root == null) return 0 + var depth = 0 + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + depth += 1 + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + if (curNode.left == null && curNode.right == null) return depth + } + } + depth + } +} +``` # 总结 diff --git a/problems/0104.二叉树的最大深度.md b/problems/0104.二叉树的最大深度.md index 2229a854..55980189 100644 --- a/problems/0104.二叉树的最大深度.md +++ b/problems/0104.二叉树的最大深度.md @@ -12,7 +12,7 @@ # 104.二叉树的最大深度 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) 给定一个二叉树,找出其最大深度。 @@ -192,11 +192,38 @@ public: }; ``` +rust: +```rust +impl Solution { + pub fn max_depth(root: Option>>) -> i32 { + if root.is_none(){ + return 0; + } + let mut max_depth: i32 = 0; + let mut stack = vec![root.unwrap()]; + while !stack.is_empty() { + let num = stack.len(); + for _i in 0..num{ + let top = stack.remove(0); + if top.borrow_mut().left.is_some(){ + stack.push(top.borrow_mut().left.take().unwrap()); + } + if top.borrow_mut().right.is_some(){ + stack.push(top.borrow_mut().right.take().unwrap()); + } + } + max_depth+=1; + } + max_depth + } +``` + + 那么我们可以顺便解决一下n叉树的最大深度问题 # 559.n叉树的最大深度 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/) +[力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-n-ary-tree/) 给定一个 n 叉树,找到其最大深度。 @@ -267,14 +294,13 @@ class solution { /** * 递归法 */ - public int maxdepth(treenode root) { + public int maxDepth(TreeNode root) { if (root == null) { return 0; } - int leftdepth = maxdepth(root.left); - int rightdepth = maxdepth(root.right); - return math.max(leftdepth, rightdepth) + 1; - + int leftDepth = maxDepth(root.left); + int rightDepth = maxDepth(root.right); + return Math.max(leftDepth, rightDepth) + 1; } } ``` @@ -284,23 +310,23 @@ class solution { /** * 迭代法,使用层序遍历 */ - public int maxdepth(treenode root) { + public int maxDepth(TreeNode root) { if(root == null) { return 0; } - deque deque = new linkedlist<>(); + Deque deque = new LinkedList<>(); deque.offer(root); int depth = 0; - while (!deque.isempty()) { + while (!deque.isEmpty()) { int size = deque.size(); depth++; for (int i = 0; i < size; i++) { - treenode poll = deque.poll(); - if (poll.left != null) { - deque.offer(poll.left); + TreeNode node = deque.poll(); + if (node.left != null) { + deque.offer(node.left); } - if (poll.right != null) { - deque.offer(poll.right); + if (node.right != null) { + deque.offer(node.right); } } } @@ -468,7 +494,7 @@ class solution: ## go - +### 104.二叉树的最大深度 ```go /** * definition for a binary tree node. @@ -521,6 +547,8 @@ func maxdepth(root *treenode) int { ## javascript +### 104.二叉树的最大深度 + ```javascript var maxdepth = function(root) { if (root === null) return 0; @@ -568,6 +596,8 @@ var maxDepth = function(root) { }; ``` +### 559.n叉树的最大深度 + N叉树的最大深度 递归写法 ```js var maxDepth = function(root) { @@ -600,9 +630,9 @@ var maxDepth = function(root) { }; ``` -## TypeScript: +## TypeScript -> 二叉树的最大深度: +### 104.二叉树的最大深度 ```typescript // 后续遍历(自下而上) @@ -645,7 +675,7 @@ function maxDepth(root: TreeNode | null): number { }; ``` -> N叉树的最大深度 +### 559.n叉树的最大深度 ```typescript // 后续遍历(自下而上) @@ -675,6 +705,8 @@ function maxDepth(root: TreeNode | null): number { ## C +### 104.二叉树的最大深度 + 二叉树最大深度递归 ```c int maxDepth(struct TreeNode* root){ @@ -731,7 +763,8 @@ int maxDepth(struct TreeNode* root){ ## Swift ->二叉树最大深度 +### 104.二叉树的最大深度 + ```swift // 递归 - 后序 func maxDepth1(_ root: TreeNode?) -> Int { @@ -770,7 +803,8 @@ func maxDepth(_ root: TreeNode?) -> Int { } ``` ->N叉树最大深度 +### 559.n叉树的最大深度 + ```swift // 递归 func maxDepth(_ root: Node?) -> Int { @@ -806,5 +840,84 @@ func maxDepth1(_ root: Node?) -> Int { } ``` +## Scala + +### 104.二叉树的最大深度 +递归法: +```scala +object Solution { + def maxDepth(root: TreeNode): Int = { + def process(curNode: TreeNode): Int = { + if (curNode == null) return 0 + // 递归左节点和右节点,返回最大的,最后+1 + math.max(process(curNode.left), process(curNode.right)) + 1 + } + // 调用递归方法,return关键字可以省略 + process(root) + } +} +``` + +迭代法: +```scala +object Solution { + import scala.collection.mutable + def maxDepth(root: TreeNode): Int = { + var depth = 0 + if (root == null) return depth + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + depth += 1 // 只要有层次就+=1 + } + depth + } +} +``` + +### 559.n叉树的最大深度 + +递归法: +```scala +object Solution { + def maxDepth(root: Node): Int = { + if (root == null) return 0 + var depth = 0 + for (node <- root.children) { + depth = math.max(depth, maxDepth(node)) + } + depth + 1 + } +} +``` + +迭代法: (层序遍历) +```scala +object Solution { + import scala.collection.mutable + def maxDepth(root: Node): Int = { + if (root == null) return 0 + var depth = 0 + val queue = mutable.Queue[Node]() + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + depth += 1 + for (i <- 0 until len) { + val curNode = queue.dequeue() + for (node <- curNode.children) queue.enqueue(node) + } + } + depth + } +} +``` + -----------------------
diff --git a/problems/0106.从中序与后序遍历序列构造二叉树.md b/problems/0106.从中序与后序遍历序列构造二叉树.md index 188ad3cb..91e0c8d8 100644 --- a/problems/0106.从中序与后序遍历序列构造二叉树.md +++ b/problems/0106.从中序与后序遍历序列构造二叉树.md @@ -12,7 +12,7 @@ # 106.从中序与后序遍历序列构造二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) +[力扣题目链接](https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/) 根据一棵树的中序遍历与后序遍历构造二叉树。 @@ -394,7 +394,7 @@ public: # 105.从前序与中序遍历序列构造二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) +[力扣题目链接](https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/) 根据一棵树的前序遍历与中序遍历构造二叉树。 @@ -584,35 +584,29 @@ tree2 的前序遍历是[1 2 3], 后序遍历是[3 2 1]。 ```java class Solution { + Map map; // 方便根据数值查找位置 public TreeNode buildTree(int[] inorder, int[] postorder) { - return buildTree1(inorder, 0, inorder.length, postorder, 0, postorder.length); + map = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置 + map.put(inorder[i], i); + } + + return findNode(inorder, 0, inorder.length, postorder,0, postorder.length); // 前闭后开 } - public TreeNode buildTree1(int[] inorder, int inLeft, int inRight, - int[] postorder, int postLeft, int postRight) { - // 没有元素了 - if (inRight - inLeft < 1) { + + public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) { + // 参数里的范围都是前闭后开 + if (inBegin >= inEnd || postBegin >= postEnd) { // 不满足左闭右开,说明没有元素,返回空树 return null; } - // 只有一个元素了 - if (inRight - inLeft == 1) { - return new TreeNode(inorder[inLeft]); - } - // 后序数组postorder里最后一个即为根结点 - int rootVal = postorder[postRight - 1]; - TreeNode root = new TreeNode(rootVal); - int rootIndex = 0; - // 根据根结点的值找到该值在中序数组inorder里的位置 - for (int i = inLeft; i < inRight; i++) { - if (inorder[i] == rootVal) { - rootIndex = i; - break; - } - } - // 根据rootIndex划分左右子树 - root.left = buildTree1(inorder, inLeft, rootIndex, - postorder, postLeft, postLeft + (rootIndex - inLeft)); - root.right = buildTree1(inorder, rootIndex + 1, inRight, - postorder, postLeft + (rootIndex - inLeft), postRight - 1); + int rootIndex = map.get(postorder[postEnd - 1]); // 找到后序遍历的最后一个元素在中序遍历中的位置 + TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点 + int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定后序数列的个数 + root.left = findNode(inorder, inBegin, rootIndex, + postorder, postBegin, postBegin + lenOfLeft); + root.right = findNode(inorder, rootIndex + 1, inEnd, + postorder, postBegin + lenOfLeft, postEnd - 1); + return root; } } @@ -622,31 +616,29 @@ class Solution { ```java class Solution { + Map map; 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; - } + map = new HashMap<>(); + for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置 + map.put(inorder[i], i); } - // 根据 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 findNode(preorder, 0, preorder.length, inorder, 0, inorder.length); // 前闭后开 + } + + public TreeNode findNode(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd) { + // 参数里的范围都是前闭后开 + if (preBegin >= preEnd || inBegin >= inEnd) { // 不满足左闭右开,说明没有元素,返回空树 + return null; + } + int rootIndex = map.get(preorder[preBegin]); // 找到前序遍历的第一个元素在中序遍历中的位置 + TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点 + int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定前序数列的个数 + root.left = findNode(preorder, preBegin + 1, preBegin + lenOfLeft + 1, + inorder, inBegin, rootIndex); + root.right = findNode(preorder, preBegin + lenOfLeft + 1, preEnd, + inorder, rootIndex + 1, inEnd); + return root; } } @@ -1091,7 +1083,53 @@ class Solution_0106 { } ``` +## Scala +106 从中序与后序遍历序列构造二叉树 + +```scala +object Solution { + def buildTree(inorder: Array[Int], postorder: Array[Int]): TreeNode = { + // 1、如果长度为0,则直接返回null + var len = inorder.size + if (len == 0) return null + // 2、后序数组的最后一个元素是当前根元素 + var rootValue = postorder(len - 1) + var root: TreeNode = new TreeNode(rootValue, null, null) + if (len == 1) return root // 如果数组只有一个节点,就直接返回 + // 3、在中序数组中找到切割点的索引 + var delimiterIndex: Int = inorder.indexOf(rootValue) + // 4、切分数组往下迭代 + root.left = buildTree(inorder.slice(0, delimiterIndex), postorder.slice(0, delimiterIndex)) + root.right = buildTree(inorder.slice(delimiterIndex + 1, len), postorder.slice(delimiterIndex, len - 1)) + root // 返回root,return关键字可以省略 + } +} +``` + +105 从前序与中序遍历序列构造二叉树 + +```scala +object Solution { + def buildTree(preorder: Array[Int], inorder: Array[Int]): TreeNode = { + // 1、如果长度为0,直接返回空 + var len = inorder.size + if (len == 0) return null + // 2、前序数组的第一个元素是当前子树根节点 + var rootValue = preorder(0) + var root = new TreeNode(rootValue, null, null) + if (len == 1) return root // 如果数组元素只有一个,那么返回根节点 + // 3、在中序数组中,找到切割点 + var delimiterIndex = inorder.indexOf(rootValue) + + // 4、切分数组往下迭代 + root.left = buildTree(preorder.slice(1, delimiterIndex + 1), inorder.slice(0, delimiterIndex)) + root.right = buildTree(preorder.slice(delimiterIndex + 1, preorder.size), inorder.slice(delimiterIndex + 1, len)) + + root + } +} +``` -----------------------
diff --git a/problems/0108.将有序数组转换为二叉搜索树.md b/problems/0108.将有序数组转换为二叉搜索树.md index 6ee3947b..b5f322f0 100644 --- a/problems/0108.将有序数组转换为二叉搜索树.md +++ b/problems/0108.将有序数组转换为二叉搜索树.md @@ -9,7 +9,7 @@ # 108.将有序数组转换为二叉搜索树 -[力扣题目链接](https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/) +[力扣题目链接](https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/) 将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。 @@ -448,5 +448,27 @@ struct TreeNode* sortedArrayToBST(int* nums, int numsSize) { } ``` +## Scala + +递归: + +```scala +object Solution { + def sortedArrayToBST(nums: Array[Int]): TreeNode = { + def buildTree(left: Int, right: Int): TreeNode = { + if (left > right) return null // 当left大于right的时候,返回空 + // 最中间的节点是当前节点 + var mid = left + (right - left) / 2 + var curNode = new TreeNode(nums(mid)) + curNode.left = buildTree(left, mid - 1) + curNode.right = buildTree(mid + 1, right) + curNode + } + buildTree(0, nums.size - 1) + } +} +``` + + -----------------------
diff --git a/problems/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md index d98ff8a9..5bdd4f1b 100644 --- a/problems/0110.平衡二叉树.md +++ b/problems/0110.平衡二叉树.md @@ -9,7 +9,7 @@ # 110.平衡二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/balanced-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/balanced-binary-tree/) 给定一个二叉树,判断它是否是高度平衡的二叉树。 @@ -208,7 +208,7 @@ int getHeight(TreeNode* node) { ```CPP class Solution { public: - // 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1 + // 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1 int getHeight(TreeNode* node) { if (node == NULL) { return 0; @@ -531,40 +531,26 @@ class Solution: 迭代法: ```python class Solution: - def isBalanced(self, root: TreeNode) -> bool: - st = [] + def isBalanced(self, root: Optional[TreeNode]) -> bool: 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() + + height_map = {} + stack = [root] + while stack: + node = stack.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) #左 + stack.append(node) + stack.append(None) + if node.left: stack.append(node.left) + if node.right: stack.append(node.right) else: - node = st.pop() - depth -= 1 - result = max(result, depth) - return result + real_node = stack.pop() + left, right = height_map.get(real_node.left, 0), height_map.get(real_node.right, 0) + if abs(left - right) > 1: + return False + height_map[real_node] = 1 + max(left, right) + return True ``` diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index 224caa5e..0137bd15 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -9,7 +9,7 @@ # 111.二叉树的最小深度 -[力扣题目链接](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) 给定一个二叉树,找出其最小深度。 @@ -488,5 +488,110 @@ func minDepth(_ root: TreeNode?) -> Int { } ``` + +## Scala + +递归法: +```scala +object Solution { + def minDepth(root: TreeNode): Int = { + if (root == null) return 0 + if (root.left == null && root.right != null) return 1 + minDepth(root.right) + if (root.left != null && root.right == null) return 1 + minDepth(root.left) + // 如果两侧都不为空,则取最小值,return关键字可以省略 + 1 + math.min(minDepth(root.left), minDepth(root.right)) + } +} +``` + +迭代法: +```scala +object Solution { + import scala.collection.mutable + def minDepth(root: TreeNode): Int = { + if (root == null) return 0 + var depth = 0 + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + depth += 1 + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + if (curNode.left == null && curNode.right == null) return depth + } + } + depth + } +} +``` + +rust: +```rust +impl Solution { + pub fn min_depth(root: Option>>) -> i32 { + return Solution::bfs(root) + } + + // 递归 + pub fn dfs(node: Option>>) -> i32{ + if node.is_none(){ + return 0; + } + let parent = node.unwrap(); + let left_child = parent.borrow_mut().left.take(); + let right_child = parent.borrow_mut().right.take(); + if left_child.is_none() && right_child.is_none(){ + return 1; + } + let mut min_depth = i32::MAX; + if left_child.is_some(){ + let left_depth = Solution::dfs(left_child); + if left_depth <= min_depth{ + min_depth = left_depth + } + } + if right_child.is_some(){ + let right_depth = Solution::dfs(right_child); + if right_depth <= min_depth{ + min_depth = right_depth + } + } + min_depth + 1 + + } + + // 迭代 + pub fn bfs(node: Option>>) -> i32{ + let mut min_depth = 0; + if node.is_none(){ + return min_depth + } + let mut stack = vec![node.unwrap()]; + while !stack.is_empty(){ + min_depth += 1; + let num = stack.len(); + for _i in 0..num{ + let top = stack.remove(0); + let left_child = top.borrow_mut().left.take(); + let right_child = top.borrow_mut().right.take(); + if left_child.is_none() && right_child.is_none(){ + return min_depth; + } + if left_child.is_some(){ + stack.push(left_child.unwrap()); + } + if right_child.is_some(){ + stack.push(right_child.unwrap()); + } + } + } + min_depth + } + +``` + -----------------------
diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index 41463ec1..d4cb5190 100644 --- a/problems/0112.路径总和.md +++ b/problems/0112.路径总和.md @@ -16,7 +16,7 @@ # 112. 路径总和 -[力扣题目链接](https://leetcode-cn.com/problems/path-sum/) +[力扣题目链接](https://leetcode.cn/problems/path-sum/) 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。 @@ -216,7 +216,7 @@ public: # 113. 路径总和ii -[力扣题目链接](https://leetcode-cn.com/problems/path-sum-ii/) +[力扣题目链接](https://leetcode.cn/problems/path-sum-ii/) 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 @@ -300,7 +300,7 @@ public: ## java -lc112 +### 0112.路径总和 ```java class solution { public boolean haspathsum(treenode root, int targetsum) { @@ -373,26 +373,26 @@ class solution { ``` -0113.路径总和-ii +### 0113.路径总和-ii ```java class solution { - public list> pathsum(treenode root, int targetsum) { - list> res = new arraylist<>(); + public List> pathsum(TreeNode root, int targetsum) { + List> res = new ArrayList<>(); if (root == null) return res; // 非空判断 - - list path = new linkedlist<>(); + + List path = new LinkedList<>(); preorderdfs(root, targetsum, res, path); return res; } - public void preorderdfs(treenode root, int targetsum, list> res, list path) { + 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)); + res.add(new ArrayList<>(path)); } return; // 如果和不为 targetsum,返回 } @@ -436,7 +436,7 @@ class Solution { ## python -0112.路径总和 +### 0112.路径总和 **递归** ```python @@ -488,7 +488,7 @@ class solution: return false ``` -0113.路径总和-ii +### 0113.路径总和-ii **递归** ```python @@ -545,7 +545,7 @@ class Solution: ## go -112. 路径总和 +### 112. 路径总和 ```go //递归法 @@ -570,7 +570,7 @@ func hasPathSum(root *TreeNode, targetSum int) bool { } ``` -113. 路径总和 II +### 113. 路径总和 II ```go /** @@ -612,7 +612,7 @@ func traverse(node *TreeNode, result *[][]int, currPath *[]int, targetSum int) { ## javascript -0112.路径总和 +### 0112.路径总和 **递归** ```javascript @@ -673,7 +673,7 @@ let hasPathSum = function(root, targetSum) { }; ``` -0113.路径总和-ii +### 0113.路径总和-ii **递归** ```javascript @@ -768,7 +768,7 @@ let pathSum = function(root, targetSum) { ## TypeScript -> 0112.路径总和 +### 0112.路径总和 **递归法:** @@ -850,7 +850,7 @@ function hasPathSum(root: TreeNode | null, targetSum: number): boolean { }; ``` -> 0112.路径总和 ii +### 0112.路径总和 ii **递归法:** @@ -888,7 +888,7 @@ function pathSum(root: TreeNode | null, targetSum: number): number[][] { ## Swift -0112.路径总和 +### 0112.路径总和 **递归** @@ -955,7 +955,7 @@ func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool { } ``` -0113.路径总和 II +### 0113.路径总和 II **递归** @@ -1006,7 +1006,210 @@ func traversal(_ cur: TreeNode?, count: Int) { } ``` +## C +> 0112.路径总和 +递归法: +```c +bool hasPathSum(struct TreeNode* root, int targetSum){ + // 递归结束条件:若当前节点不存在,返回false + if(!root) + return false; + // 若当前节点为叶子节点,且targetSum-root的值为0。(当前路径上的节点值的和满足条件)返回true + if(!root->right && !root->left && targetSum == root->val) + return true; + + // 查看左子树和右子树的所有节点是否满足条件 + return hasPathSum(root->right, targetSum - root->val) || hasPathSum(root->left, targetSum - root->val); +} +``` +迭代法: +```c +// 存储一个节点以及当前的和 +struct Pair { + struct TreeNode* node; + int sum; +}; + +bool hasPathSum(struct TreeNode* root, int targetSum){ + struct Pair stack[1000]; + int stackTop = 0; + + // 若root存在,则将节点和值封装成一个pair入栈 + if(root) { + struct Pair newPair = {root, root->val}; + stack[stackTop++] = newPair; + } + + // 当栈不为空时 + while(stackTop) { + // 出栈栈顶元素 + struct Pair topPair = stack[--stackTop]; + // 若栈顶元素为叶子节点,且和为targetSum时,返回true + if(!topPair.node->left && !topPair.node->right && topPair.sum == targetSum) + return true; + + // 若当前栈顶节点有左右孩子,计算和并入栈 + if(topPair.node->left) { + struct Pair newPair = {topPair.node->left, topPair.sum + topPair.node->left->val}; + stack[stackTop++] = newPair; + } + if(topPair.node->right) { + struct Pair newPair = {topPair.node->right, topPair.sum + topPair.node->right->val}; + stack[stackTop++] = newPair; + } + } + return false; +} +``` +> 0113.路径总和 II +```c +int** ret; +int* path; +int* colSize; +int retTop; +int pathTop; + +void traversal(const struct TreeNode* const node, int count) { + // 若当前节点为叶子节点 + if(!node->right && !node->left) { + // 若当前path上的节点值总和等于targetSum。 + if(count == 0) { + // 复制当前path + int *curPath = (int*)malloc(sizeof(int) * pathTop); + memcpy(curPath, path, sizeof(int) * pathTop); + // 记录当前path的长度为pathTop + colSize[retTop] = pathTop; + // 将当前path加入到ret数组中 + ret[retTop++] = curPath; + } + return; + } + + // 若节点有左/右孩子 + if(node->left) { + // 将左孩子的值加入path中 + path[pathTop++] = node->left->val; + traversal(node->left, count - node->left->val); + // 回溯 + pathTop--; + } + if(node->right) { + // 将右孩子的值加入path中 + path[pathTop++] = node->right->val; + traversal(node->right, count - node->right->val); + // 回溯 + --pathTop; + } +} + +int** pathSum(struct TreeNode* root, int targetSum, int* returnSize, int** returnColumnSizes){ + // 初始化数组 + ret = (int**)malloc(sizeof(int*) * 1000); + path = (int*)malloc(sizeof(int*) * 1000); + colSize = (int*)malloc(sizeof(int) * 1000); + retTop = pathTop = 0; + *returnSize = 0; + + // 若根节点不存在,返回空的ret + if(!root) + return ret; + // 将根节点加入到path中 + path[pathTop++] = root->val; + traversal(root, targetSum - root->val); + + // 设置返回ret数组大小,以及其中每个一维数组元素的长度 + *returnSize = retTop; + *returnColumnSizes = colSize; + + return ret; +} +``` + +## Scala + +### 0112.路径总和 + +**递归:** +```scala +object Solution { + def hasPathSum(root: TreeNode, targetSum: Int): Boolean = { + if(root == null) return false + var res = false + + def traversal(curNode: TreeNode, sum: Int): Unit = { + if (res) return // 如果直接标记为true了,就没有往下递归的必要了 + if (curNode.left == null && curNode.right == null && sum == targetSum) { + res = true + return + } + // 往下递归 + if (curNode.left != null) traversal(curNode.left, sum + curNode.left.value) + if (curNode.right != null) traversal(curNode.right, sum + curNode.right.value) + } + + traversal(root, root.value) + res // return关键字可以省略 + } +} +``` + +**迭代:** +```scala +object Solution { + import scala.collection.mutable + def hasPathSum(root: TreeNode, targetSum: Int): Boolean = { + if (root == null) return false + val stack = mutable.Stack[(TreeNode, Int)]() + stack.push((root, root.value)) // 将根节点元素放入stack + while (!stack.isEmpty) { + val curNode = stack.pop() // 取出栈顶元素 + // 如果遇到叶子节点,看当前的值是否等于targetSum,等于则返回true + if (curNode._1.left == null && curNode._1.right == null && curNode._2 == targetSum) { + return true + } + if (curNode._1.right != null) stack.push((curNode._1.right, curNode._2 + curNode._1.right.value)) + if (curNode._1.left != null) stack.push((curNode._1.left, curNode._2 + curNode._1.left.value)) + } + false //如果没有返回true,即可返回false,return关键字可以省略 + } +} +``` + +### 0113.路径总和 II + +**递归:** +```scala +object Solution { + import scala.collection.mutable.ListBuffer + def pathSum(root: TreeNode, targetSum: Int): List[List[Int]] = { + val res = ListBuffer[List[Int]]() + if (root == null) return res.toList + val path = ListBuffer[Int](); + + def traversal(cur: TreeNode, count: Int): Unit = { + if (cur.left == null && cur.right == null && count == 0) { + res.append(path.toList) + return + } + if (cur.left != null) { + path.append(cur.left.value) + traversal(cur.left, count - cur.left.value) + path.remove(path.size - 1) + } + if (cur.right != null) { + path.append(cur.right.value) + traversal(cur.right, count - cur.right.value) + path.remove(path.size - 1) + } + } + + path.append(root.value) + traversal(root, targetSum - root.value) + res.toList + } +} +``` -----------------------
diff --git a/problems/0115.不同的子序列.md b/problems/0115.不同的子序列.md index 0f762969..9ae04139 100644 --- a/problems/0115.不同的子序列.md +++ b/problems/0115.不同的子序列.md @@ -6,7 +6,7 @@ ## 115.不同的子序列 -[力扣题目链接](https://leetcode-cn.com/problems/distinct-subsequences/) +[力扣题目链接](https://leetcode.cn/problems/distinct-subsequences/) 给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。 @@ -267,6 +267,36 @@ const numDistinct = (s, t) => { }; ``` +TypeScript: + +```typescript +function numDistinct(s: string, t: string): number { + /** + dp[i][j]: s前i个字符,t前j个字符,s子序列中t出现的个数 + dp[0][0]=1, 表示s前0个字符为'',t前0个字符为'' + */ + const sLen: number = s.length, + tLen: number = t.length; + const dp: number[][] = new Array(sLen + 1).fill(0) + .map(_ => new Array(tLen + 1).fill(0)); + for (let m = 0; m < sLen; m++) { + dp[m][0] = 1; + } + for (let i = 1; i <= sLen; i++) { + for (let j = 1; j <= tLen; j++) { + 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[sLen][tLen]; +}; +``` + + + -----------------------
diff --git a/problems/0116.填充每个节点的下一个右侧节点指针.md b/problems/0116.填充每个节点的下一个右侧节点指针.md index 2c443de5..303108be 100644 --- a/problems/0116.填充每个节点的下一个右侧节点指针.md +++ b/problems/0116.填充每个节点的下一个右侧节点指针.md @@ -7,7 +7,7 @@ # 116. 填充每个节点的下一个右侧节点指针 -[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) +[力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) 给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: @@ -287,6 +287,79 @@ const connect = root => { }; ``` +## TypeScript + +(注:命名空间‘Node’与typescript中内置类型冲突,这里改成了‘NodePro’) + +> 递归法: + +```typescript +class NodePro { + val: number + left: NodePro | null + right: NodePro | null + next: NodePro | null + constructor(val?: number, left?: NodePro, right?: NodePro, next?: NodePro) { + this.val = (val === undefined ? 0 : val) + this.left = (left === undefined ? null : left) + this.right = (right === undefined ? null : right) + this.next = (next === undefined ? null : next) + } +} + +function connect(root: NodePro | null): NodePro | null { + if (root === null) return null; + root.next = null; + recur(root); + return root; +}; +function recur(node: NodePro): void { + if (node.left === null || node.right === null) return; + node.left.next = node.right; + node.right.next = node.next && node.next.left; + recur(node.left); + recur(node.right); +} +``` + +> 迭代法: + +```typescript +class NodePro { + val: number + left: NodePro | null + right: NodePro | null + next: NodePro | null + constructor(val?: number, left?: NodePro, right?: NodePro, next?: NodePro) { + this.val = (val === undefined ? 0 : val) + this.left = (left === undefined ? null : left) + this.right = (right === undefined ? null : right) + this.next = (next === undefined ? null : next) + } +} + +function connect(root: NodePro | null): NodePro | null { + if (root === null) return null; + const queue: NodePro[] = []; + queue.push(root); + while (queue.length > 0) { + for (let i = 0, length = queue.length; i < length; i++) { + const curNode: NodePro = queue.shift()!; + if (i === length - 1) { + curNode.next = null; + } else { + curNode.next = queue[0]; + } + if (curNode.left !== null) queue.push(curNode.left); + if (curNode.right !== null) queue.push(curNode.right); + } + } + return root; +}; +``` + + + -----------------------
diff --git a/problems/0121.买卖股票的最佳时机.md b/problems/0121.买卖股票的最佳时机.md index f0bc3b97..a577f1dd 100644 --- a/problems/0121.买卖股票的最佳时机.md +++ b/problems/0121.买卖股票的最佳时机.md @@ -6,7 +6,7 @@ ## 121. 买卖股票的最佳时机 -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/) 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 @@ -426,6 +426,46 @@ var maxProfit = function(prices) { }; ``` +TypeScript: + +> 贪心法 + +```typescript +function maxProfit(prices: number[]): number { + if (prices.length === 0) return 0; + let buy: number = prices[0]; + let profitMax: number = 0; + for (let i = 1, length = prices.length; i < length; i++) { + profitMax = Math.max(profitMax, prices[i] - buy); + buy = Math.min(prices[i], buy); + } + return profitMax; +}; +``` + +> 动态规划 + +```typescript +function maxProfit(prices: number[]): number { + /** + dp[i][0]: 第i天持有股票的最大现金 + dp[i][1]: 第i天不持有股票的最大现金 + */ + const length = prices.length; + if (length === 0) return 0; + const dp: number[][] = []; + dp[0] = [-prices[0], 0]; + for (let i = 1; i < length; i++) { + dp[i] = []; + dp[i][0] = Math.max(dp[i - 1][0], -prices[i]); + dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]); + } + return dp[length - 1][1]; +}; +``` + + + -----------------------
diff --git a/problems/0122.买卖股票的最佳时机II.md b/problems/0122.买卖股票的最佳时机II.md index 1e7b77d8..e4742cf0 100644 --- a/problems/0122.买卖股票的最佳时机II.md +++ b/problems/0122.买卖股票的最佳时机II.md @@ -7,7 +7,7 @@ # 122.买卖股票的最佳时机II -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 @@ -91,8 +91,8 @@ public: }; ``` -* 时间复杂度:$O(n)$ -* 空间复杂度:$O(1)$ +* 时间复杂度:O(n) +* 空间复杂度:O(1) ### 动态规划 @@ -133,8 +133,9 @@ public: ## 其他语言版本 -Java: +### Java: +贪心: ```java // 贪心思路 class Solution { @@ -148,6 +149,7 @@ class Solution { } ``` +动态规划: ```java class Solution { // 动态规划 public int maxProfit(int[] prices) { @@ -169,8 +171,8 @@ class Solution { // 动态规划 } ``` -Python: - +### Python: +贪心: ```python class Solution: def maxProfit(self, prices: List[int]) -> int: @@ -180,7 +182,7 @@ class Solution: return result ``` -python动态规划 +动态规划: ```python class Solution: def maxProfit(self, prices: List[int]) -> int: @@ -194,7 +196,7 @@ class Solution: return dp[-1][1] ``` -Go: +### Go: ```golang //贪心算法 @@ -231,7 +233,7 @@ func maxProfit(prices []int) int { } ``` -Javascript: +### Javascript: 贪心 ```Javascript @@ -268,7 +270,7 @@ const maxProfit = (prices) => { }; ``` -TypeScript: +### TypeScript: ```typescript function maxProfit(prices: number[]): number { @@ -280,7 +282,7 @@ function maxProfit(prices: number[]): number { }; ``` -C: +### C: 贪心: ```c int maxProfit(int* prices, int pricesSize){ @@ -318,5 +320,22 @@ int maxProfit(int* prices, int pricesSize){ } ``` +### Scala + +贪心: +```scala +object Solution { + def maxProfit(prices: Array[Int]): Int = { + var result = 0 + for (i <- 1 until prices.length) { + if (prices(i) > prices(i - 1)) { + result += prices(i) - prices(i - 1) + } + } + result + } +} +``` + -----------------------
diff --git a/problems/0122.买卖股票的最佳时机II(动态规划).md b/problems/0122.买卖股票的最佳时机II(动态规划).md index 5a165a14..fa9f8842 100644 --- a/problems/0122.买卖股票的最佳时机II(动态规划).md +++ b/problems/0122.买卖股票的最佳时机II(动态规划).md @@ -6,7 +6,7 @@ ## 122.买卖股票的最佳时机II -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/) 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。 @@ -295,6 +295,42 @@ const maxProfit = (prices) => { } ``` +TypeScript: + +> 动态规划 + +```typescript +function maxProfit(prices: number[]): number { + /** + dp[i][0]: 第i天持有股票 + dp[i][1]: 第i天不持有股票 + */ + const length: number = prices.length; + if (length === 0) return 0; + const dp: number[][] = new Array(length).fill(0).map(_ => []); + dp[0] = [-prices[0], 0]; + for (let i = 1; i < length; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i]); + } + return dp[length - 1][1]; +}; +``` + +> 贪心法 + +```typescript +function maxProfit(prices: number[]): number { + let resProfit: number = 0; + for (let i = 1, length = prices.length; i < length; i++) { + if (prices[i] > prices[i - 1]) { + resProfit += prices[i] - prices[i - 1]; + } + } + return resProfit; +}; +``` + ----------------------- diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md index 56ade343..c15aaee8 100644 --- a/problems/0123.买卖股票的最佳时机III.md +++ b/problems/0123.买卖股票的最佳时机III.md @@ -6,7 +6,7 @@ ## 123.买卖股票的最佳时机III -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iii/) 给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。 @@ -352,6 +352,36 @@ const maxProfit = prices => { }; ``` +TypeScript: + +> 版本一 + +```typescript +function maxProfit(prices: number[]): number { + /** + dp[i][0]: 无操作; + dp[i][1]: 第一次买入; + dp[i][2]: 第一次卖出; + dp[i][3]: 第二次买入; + dp[i][4]: 第二次卖出; + */ + const length: number = prices.length; + if (length === 0) return 0; + const dp: number[][] = new Array(length).fill(0) + .map(_ => new Array(5).fill(0)); + dp[0][1] = -prices[0]; + dp[0][3] = -prices[0]; + for (let i = 1; i < length; i++) { + dp[i][0] = dp[i - 1][0]; + dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); + dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i]); + dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i]); + dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i]); + } + return Math.max(dp[length - 1][2], dp[length - 1][4]); +}; +``` + Go: > 版本一: diff --git a/problems/0127.单词接龙.md b/problems/0127.单词接龙.md index 584bcb2a..f1c6f182 100644 --- a/problems/0127.单词接龙.md +++ b/problems/0127.单词接龙.md @@ -7,7 +7,7 @@ # 127. 单词接龙 -[力扣题目链接](https://leetcode-cn.com/problems/word-ladder/) +[力扣题目链接](https://leetcode.cn/problems/word-ladder/) 字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列: * 序列中第一个单词是 beginWord 。 diff --git a/problems/0129.求根到叶子节点数字之和.md b/problems/0129.求根到叶子节点数字之和.md index b271ca7d..92a72fe3 100644 --- a/problems/0129.求根到叶子节点数字之和.md +++ b/problems/0129.求根到叶子节点数字之和.md @@ -3,9 +3,12 @@

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

+ + + # 129. 求根节点到叶节点数字之和 -[力扣题目链接](https://leetcode-cn.com/problems/sum-root-to-leaf-numbers/) +[力扣题目链接](https://leetcode.cn/problems/sum-root-to-leaf-numbers/) # 思路 @@ -245,6 +248,29 @@ class Solution: ``` Go: +```go +func sumNumbers(root *TreeNode) int { + sum = 0 + travel(root, root.Val) + return sum +} + +func travel(root *TreeNode, tmpSum int) { + if root.Left == nil && root.Right == nil { + sum += tmpSum + } else { + if root.Left != nil { + travel(root.Left, tmpSum*10+root.Left.Val) + } + if root.Right != nil { + travel(root.Right, tmpSum*10+root.Right.Val) + } + } +} +``` + + + JavaScript: ```javascript var sumNumbers = function(root) { @@ -289,7 +315,40 @@ var sumNumbers = function(root) { }; ``` +TypeScript: + +```typescript +function sumNumbers(root: TreeNode | null): number { + if (root === null) return 0; + let resTotal: number = 0; + const route: number[] = []; + route.push(root.val); + recur(root, route); + return resTotal; + function recur(node: TreeNode, route: number[]): void { + if (node.left === null && node.right === null) { + resTotal += listToSum(route); + return; + } + if (node.left !== null) { + route.push(node.left.val); + recur(node.left, route); + route.pop(); + }; + if (node.right !== null) { + route.push(node.right.val); + recur(node.right, route); + route.pop(); + }; + } + function listToSum(nums: number[]): number { + return Number(nums.join('')); + } +}; +``` + C: + ```c //sum记录总和 int sum; diff --git a/problems/0131.分割回文串.md b/problems/0131.分割回文串.md index 10b747cb..64d45853 100644 --- a/problems/0131.分割回文串.md +++ b/problems/0131.分割回文串.md @@ -9,7 +9,7 @@ # 131.分割回文串 -[力扣题目链接](https://leetcode-cn.com/problems/palindrome-partitioning/) +[力扣题目链接](https://leetcode.cn/problems/palindrome-partitioning/) 给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。 @@ -442,7 +442,7 @@ var partition = function(s) { } for(let j = i; j < len; j++) { if(!isPalindrome(s, i, j)) continue; - path.push(s.substr(i, j - i + 1)); + path.push(s.slice(i, j + 1)); backtracking(j + 1); path.pop(); } @@ -454,31 +454,36 @@ var partition = function(s) { ```typescript function partition(s: string): string[][] { - function isPalindromeStr(s: string, left: number, right: number): boolean { - while (left < right) { - if (s[left++] !== s[right--]) { - return false; + const res: string[][] = [] + const path: string[] = [] + const isHuiwen = ( + str: string, + startIndex: number, + endIndex: number + ): boolean => { + for (; startIndex < endIndex; startIndex++, endIndex--) { + if (str[startIndex] !== str[endIndex]) { + return false } } - return true; + return true } - function backTracking(s: string, startIndex: number, route: string[]): void { - let length: number = s.length; - if (length === startIndex) { - resArr.push(route.slice()); - return; + const rec = (str: string, index: number): void => { + if (index >= str.length) { + res.push([...path]) + return } - for (let i = startIndex; i < length; i++) { - if (isPalindromeStr(s, startIndex, i)) { - route.push(s.slice(startIndex, i + 1)); - backTracking(s, i + 1, route); - route.pop(); + for (let i = index; i < str.length; i++) { + if (!isHuiwen(str, index, i)) { + continue } + path.push(str.substring(index, i + 1)) + rec(str, i + 1) + path.pop() } } - const resArr: string[][] = []; - backTracking(s, 0, []); - return resArr; + rec(s, 0) + return res }; ``` @@ -671,5 +676,50 @@ impl Solution { } } ``` + + +## Scala + +```scala +object Solution { + + import scala.collection.mutable + + def partition(s: String): List[List[String]] = { + var result = mutable.ListBuffer[List[String]]() + var path = mutable.ListBuffer[String]() + + // 判断字符串是否回文 + def isPalindrome(start: Int, end: Int): Boolean = { + var (left, right) = (start, end) + while (left < right) { + if (s(left) != s(right)) return false + left += 1 + right -= 1 + } + true + } + + // 回溯算法 + def backtracking(startIndex: Int): Unit = { + if (startIndex >= s.size) { + result.append(path.toList) + return + } + // 添加循环守卫,如果当前分割是回文子串则进入回溯 + for (i <- startIndex until s.size if isPalindrome(startIndex, i)) { + path.append(s.substring(startIndex, i + 1)) + backtracking(i + 1) + path = path.take(path.size - 1) + } + } + + backtracking(0) + result.toList + } +} +``` + + -----------------------
diff --git a/problems/0132.分割回文串II.md b/problems/0132.分割回文串II.md index 87d3e4b4..36db56cb 100644 --- a/problems/0132.分割回文串II.md +++ b/problems/0132.分割回文串II.md @@ -8,7 +8,7 @@ # 132. 分割回文串 II -[力扣题目链接](https://leetcode-cn.com/problems/palindrome-partitioning-ii/) +[力扣题目链接](https://leetcode.cn/problems/palindrome-partitioning-ii/) 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文。 @@ -206,6 +206,55 @@ public: ## Java ```java +class Solution { + + public int minCut(String s) { + if(null == s || "".equals(s)){ + return 0; + } + int len = s.length(); + // 1. + // 记录子串[i..j]是否是回文串 + boolean[][] isPalindromic = new boolean[len][len]; + // 从下到上,从左到右 + for(int i = len - 1; i >= 0; i--){ + for(int j = i; j < len; j++){ + if(s.charAt(i) == s.charAt(j)){ + if(j - i <= 1){ + isPalindromic[i][j] = true; + } else{ + isPalindromic[i][j] = isPalindromic[i + 1][j - 1]; + } + } else{ + isPalindromic[i][j] = false; + } + } + } + + // 2. + // dp[i] 表示[0..i]的最小分割次数 + int[] dp = new int[len]; + for(int i = 0; i < len; i++){ + //初始考虑最坏的情况。 1个字符分割0次, len个字符分割 len - 1次 + dp[i] = i; + } + + for(int i = 1; i < len; i++){ + if(isPalindromic[0][i]){ + // s[0..i]是回文了,那 dp[i] = 0, 一次也不用分割 + dp[i] = 0; + continue; + } + for(int j = 0; j < i; j++){ + // 按文中的思路,不清楚就拿 "ababa" 为例,先写出 isPalindromic 数组,再进行求解 + if(isPalindromic[j + 1][i]){ + dp[i] = Math.min(dp[i], dp[j] + 1); + } + } + } + return dp[len - 1]; + } +} ``` ## Python @@ -240,6 +289,7 @@ class Solution: ## Go ```go + ``` ## JavaScript diff --git a/problems/0134.加油站.md b/problems/0134.加油站.md index e96773ff..e5d50a9b 100644 --- a/problems/0134.加油站.md +++ b/problems/0134.加油站.md @@ -7,7 +7,7 @@ # 134. 加油站 -[力扣题目链接](https://leetcode-cn.com/problems/gas-station/) +[力扣题目链接](https://leetcode.cn/problems/gas-station/) 在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 @@ -408,6 +408,9 @@ function canCompleteCircuit(gas: number[], cost: number[]): number { ### C +贪心算法:方法一 + + ```c int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){ int curSum = 0; @@ -437,5 +440,104 @@ int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){ } ``` +贪心算法:方法二 +```c +int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){ + int curSum = 0; + int totalSum = 0; + int start = 0; + + int i; + for(i = 0; i < gasSize; ++i) { + // 当前i站中加油量与耗油量的差 + int diff = gas[i] - cost[i]; + + curSum += diff; + totalSum += diff; + + // 若0到i的加油量都为负,则开始位置应为i+1 + if(curSum < 0) { + curSum = 0; + // 当i + 1 == gasSize时,totalSum < 0(此时i为gasSize - 1),油车不可能返回原点 + start = i + 1; + } + } + + // 若总和小于0,加油车无论如何都无法返回原点。返回-1 + if(totalSum < 0) + return -1; + + return start; +} +``` + +### Scala + +暴力解法: + +```scala +object Solution { + def canCompleteCircuit(gas: Array[Int], cost: Array[Int]): Int = { + for (i <- cost.indices) { + var rest = gas(i) - cost(i) + var index = (i + 1) % cost.length // index为i的下一个节点 + while (rest > 0 && i != index) { + rest += (gas(index) - cost(index)) + index = (index + 1) % cost.length + } + if (rest >= 0 && index == i) return i + } + -1 + } +} +``` + +贪心算法,方法一: + +```scala +object Solution { + def canCompleteCircuit(gas: Array[Int], cost: Array[Int]): Int = { + var curSum = 0 + var min = Int.MaxValue + for (i <- gas.indices) { + var rest = gas(i) - cost(i) + curSum += rest + min = math.min(min, curSum) + } + if (curSum < 0) return -1 // 情况1: gas的总和小于cost的总和,不可能到达终点 + if (min >= 0) return 0 // 情况2: 最小值>=0,从0号出发可以直接到达 + // 情况3: min为负值,从后向前看,能把min填平的节点就是出发节点 + for (i <- gas.length - 1 to 0 by -1) { + var rest = gas(i) - cost(i) + min += rest + if (min >= 0) return i + } + -1 + } +} +``` + +贪心算法,方法二: + +```scala +object Solution { + def canCompleteCircuit(gas: Array[Int], cost: Array[Int]): Int = { + var curSum = 0 + var totalSum = 0 + var start = 0 + for (i <- gas.indices) { + curSum += (gas(i) - cost(i)) + totalSum += (gas(i) - cost(i)) + if (curSum < 0) { + start = i + 1 // 起始位置更新 + curSum = 0 // curSum从0开始 + } + } + if (totalSum < 0) return -1 // 说明怎么走不可能跑一圈 + start + } +} +``` + -----------------------
diff --git a/problems/0135.分发糖果.md b/problems/0135.分发糖果.md index 72f3dd56..242664f0 100644 --- a/problems/0135.分发糖果.md +++ b/problems/0135.分发糖果.md @@ -7,7 +7,7 @@ # 135. 分发糖果 -[力扣题目链接](https://leetcode-cn.com/problems/candy/) +[力扣题目链接](https://leetcode.cn/problems/candy/) 老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 @@ -126,11 +126,11 @@ public: ## 其他语言版本 -### Java +### Java ```java class Solution { - /** - 分两个阶段 + /** + 分两个阶段 1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 1 2、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大 */ @@ -160,7 +160,7 @@ class Solution { } ``` -### Python +### Python ```python class Solution: def candy(self, ratings: List[int]) -> int: @@ -213,6 +213,25 @@ func findMax(num1 int ,num2 int) int{ } ``` +### Rust +```rust +pub fn candy(ratings: Vec) -> i32 { + let mut candies = vec![1i32; ratings.len()]; + for i in 1..ratings.len() { + if ratings[i - 1] < ratings[i] { + candies[i] = candies[i - 1] + 1; + } + } + + for i in (0..ratings.len()-1).rev() { + if ratings[i] > ratings[i + 1] { + candies[i] = candies[i].max(candies[i + 1] + 1); + } + } + candies.iter().sum() +} +``` + ### Javascript: ```Javascript var candy = function(ratings) { @@ -229,7 +248,7 @@ var candy = function(ratings) { candys[i] = Math.max(candys[i], candys[i + 1] + 1) } } - + let count = candys.reduce((a, b) => { return a + b }) @@ -305,6 +324,31 @@ function candy(ratings: number[]): number { }; ``` +### Scala + +```scala +object Solution { + def candy(ratings: Array[Int]): Int = { + var candyVec = new Array[Int](ratings.length) + for (i <- candyVec.indices) candyVec(i) = 1 + // 从前向后 + for (i <- 1 until candyVec.length) { + if (ratings(i) > ratings(i - 1)) { + candyVec(i) = candyVec(i - 1) + 1 + } + } + + // 从后向前 + for (i <- (candyVec.length - 2) to 0 by -1) { + if (ratings(i) > ratings(i + 1)) { + candyVec(i) = math.max(candyVec(i), candyVec(i + 1) + 1) + } + } + + candyVec.sum // 求和 + } +} +``` ----------------------- diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md index ac834f04..7ff13f72 100644 --- a/problems/0139.单词拆分.md +++ b/problems/0139.单词拆分.md @@ -8,7 +8,7 @@ ## 139.单词拆分 -[力扣题目链接](https://leetcode-cn.com/problems/word-break/) +[力扣题目链接](https://leetcode.cn/problems/word-break/) 给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 @@ -345,6 +345,48 @@ const wordBreak = (s, wordDict) => { } ``` +TypeScript: + +> 动态规划 + +```typescript +function wordBreak(s: string, wordDict: string[]): boolean { + const dp: boolean[] = new Array(s.length + 1).fill(false); + dp[0] = true; + for (let i = 1; i <= s.length; i++) { + for (let j = 0; j < i; j++) { + const tempStr: string = s.slice(j, i); + if (wordDict.includes(tempStr) && dp[j] === true) { + dp[i] = true; + break; + } + } + } + return dp[s.length]; +}; +``` + +> 记忆化回溯 + +```typescript +function wordBreak(s: string, wordDict: string[]): boolean { + // 只需要记忆结果为false的情况 + const memory: boolean[] = []; + return backTracking(s, wordDict, 0, memory); + function backTracking(s: string, wordDict: string[], startIndex: number, memory: boolean[]): boolean { + if (startIndex >= s.length) return true; + if (memory[startIndex] === false) return false; + for (let i = startIndex + 1, length = s.length; i <= length; i++) { + const str: string = s.slice(startIndex, i); + if (wordDict.includes(str) && backTracking(s, wordDict, i, memory)) + return true; + } + memory[startIndex] = false; + return false; + } +}; +``` + ----------------------- diff --git a/problems/0141.环形链表.md b/problems/0141.环形链表.md index ddd83c94..ce90b6c4 100644 --- a/problems/0141.环形链表.md +++ b/problems/0141.环形链表.md @@ -7,6 +7,8 @@ # 141. 环形链表 +[力扣题目链接](https://leetcode.cn/problems/linked-list-cycle/submissions/) + 给定一个链表,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 @@ -103,7 +105,7 @@ class Solution: return False ``` -## Go +### Go ```go func hasCycle(head *ListNode) bool { @@ -139,6 +141,23 @@ var hasCycle = function(head) { }; ``` +### TypeScript + +```typescript +function hasCycle(head: ListNode | null): boolean { + let slowNode: ListNode | null = head, + fastNode: ListNode | null = head; + while (fastNode !== null && fastNode.next !== null) { + slowNode = slowNode!.next; + fastNode = fastNode.next.next; + if (slowNode === fastNode) return true; + } + return false; +}; +``` + + + -----------------------
diff --git a/problems/0142.环形链表II.md b/problems/0142.环形链表II.md index e8ca950d..0b9059dc 100644 --- a/problems/0142.环形链表II.md +++ b/problems/0142.环形链表II.md @@ -11,7 +11,7 @@ ## 142.环形链表II -[力扣题目链接](https://leetcode-cn.com/problems/linked-list-cycle-ii/) +[力扣题目链接](https://leetcode.cn/problems/linked-list-cycle-ii/) 题意: 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 @@ -24,6 +24,8 @@ ## 思路 +为了易于大家理解,我录制讲解视频:[B站:把环形链表讲清楚! ](https://www.bilibili.com/video/BV1if4y1d7ob)。结合视频在看本篇题解,事半功倍。 + 这道题目,不仅考察对链表的操作,而且还需要一些数学运算。 主要考察两知识点: @@ -301,13 +303,13 @@ function detectCycle(head: ListNode | null): ListNode | null { let slowNode: ListNode | null = head, fastNode: ListNode | null = head; while (fastNode !== null && fastNode.next !== null) { - slowNode = (slowNode as ListNode).next; + slowNode = slowNode!.next; fastNode = fastNode.next.next; if (slowNode === fastNode) { slowNode = head; while (slowNode !== fastNode) { - slowNode = (slowNode as ListNode).next; - fastNode = (fastNode as ListNode).next; + slowNode = slowNode!.next; + fastNode = fastNode!.next; } return slowNode; } @@ -370,7 +372,31 @@ ListNode *detectCycle(ListNode *head) { } ``` - +Scala: +```scala +object Solution { + def detectCycle(head: ListNode): ListNode = { + var fast = head // 快指针 + var slow = head // 慢指针 + while (fast != null && fast.next != null) { + fast = fast.next.next // 快指针一次走两步 + slow = slow.next // 慢指针一次走一步 + // 如果相遇,fast快指针回到头 + if (fast == slow) { + fast = head + // 两个指针一步一步的走,第一次相遇的节点必是入环节点 + while (fast != slow) { + fast = fast.next + slow = slow.next + } + return fast + } + } + // 如果fast指向空值,必然无环返回null + null + } +} +``` -----------------------
diff --git a/problems/0143.重排链表.md b/problems/0143.重排链表.md index 790bcb48..c60fc0f9 100644 --- a/problems/0143.重排链表.md +++ b/problems/0143.重排链表.md @@ -6,6 +6,8 @@ # 143.重排链表 +[力扣题目链接](https://leetcode.cn/problems/reorder-list/submissions/) + ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210726160122.png) ## 思路 @@ -465,7 +467,81 @@ var reorderList = function(head, s = [], tmp) { } ``` +### TypeScript + +> 辅助数组法: + +```typescript +function reorderList(head: ListNode | null): void { + if (head === null) return; + const helperArr: ListNode[] = []; + let curNode: ListNode | null = head; + while (curNode !== null) { + helperArr.push(curNode); + curNode = curNode.next; + } + let node: ListNode = head; + let left: number = 1, + right: number = helperArr.length - 1; + let count: number = 0; + while (left <= right) { + if (count % 2 === 0) { + node.next = helperArr[right--]; + } else { + node.next = helperArr[left++]; + } + count++; + node = node.next; + } + node.next = null; +}; +``` + +> 分割链表法: + +```typescript +function reorderList(head: ListNode | null): void { + if (head === null || head.next === null) return; + let fastNode: ListNode = head, + slowNode: ListNode = head; + while (fastNode.next !== null && fastNode.next.next !== null) { + slowNode = slowNode.next!; + fastNode = fastNode.next.next; + } + let head1: ListNode | null = head; + // 反转后半部分链表 + let head2: ListNode | null = reverseList(slowNode.next); + // 分割链表 + slowNode.next = null; + /** + 直接在head1链表上进行插入 + head1 链表长度一定大于或等于head2, + 因此在下面的循环中,只要head2不为null, head1 一定不为null + */ + while (head2 !== null) { + const tempNode1: ListNode | null = head1!.next, + tempNode2: ListNode | null = head2.next; + head1!.next = head2; + head2.next = tempNode1; + head1 = tempNode1; + head2 = tempNode2; + } +}; +function reverseList(head: ListNode | null): ListNode | null { + let curNode: ListNode | null = head, + preNode: ListNode | null = null; + while (curNode !== null) { + const tempNode: ListNode | null = curNode.next; + curNode.next = preNode; + preNode = curNode; + curNode = tempNode; + } + return preNode; +} +``` + ### C + 方法三:反转链表 ```c //翻转链表 diff --git a/problems/0150.逆波兰表达式求值.md b/problems/0150.逆波兰表达式求值.md index 8dafa460..21bd8593 100644 --- a/problems/0150.逆波兰表达式求值.md +++ b/problems/0150.逆波兰表达式求值.md @@ -11,7 +11,7 @@ # 150. 逆波兰表达式求值 -[力扣题目链接](https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/) +[力扣题目链接](https://leetcode.cn/problems/evaluate-reverse-polish-notation/) 根据 逆波兰表示法,求表达式的值。 @@ -109,7 +109,7 @@ public: }; ``` -# 题外话 +## 题外话 我们习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。 @@ -128,7 +128,7 @@ public: -# 其他语言版本 +## 其他语言版本 java: @@ -136,19 +136,19 @@ java: class Solution { public int evalRPN(String[] tokens) { Deque stack = new LinkedList(); - for (int i = 0; i < tokens.length; ++i) { - if ("+".equals(tokens[i])) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等 + for (String s : tokens) { + if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等 stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理 - } else if ("-".equals(tokens[i])) { + } else if ("-".equals(s)) { stack.push(-stack.pop() + stack.pop()); - } else if ("*".equals(tokens[i])) { + } else if ("*".equals(s)) { stack.push(stack.pop() * stack.pop()); - } else if ("/".equals(tokens[i])) { + } else if ("/".equals(s)) { int temp1 = stack.pop(); int temp2 = stack.pop(); stack.push(temp2 / temp1); } else { - stack.push(Integer.valueOf(tokens[i])); + stack.push(Integer.valueOf(s)); } } return stack.pop(); @@ -360,5 +360,60 @@ public int EvalRPN(string[] tokens) { } ``` + +PHP: +```php +class Solution { + function evalRPN($tokens) { + $st = new SplStack(); + for($i = 0;$ipush($tokens[$i]); + }else{ + // 是符号进行运算 + $num1 = $st->pop(); + $num2 = $st->pop(); + if ($tokens[$i] == "+") $st->push($num2 + $num1); + if ($tokens[$i] == "-") $st->push($num2 - $num1); + if ($tokens[$i] == "*") $st->push($num2 * $num1); + // 注意处理小数部分 + if ($tokens[$i] == "/") $st->push(intval($num2 / $num1)); + } + } + return $st->pop(); + } +} +``` + +Scala: +```scala +object Solution { + import scala.collection.mutable + def evalRPN(tokens: Array[String]): Int = { + val stack = mutable.Stack[Int]() // 定义栈 + // 抽取运算操作,需要传递x,y,和一个函数 + def operator(x: Int, y: Int, f: (Int, Int) => Int): Int = f(x, y) + for (token <- tokens) { + // 模式匹配,匹配不同的操作符做什么样的运算 + token match { + // 最后一个参数 _+_,代表x+y,遵循Scala的函数至简原则,以下运算同理 + case "+" => stack.push(operator(stack.pop(), stack.pop(), _ + _)) + case "-" => stack.push(operator(stack.pop(), stack.pop(), -_ + _)) + case "*" => stack.push(operator(stack.pop(), stack.pop(), _ * _)) + case "/" => { + var pop1 = stack.pop() + var pop2 = stack.pop() + stack.push(operator(pop2, pop1, _ / _)) + } + case _ => stack.push(token.toInt) // 不是运算符就入栈 + } + } + // 最后返回栈顶,不需要加return关键字 + stack.pop() + } + +} +``` -----------------------
diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index d03de421..d0719469 100644 --- a/problems/0151.翻转字符串里的单词.md +++ b/problems/0151.翻转字符串里的单词.md @@ -10,7 +10,7 @@ # 151.翻转字符串里的单词 -[力扣题目链接](https://leetcode-cn.com/problems/reverse-words-in-a-string/) +[力扣题目链接](https://leetcode.cn/problems/reverse-words-in-a-string/) 给定一个字符串,逐个翻转字符串中的每个单词。 @@ -79,7 +79,7 @@ void removeExtraSpaces(string& s) { 逻辑很简单,从前向后遍历,遇到空格了就erase。 -如果不仔细琢磨一下erase的时间复杂读,还以为以上的代码是O(n)的时间复杂度呢。 +如果不仔细琢磨一下erase的时间复杂度,还以为以上的代码是O(n)的时间复杂度呢。 想一下真正的时间复杂度是多少,一个erase本来就是O(n)的操作,erase实现原理题目:[数组:就移除个元素很难么?](https://programmercarl.com/0027.移除元素.html),最优的算法来移除元素也要O(n)。 @@ -234,7 +234,7 @@ public: } void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。 - int slow = 0; //整体思想参考Leetcode: 27. 移除元素:https://leetcode-cn.com/problems/remove-element/ + int slow = 0; //整体思想参考Leetcode: 27. 移除元素:https://leetcode.cn/problems/remove-element/ for (int i = 0; i < s.size(); ++i) { // if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。 if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。 @@ -758,10 +758,164 @@ func reverseWord(_ s: inout [Character]) { } ``` +Scala: + +```scala +object Solution { + def reverseWords(s: String): String = { + var sb = removeSpace(s) // 移除多余的空格 + reverseString(sb, 0, sb.length - 1) // 翻转字符串 + reverseEachWord(sb) + sb.mkString + } + + // 移除多余的空格 + def removeSpace(s: String): Array[Char] = { + var start = 0 + var end = s.length - 1 + // 移除字符串前面的空格 + while (start < s.length && s(start) == ' ') start += 1 + // 移除字符串后面的空格 + while (end >= 0 && s(end) == ' ') end -= 1 + var sb = "" // String + // 当start小于等于end的时候,执行添加操作 + while (start <= end) { + var c = s(start) + // 当前字符不等于空,sb的最后一个字符不等于空的时候添加到sb + if (c != ' ' || sb(sb.length - 1) != ' ') { + sb ++= c.toString + } + start += 1 // 指针向右移动 + } + sb.toArray + } + + // 翻转字符串 + def reverseString(s: Array[Char], start: Int, end: Int): Unit = { + var (left, right) = (start, end) + while (left < right) { + var tmp = s(left) + s(left) = s(right) + s(right) = tmp + left += 1 + right -= 1 + } + } + + // 翻转每个单词 + def reverseEachWord(s: Array[Char]): Unit = { + var i = 0 + while (i < s.length) { + var j = i + 1 + // 向后迭代寻找每个单词的坐标 + while (j < s.length && s(j) != ' ') j += 1 + reverseString(s, i, j - 1) // 翻转每个单词 + i = j + 1 // i往后更新 + } + } +} +``` +PHP: +```php +function reverseWords($s) { + $this->removeExtraSpaces($s); + $this->reverseString($s, 0, strlen($s)-1); + // 将每个单词反转 + $start = 0; + for ($i = 0; $i <= strlen($s); $i++) { + // 到达空格或者串尾,说明一个单词结束。进行翻转。 + if ($i == strlen($s) || $s[$i] == ' ') { + // 翻转,注意是左闭右闭 []的翻转。 + $this->reverseString($s, $start, $i-1); + // +1: 单词与单词直接有个空格 + $start = $i + 1; + } + } + return $s; +} +// 移除多余空格 +function removeExtraSpaces(&$s){ + $slow = 0; + for ($i = 0; $i < strlen($s); $i++) { + if ($s[$i] != ' ') { + if ($slow != 0){ + $s[$slow++] = ' '; + } + while ($i < strlen($s) && $s[$i] != ' ') { + $s[$slow++] = $s[$i++]; + } + } + } + // 移动覆盖处理,丢弃多余的脏数据。 + $s = substr($s,0,$slow); + return ; +} +// 翻转字符串 +function reverseString(&$s, $start, $end) { + for ($i = $start, $j = $end; $i < $j; $i++, $j--) { + $tmp = $s[$i]; + $s[$i] = $s[$j]; + $s[$j] = $tmp; + } + return ; +} +``` +Rust: + +```Rust +// 根据C++版本二思路进行实现 +// 函数名根据Rust编译器建议由驼峰命名法改为蛇形命名法 +impl Solution { + pub fn reverse(s: &mut Vec, mut begin: usize, mut end: usize){ + while begin < end { + let temp = s[begin]; + s[begin] = s[end]; + s[end] = temp; + begin += 1; + end -= 1; + } +} +pub fn remove_extra_spaces(s: &mut Vec) { + let mut slow: usize = 0; + let len = s.len(); + // 注意这里不能用for循环,不然在里面那个while循环中对i的递增会失效 + let mut i: usize = 0; + while i < len { + if !s[i].is_ascii_whitespace() { + if slow != 0 { + s[slow] = ' '; + slow += 1; + } + while i < len && !s[i].is_ascii_whitespace() { + s[slow] = s[i]; + slow += 1; + i += 1; + } + } + i += 1; + } + s.resize(slow, ' '); + } + pub fn reverse_words(s: String) -> String { + let mut s = s.chars().collect::>(); + Self::remove_extra_spaces(&mut s); + let len = s.len(); + Self::reverse(&mut s, 0, len - 1); + let mut start = 0; + for i in 0..=len { + if i == len || s[i].is_ascii_whitespace() { + Self::reverse(&mut s, start, i - 1); + start = i + 1; + } + } + s.iter().collect::() + } +} +``` -----------------------
diff --git a/problems/0188.买卖股票的最佳时机IV.md b/problems/0188.买卖股票的最佳时机IV.md index 61c558a1..7ff19852 100644 --- a/problems/0188.买卖股票的最佳时机IV.md +++ b/problems/0188.买卖股票的最佳时机IV.md @@ -6,7 +6,7 @@ ## 188.买卖股票的最佳时机IV -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-iv/) 给定一个整数数组 prices ,它的第 i 个元素 prices[i] 是一支给定的股票在第 i 天的价格。 @@ -409,5 +409,27 @@ var maxProfit = function(k, prices) { }; ``` +TypeScript: + +```typescript +function maxProfit(k: number, prices: number[]): number { + const length: number = prices.length; + if (length === 0) return 0; + const dp: number[][] = new Array(length).fill(0) + .map(_ => new Array(k * 2 + 1).fill(0)); + for (let i = 1; i <= k; i++) { + dp[0][i * 2 - 1] = -prices[0]; + } + for (let i = 1; i < length; i++) { + for (let j = 1; j < 2 * k + 1; j++) { + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - 1] + Math.pow(-1, j) * prices[i]); + } + } + return dp[length - 1][2 * k]; +}; +``` + + + -----------------------
diff --git a/problems/0189.旋转数组.md b/problems/0189.旋转数组.md index 3ffed877..23092f9c 100644 --- a/problems/0189.旋转数组.md +++ b/problems/0189.旋转数组.md @@ -7,6 +7,8 @@ # 189. 旋转数组 +[力扣题目链接](https://leetcode.cn/problems/rotate-array/) + 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 进阶: @@ -160,6 +162,27 @@ var rotate = function (nums, k) { }; ``` +## TypeScript + +```typescript +function rotate(nums: number[], k: number): void { + const length: number = nums.length; + k %= length; + reverseByRange(nums, 0, length - 1); + reverseByRange(nums, 0, k - 1); + reverseByRange(nums, k, length - 1); +}; +function reverseByRange(nums: number[], left: number, right: number): void { + while (left < right) { + const temp = nums[left]; + nums[left] = nums[right]; + nums[right] = temp; + left++; + right--; + } +} +``` + ----------------------- diff --git a/problems/0198.打家劫舍.md b/problems/0198.打家劫舍.md index dfe1f3a0..ad660f27 100644 --- a/problems/0198.打家劫舍.md +++ b/problems/0198.打家劫舍.md @@ -6,7 +6,7 @@ ## 198.打家劫舍 -[力扣题目链接](https://leetcode-cn.com/problems/house-robber/) +[力扣题目链接](https://leetcode.cn/problems/house-robber/) 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 @@ -189,6 +189,29 @@ const rob = nums => { }; ``` +TypeScript: + +```typescript +function rob(nums: number[]): number { + /** + dp[i]: 前i个房屋能偷到的最大金额 + dp[0]: nums[0]; + dp[1]: max(nums[0], nums[1]); + ... + dp[i]: max(dp[i-1], dp[i-2]+nums[i]); + */ + const length: number = nums.length; + if (length === 1) return nums[0]; + const dp: number[] = []; + dp[0] = nums[0]; + dp[1] = Math.max(nums[0], nums[1]); + for (let i = 2; i < length; i++) { + dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]); + } + return dp[length - 1]; +}; +``` + diff --git a/problems/0202.快乐数.md b/problems/0202.快乐数.md index 51a79aff..d14ee770 100644 --- a/problems/0202.快乐数.md +++ b/problems/0202.快乐数.md @@ -10,7 +10,7 @@ # 第202题. 快乐数 -[力扣题目链接](https://leetcode-cn.com/problems/happy-number/) +[力扣题目链接](https://leetcode.cn/problems/happy-number/) 编写一个算法来判断一个数 n 是不是快乐数。 @@ -315,6 +315,36 @@ class Solution { } ``` +Rust: +```Rust +use std::collections::HashSet; +impl Solution { + pub fn get_sum(mut n: i32) -> i32 { + let mut sum = 0; + while n > 0 { + sum += (n % 10) * (n % 10); + n /= 10; + } + sum + } + + pub fn is_happy(n: i32) -> bool { + let mut n = n; + let mut set = HashSet::new(); + loop { + let sum = Self::get_sum(n); + if sum == 1 { + return true; + } + if set.contains(&sum) { + return false; + } else { set.insert(sum); } + n = sum; + } + } +} +``` + C: ```C typedef struct HashNodeTag { @@ -338,54 +368,41 @@ static inline int calcSquareSum(int num) { return sum; } -#define HASH_TABLE_SIZE (32) -bool isHappy(int n){ - int sum = n; - int index = 0; - bool bHappy = false; - bool bExit = false; - /* allocate the memory for hash table with chaining method*/ - HashNode ** hashTable = (HashNode **)calloc(HASH_TABLE_SIZE, sizeof(HashNode)); - - while(bExit == false) { - /* check if n has been calculated */ - index = hash(n, HASH_TABLE_SIZE); - - HashNode ** p = hashTable + index; - - while((*p) && (bExit == false)) { - /* Check if this num was calculated, if yes, this will be endless loop */ - if((*p)->key == n) { - bHappy = false; - bExit = true; - } - /* move to next node of the same index */ - p = &((*p)->next); - } - - /* put n intot hash table */ - HashNode * newNode = (HashNode *)malloc(sizeof(HashNode)); - newNode->key = n; - newNode->next = NULL; - - *p = newNode; - - sum = calcSquareSum(n); - if(sum == 1) { - bHappy = true; - bExit = true; - } - else { - n = sum; - - } +Scala: +```scala +object Solution { + // 引入mutable + import scala.collection.mutable + def isHappy(n: Int): Boolean = { + // 存放每次计算后的结果 + val set: mutable.HashSet[Int] = new mutable.HashSet[Int]() + var tmp = n // 因为形参是不可变量,所以需要找到一个临时变量 + // 开始进入循环 + while (true) { + val sum = getSum(tmp) // 获取这个数每个值的平方和 + if (sum == 1) return true // 如果最终等于 1,则返回true + // 如果set里面已经有这个值了,说明进入无限循环,可以返回false,否则添加这个值到set + if (set.contains(sum)) return false + else set.add(sum) + tmp = sum } + // 最终需要返回值,直接返回个false + false + } - return bHappy; -} + def getSum(n: Int): Int = { + var sum = 0 + var tmp = n + while (tmp != 0) { + sum += (tmp % 10) * (tmp % 10) + tmp = tmp / 10 + } + sum + } ``` + C#: ```csharp public class Solution { diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md index c34831b7..8af8675b 100644 --- a/problems/0203.移除链表元素.md +++ b/problems/0203.移除链表元素.md @@ -9,7 +9,7 @@ # 203.移除链表元素 -[力扣题目链接](https://leetcode-cn.com/problems/remove-linked-list-elements/) +[力扣题目链接](https://leetcode.cn/problems/remove-linked-list-elements/) 题意:删除链表中等于给定值 val 的所有节点。 @@ -28,6 +28,8 @@ # 思路 +为了方便大家理解,我特意录制了视频:[手把手带你学会操作链表,移除链表元素](https://www.bilibili.com/video/BV18B4y1s7R9),结合视频在看本题解,事半功倍。 + 这里以链表 1 4 2 4 来举例,移除元素4。 ![203_链表删除元素1](https://img-blog.csdnimg.cn/20210316095351161.png) @@ -145,6 +147,38 @@ public: ## 其他语言版本 C: +用原来的链表操作: +```c +struct ListNode* removeElements(struct ListNode* head, int val){ + struct ListNode* temp; + // 当头结点存在并且头结点的值等于val时 + while(head && head->val == val) { + temp = head; + // 将新的头结点设置为head->next并删除原来的头结点 + head = head->next; + free(temp); + } + + struct ListNode *cur = head; + // 当cur存在并且cur->next存在时 + // 此解法需要判断cur存在因为cur指向head。若head本身为NULL或者原链表中元素都为val的话,cur也会为NULL + while(cur && (temp = cur->next)) { + // 若cur->next的值等于val + if(temp->val == val) { + // 将cur->next设置为cur->next->next并删除cur->next + cur->next = temp->next; + free(temp); + } + // 若cur->next不等于val,则将cur后移一位 + else + cur = cur->next; + } + + // 返回头结点 + return head; +} +``` +设置一个虚拟头结点: ```c /** * Definition for singly-linked list. @@ -234,6 +268,27 @@ public ListNode removeElements(ListNode head, int val) { } return head; } +/** + * 不添加虚拟节点and pre Node方式 + * 时间复杂度 O(n) + * 空间复杂度 O(1) + * @param head + * @param val + * @return + */ +public ListNode removeElements(ListNode head, int val) { + while(head!=null && head.val==val){ + head = head.next; + } + ListNode curr = head; + while(curr!=null){ + while(curr.next!=null && curr.next.val == val){ + curr.next = curr.next.next; + } + curr = curr.next; + } + return head; +} ``` Python: @@ -342,18 +397,18 @@ function removeElements(head: ListNode | null, val: number): ListNode | null { ```typescript function removeElements(head: ListNode | null, val: number): ListNode | null { - let dummyHead = new ListNode(0, head); - let pre: ListNode = dummyHead, cur: ListNode | null = dummyHead.next; - // 删除非头部节点 + // 添加虚拟节点 + const data = new ListNode(0, head); + let pre = data, cur = data.next; while (cur) { if (cur.val === val) { - pre.next = cur.next; + pre.next = cur.next } else { pre = cur; } cur = cur.next; } - return head.next; + return data.next; }; ``` @@ -432,20 +487,86 @@ RUST: // } impl Solution { pub fn remove_elements(head: Option>, val: i32) -> Option> { - let mut head = head; - let mut dummy_head = ListNode::new(0); - let mut cur = &mut dummy_head; - while let Some(mut node) = head { - head = std::mem::replace(&mut node.next, None); - if node.val != val { - cur.next = Some(node); + let mut dummyHead = Box::new(ListNode::new(0)); + dummyHead.next = head; + let mut cur = dummyHead.as_mut(); + // 使用take()替换std::men::replace(&mut node.next, None)达到相同的效果,并且更普遍易读 + while let Some(nxt) = cur.next.take() { + if nxt.val == val { + cur.next = nxt.next; + } else { + cur.next = Some(nxt); cur = cur.next.as_mut().unwrap(); } } - dummy_head.next + dummyHead.next + } +} +``` +Scala: +```scala +/** + * Definition for singly-linked list. + * class ListNode(_x: Int = 0, _next: ListNode = null) { + * var next: ListNode = _next + * var x: Int = _x + * } + */ +object Solution { + def removeElements(head: ListNode, `val`: Int): ListNode = { + if (head == null) return head + var dummy = new ListNode(-1, head) // 定义虚拟头节点 + var cur = head // cur 表示当前节点 + var pre = dummy // pre 表示cur前一个节点 + while (cur != null) { + if (cur.x == `val`) { + // 相等,就删除那么cur的前一个节点pre执行cur的下一个 + pre.next = cur.next + } else { + // 不相等,pre就等于当前cur节点 + pre = cur + } + // 向下迭代 + cur = cur.next + } + // 最终返回dummy的下一个,就是链表的头 + dummy.next + } +} +``` +Kotlin: +```kotlin +/** + * Example: + * var li = ListNode(5) + * var v = li.`val` + * Definition for singly-linked list. + * class ListNode(var `val`: Int) { + * var next: ListNode? = null + * } + */ +class Solution { + fun removeElements(head: ListNode?, `val`: Int): ListNode? { + // 使用虚拟节点,令该节点指向head + var dummyNode = ListNode(-1) + dummyNode.next = head + // 使用cur遍历链表各个节点 + var cur = dummyNode + // 判断下个节点是否为空 + while (cur.next != null) { + // 符合条件,移除节点 + if (cur.next.`val` == `val`) { + cur.next = cur.next.next + } + // 不符合条件,遍历下一节点 + else { + cur = cur.next + } + } + // 注意:返回的不是虚拟节点 + return dummyNode.next } } ``` - -----------------------
diff --git a/problems/0205.同构字符串.md b/problems/0205.同构字符串.md index d4b71c59..43e2b0f0 100644 --- a/problems/0205.同构字符串.md +++ b/problems/0205.同构字符串.md @@ -7,7 +7,7 @@ # 205. 同构字符串 -[力扣题目链接](https://leetcode-cn.com/problems/isomorphic-strings/) +[力扣题目链接](https://leetcode.cn/problems/isomorphic-strings/) 给定两个字符串 s 和 t,判断它们是否是同构的。 @@ -156,6 +156,28 @@ var isIsomorphic = function(s, t) { }; ``` +## TypeScript + +```typescript +function isIsomorphic(s: string, t: string): boolean { + const helperMap1: Map = new Map(); + const helperMap2: Map = new Map(); + for (let i = 0, length = s.length; i < length; i++) { + let temp1: string | undefined = helperMap1.get(s[i]); + let temp2: string | undefined = helperMap2.get(t[i]); + if (temp1 === undefined && temp2 === undefined) { + helperMap1.set(s[i], t[i]); + helperMap2.set(t[i], s[i]); + } else if (temp1 !== t[i] || temp2 !== s[i]) { + return false; + } + } + return true; +}; +``` + + + -----------------------
diff --git a/problems/0206.翻转链表.md b/problems/0206.翻转链表.md index 941928ba..a6f4382a 100644 --- a/problems/0206.翻转链表.md +++ b/problems/0206.翻转链表.md @@ -9,7 +9,7 @@ # 206.反转链表 -[力扣题目链接](https://leetcode-cn.com/problems/reverse-linked-list/) +[力扣题目链接](https://leetcode.cn/problems/reverse-linked-list/) 题意:反转一个单链表。 @@ -19,6 +19,8 @@ # 思路 +本题我录制了B站视频,[帮你拿下反转链表 | LeetCode:206.反转链表](https://www.bilibili.com/video/BV1nB4y1i7eL),相信结合视频在看本篇题解,更有助于大家对链表的理解。 + 如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。 其实只需要改变链表的next指针的指向,直接将链表反转 ,而不用重新定义一个新的链表,如图所示: @@ -420,6 +422,41 @@ fun reverseList(head: ListNode?): ListNode? { return pre } ``` +```kotlin +/** + * Example: + * var li = ListNode(5) + * var v = li.`val` + * Definition for singly-linked list. + * class ListNode(var `val`: Int) { + * var next: ListNode? = null + * } + */ +class Solution { + fun reverseList(head: ListNode?): ListNode? { + // temp用来存储临时的节点 + var temp: ListNode? + // cur用来遍历链表 + var cur: ListNode? = head + // pre用来作为链表反转的工具 + // pre是比pre前一位的节点 + var pre: ListNode? = null + while (cur != null) { + // 临时存储原本cur的下一个节点 + temp = cur.next + // 使cur下一节点地址为它之前的 + cur.next = pre + // 之后随着cur的遍历移动pre + pre = cur; + // 移动cur遍历链表各个节点 + cur = temp; + } + // 由于开头使用pre为null,所以cur等于链表本身长度+1, + // 此时pre在cur前一位,所以此时pre为头节点 + return pre; + } +} +``` Swift: ```swift @@ -497,5 +534,58 @@ struct ListNode* reverseList(struct ListNode* head){ } ``` + + +PHP: +```php +// 双指针法: +function reverseList($head) { + $cur = $head; + $pre = NULL; + while($cur){ + $temp = $cur->next; + $cur->next = $pre; + $pre = $cur; + $cur = $temp; + } + return $pre; + } +``` + +Scala: +双指针法: +```scala +object Solution { + def reverseList(head: ListNode): ListNode = { + var pre: ListNode = null + var cur = head + while (cur != null) { + var tmp = cur.next + cur.next = pre + pre = cur + cur = tmp + } + pre + } +} +``` +递归法: +```scala +object Solution { + def reverseList(head: ListNode): ListNode = { + reverse(null, head) + } + + def reverse(pre: ListNode, cur: ListNode): ListNode = { + if (cur == null) { + return pre // 如果当前cur为空,则返回pre + } + val tmp: ListNode = cur.next + cur.next = pre + reverse(cur, tmp) // 此时cur成为前一个节点,tmp是当前节点 + } + +} +``` -----------------------
diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index fd72cf1b..570242f9 100644 --- a/problems/0209.长度最小的子数组.md +++ b/problems/0209.长度最小的子数组.md @@ -5,9 +5,9 @@

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

-## 209.长度最小的子数组 +# 209.长度最小的子数组 -[力扣题目链接](https://leetcode-cn.com/problems/minimum-size-subarray-sum/) +[力扣题目链接](https://leetcode.cn/problems/minimum-size-subarray-sum/) 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。 @@ -17,6 +17,9 @@ 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。 +# 思路 + +为了易于大家理解,我特意录制了B站视频[拿下滑动窗口! | LeetCode 209 长度最小的子数组](https://www.bilibili.com/video/BV1tZ4y1q7XE) ## 暴力解法 @@ -47,8 +50,8 @@ public: } }; ``` -时间复杂度:O(n^2) -空间复杂度:O(1) +* 时间复杂度:O(n^2) +* 空间复杂度:O(1) ## 滑动窗口 @@ -56,6 +59,20 @@ public: 所谓滑动窗口,**就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果**。 +在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。 + +那么滑动窗口如何用一个for循环来完成这个操作呢。 + +首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。 + +如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置? + +此时难免再次陷入 暴力解法的怪圈。 + +所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。 + +那么问题来了, 滑动窗口的起始位置如何移动呢? + 这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程: ![209.长度最小的子数组](https://code-thinking.cdn.bcebos.com/gifs/209.%E9%95%BF%E5%BA%A6%E6%9C%80%E5%B0%8F%E7%9A%84%E5%AD%90%E6%95%B0%E7%BB%84.gif) @@ -74,7 +91,7 @@ public: 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。 -窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,窗口的起始位置设置为数组的起始位置就可以了。 +窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。 解题的关键在于 窗口的起始位置如何移动,如图所示: @@ -107,17 +124,17 @@ public: }; ``` -时间复杂度:O(n) -空间复杂度:O(1) +* 时间复杂度:O(n) +* 空间复杂度:O(1) **一些录友会疑惑为什么时间复杂度是O(n)**。 -不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被被操作两次,所以时间复杂度是 2 × n 也就是O(n)。 +不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。 ## 相关题目推荐 -* [904.水果成篮](https://leetcode-cn.com/problems/fruit-into-baskets/) -* [76.最小覆盖子串](https://leetcode-cn.com/problems/minimum-window-substring/) +* [904.水果成篮](https://leetcode.cn/problems/fruit-into-baskets/) +* [76.最小覆盖子串](https://leetcode.cn/problems/minimum-window-substring/) @@ -162,8 +179,27 @@ class Solution: index += 1 return 0 if res==float("inf") else res ``` - - +```python3 +#滑动窗口 +class Solution: + def minSubArrayLen(self, target: int, nums: List[int]) -> int: + if nums is None or len(nums)==0: + return 0 + lenf=len(nums)+1 + total=0 + i=j=0 + while (j=target): + lenf=min(lenf,j-i) + total=total-nums[i] + i+=1 + if lenf==len(nums)+1: + return 0 + else: + return lenf +``` Go: ```go func minSubArrayLen(target int, nums []int) int { @@ -400,6 +436,107 @@ class Solution { } } ``` +滑动窗口 +```kotlin +class Solution { + fun minSubArrayLen(target: Int, nums: IntArray): Int { + // 左边界 和 右边界 + var left: Int = 0 + var right: Int = 0 + // sum 用来记录和 + var sum: Int = 0 + // result记录一个固定值,便于判断是否存在的这样的数组 + var result: Int = Int.MAX_VALUE + // subLenth记录长度 + var subLength = Int.MAX_VALUE + + while (right < nums.size) { + // 从数组首元素开始逐次求和 + sum += nums[right++] + // 判断 + while (sum >= target) { + var temp = right - left + // 每次和上一次比较求出最小数组长度 + subLength = if (subLength > temp) temp else subLength + // sum减少,左边界右移 + sum -= nums[left++] + } + } + // 如果subLength为初始值,则说明长度为0,否则返回subLength + return if(subLength == result) 0 else subLength + } +} +``` +Scala: + +滑动窗口: +```scala +object Solution { + def minSubArrayLen(target: Int, nums: Array[Int]): Int = { + var result = Int.MaxValue // 返回结果,默认最大值 + var left = 0 // 慢指针,当sum>=target,向右移动 + var sum = 0 // 窗口值的总和 + for (right <- 0 until nums.length) { + sum += nums(right) + while (sum >= target) { + result = math.min(result, right - left + 1) // 产生新结果 + sum -= nums(left) // 左指针移动,窗口总和减去左指针的值 + left += 1 // 左指针向右移动 + } + } + // 相当于三元运算符,return关键字可以省略 + if (result == Int.MaxValue) 0 else result + } +} +``` + +暴力解法: +```scala +object Solution { + def minSubArrayLen(target: Int, nums: Array[Int]): Int = { + import scala.util.control.Breaks + var res = Int.MaxValue + var subLength = 0 + for (i <- 0 until nums.length) { + var sum = 0 + Breaks.breakable( + for (j <- i until nums.length) { + sum += nums(j) + if (sum >= target) { + subLength = j - i + 1 + res = math.min(subLength, res) + Breaks.break() + } + } + ) + } + // 相当于三元运算符 + if (res == Int.MaxValue) 0 else res + } +} +``` +C#: +```csharp +public class Solution { + public int MinSubArrayLen(int s, int[] nums) { + int n = nums.Length; + int ans = int.MaxValue; + int start = 0, end = 0; + int sum = 0; + while (end < n) { + sum += nums[end]; + while (sum >= s) + { + ans = Math.Min(ans, end - start + 1); + sum -= nums[start]; + start++; + } + end++; + } + return ans == int.MaxValue ? 0 : ans; + } +} +``` -----------------------
diff --git a/problems/0213.打家劫舍II.md b/problems/0213.打家劫舍II.md index 8e569e46..d6b53a24 100644 --- a/problems/0213.打家劫舍II.md +++ b/problems/0213.打家劫舍II.md @@ -6,7 +6,7 @@ ## 213.打家劫舍II -[力扣题目链接](https://leetcode-cn.com/problems/house-robber-ii/) +[力扣题目链接](https://leetcode.cn/problems/house-robber-ii/) 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。 @@ -165,7 +165,30 @@ const robRange = (nums, start, end) => { return dp[end] } ``` +TypeScript: + +```typescript +function rob(nums: number[]): number { + const length: number = nums.length; + if (length === 0) return 0; + if (length === 1) return nums[0]; + return Math.max(robRange(nums, 0, length - 2), + robRange(nums, 1, length - 1)); +}; +function robRange(nums: number[], start: number, end: number): number { + if (start === end) return nums[start]; + const dp: number[] = []; + 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 - 1], dp[i - 2] + nums[i]); + } + return dp[end]; +} +``` + Go: + ```go // 打家劫舍Ⅱ 动态规划 // 时间复杂度O(n) 空间复杂度O(n) diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index 32b1347e..1ef278ff 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -11,7 +11,7 @@ # 216.组合总和III -[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-iii/) +[力扣题目链接](https://leetcode.cn/problems/combination-sum-iii/) 找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 @@ -212,7 +212,7 @@ public: # 总结 -开篇就介绍了本题与[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)的区别,相对来说加了元素总和的限制,如果做完[回溯算法:求组合问题!](https://programmercarl.com/0077.组合.html)再做本题在合适不过。 +开篇就介绍了本题与[77.组合](https://programmercarl.com/0077.组合.html)的区别,相对来说加了元素总和的限制,如果做完[77.组合](https://programmercarl.com/0077.组合.html)再做本题在合适不过。 分析完区别,依然把问题抽象为树形结构,按照回溯三部曲进行讲解,最后给出剪枝的优化。 @@ -360,39 +360,30 @@ func backTree(n,k,startIndex int,track *[]int,result *[][]int){ ## javaScript ```js -// 等差数列 -var maxV = k => k * (9 + 10 - k) / 2; -var minV = k => k * (1 + k) / 2; +/** + * @param {number} k + * @param {number} n + * @return {number[][]} + */ 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)); + const backtrack = (start) => { + const l = path.length; + if (l === k) { + const sum = path.reduce((a, b) => a + b); + if (sum === n) { + res.push([...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); + for (let i = start; i <= 9 - (k - l) + 1; i++) { + path.push(i); + backtrack(i + 1); path.pop(); - sum -= a; } } + let res = [], path = []; + backtrack(1); + return res; }; ``` @@ -420,6 +411,35 @@ function combinationSum3(k: number, n: number): number[][] { }; ``` +## Rust + +```Rust +impl Solution { + fn backtracking(result: &mut Vec>, path:&mut Vec, targetSum:i32, k: i32, mut sum: i32, startIndex: i32) { + let len = path.len() as i32; + if len == k { + if sum == targetSum { + result.push(path.to_vec()); + } + return; + } + for i in startIndex..=9 { + sum += i; + path.push(i); + Self::backtracking(result, path, targetSum, k, sum, i+1); + sum -= i; + path.pop(); + } + } + pub fn combination_sum3(k: i32, n: i32) -> Vec> { + let mut result: Vec> = Vec::new(); + let mut path: Vec = Vec::new(); + Self::backtracking(&mut result, &mut path, n, k, 0, 1); + result + } +} +``` + ## C ```c @@ -511,5 +531,35 @@ func combinationSum3(_ count: Int, _ targetSum: Int) -> [[Int]] { } ``` +## Scala + +```scala +object Solution { + import scala.collection.mutable + def combinationSum3(k: Int, n: Int): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + + def backtracking(k: Int, n: Int, sum: Int, startIndex: Int): Unit = { + if (sum > n) return // 剪枝,如果sum>目标和,就返回 + if (sum == n && path.size == k) { + result.append(path.toList) + return + } + // 剪枝 + for (i <- startIndex to (9 - (k - path.size) + 1)) { + path.append(i) + backtracking(k, n, sum + i, i + 1) + path = path.take(path.size - 1) + } + } + + backtracking(k, n, 0, 1) // 调用递归方法 + result.toList // 最终返回结果集的List形式 + } +} +``` + + -----------------------
diff --git a/problems/0222.完全二叉树的节点个数.md b/problems/0222.完全二叉树的节点个数.md index ba7acc5a..e2825cfb 100644 --- a/problems/0222.完全二叉树的节点个数.md +++ b/problems/0222.完全二叉树的节点个数.md @@ -7,7 +7,7 @@ # 222.完全二叉树的节点个数 -[力扣题目链接](https://leetcode-cn.com/problems/count-complete-tree-nodes/) +[力扣题目链接](https://leetcode.cn/problems/count-complete-tree-nodes/) 给出一个完全二叉树,求出该树的节点个数。 @@ -646,5 +646,68 @@ func countNodes(_ root: TreeNode?) -> Int { } ``` +## Scala + +递归: +```scala +object Solution { + def countNodes(root: TreeNode): Int = { + if(root == null) return 0 + 1 + countNodes(root.left) + countNodes(root.right) + } +} +``` + +层序遍历: +```scala +object Solution { + import scala.collection.mutable + def countNodes(root: TreeNode): Int = { + if (root == null) return 0 + val queue = mutable.Queue[TreeNode]() + var node = 0 + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + for (i <- 0 until len) { + node += 1 + val curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + } + node + } +} +``` + +利用完全二叉树性质: +```scala +object Solution { + def countNodes(root: TreeNode): Int = { + if (root == null) return 0 + var leftNode = root.left + var rightNode = root.right + // 向左向右往下探 + var leftDepth = 0 + while (leftNode != null) { + leftDepth += 1 + leftNode = leftNode.left + } + var rightDepth = 0 + while (rightNode != null) { + rightDepth += 1 + rightNode = rightNode.right + } + // 如果相等就是一个满二叉树 + if (leftDepth == rightDepth) { + return (2 << leftDepth) - 1 + } + // 如果不相等就不是一个完全二叉树,继续向下递归 + countNodes(root.left) + countNodes(root.right) + 1 + } +} +``` + -----------------------
diff --git a/problems/0225.用队列实现栈.md b/problems/0225.用队列实现栈.md index 5d902fb1..17c50cd9 100644 --- a/problems/0225.用队列实现栈.md +++ b/problems/0225.用队列实现栈.md @@ -10,7 +10,7 @@ # 225. 用队列实现栈 -[力扣题目链接](https://leetcode-cn.com/problems/implement-stack-using-queues/) +[力扣题目链接](https://leetcode.cn/problems/implement-stack-using-queues/) 使用队列实现栈的下列操作: @@ -815,6 +815,90 @@ class MyStack { } } ``` +Scala: +使用两个队列模拟栈: +```scala +import scala.collection.mutable + +class MyStack() { + + val queue1 = new mutable.Queue[Int]() + val queue2 = new mutable.Queue[Int]() + + def push(x: Int) { + queue1.enqueue(x) + } + + def pop(): Int = { + var size = queue1.size + // 将queue1中的每个元素都移动到queue2 + for (i <- 0 until size - 1) { + queue2.enqueue(queue1.dequeue()) + } + var res = queue1.dequeue() + // 再将queue2中的每个元素都移动到queue1 + while (!queue2.isEmpty) { + queue1.enqueue(queue2.dequeue()) + } + res + } + + def top(): Int = { + var size = queue1.size + for (i <- 0 until size - 1) { + queue2.enqueue(queue1.dequeue()) + } + var res = queue1.dequeue() + while (!queue2.isEmpty) { + queue1.enqueue(queue2.dequeue()) + } + // 最终还需要把res送进queue1 + queue1.enqueue(res) + res + } + + def empty(): Boolean = { + queue1.isEmpty + } +} +``` +使用一个队列模拟: +```scala +import scala.collection.mutable + +class MyStack() { + + val queue = new mutable.Queue[Int]() + + def push(x: Int) { + queue.enqueue(x) + } + + def pop(): Int = { + var size = queue.size + for (i <- 0 until size - 1) { + queue.enqueue(queue.head) // 把头添到队列最后 + queue.dequeue() // 再出队 + } + queue.dequeue() + } + + def top(): Int = { + var size = queue.size + var res = 0 + for (i <- 0 until size) { + queue.enqueue(queue.head) // 把头添到队列最后 + res = queue.dequeue() // 再出队 + } + res + } + + def empty(): Boolean = { + queue.isEmpty + } + } +``` + C#: ```csharp @@ -849,7 +933,85 @@ public class MyStack { return queue1.Count == 0; } } +``` +PHP +> 双对列 +```php +// SplQueue 类通过使用一个双向链表来提供队列的主要功能。(PHP 5 >= 5.3.0, PHP 7, PHP 8) +// https://www.php.net/manual/zh/class.splqueue.php +class MyStack { + public $queueMain; // 保存数据 + public $queueTmp; // 辅助作用 + + function __construct() { + $this->queueMain=new SplQueue(); + $this->queueTmp=new SplQueue(); + } + + // queueMain: 1,2,3 <= add + function push($x) { + $this->queueMain->enqueue($x); + } + + function pop() { + $qmSize = $this->queueMain->Count(); + $qmSize --; + // queueMain: 3,2,1 => pop =>2,1 => add => 2,1 :queueTmp + while($qmSize --){ + $this->queueTmp->enqueue($this->queueMain->dequeue()); + } + // queueMain: 3 + $val = $this->queueMain->dequeue(); + // queueMain <= queueTmp + $this->queueMain = $this->queueTmp; + // 清空queueTmp,下次使用 + $this->queueTmp = new SplQueue(); + return $val; + } + + function top() { + // 底层是双链表实现:从双链表的末尾查看节点 + return $this->queueMain->top(); + } + + function empty() { + return $this->queueMain->isEmpty(); + } +} +``` +> 单对列 +```php +class MyStack { + public $queue; + + function __construct() { + $this->queue=new SplQueue(); + } + + function push($x) { + $this->queue->enqueue($x); + } + + function pop() { + $qmSize = $this->queue->Count(); + $qmSize --; + //queue: 3,2,1 => pop =>2,1 => add => 2,1,3 :queue + while($qmSize --){ + $this->queue->enqueue($this->queue->dequeue()); + } + $val = $this->queue->dequeue(); + return $val; + } + + function top() { + return $this->queue->top(); + } + + function empty() { + return $this->queue->isEmpty(); + } +} ``` -----------------------
diff --git a/problems/0226.翻转二叉树.md b/problems/0226.翻转二叉树.md index a3ebe24d..83d20df8 100644 --- a/problems/0226.翻转二叉树.md +++ b/problems/0226.翻转二叉树.md @@ -7,7 +7,7 @@ # 226.翻转二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/invert-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/invert-binary-tree/) 翻转一棵二叉树。 @@ -368,9 +368,7 @@ func invertTree(root *TreeNode) *TreeNode { if root ==nil{ return nil } - temp:=root.Left - root.Left=root.Right - root.Right=temp + root.Left,root.Right=root.Right,root.Left//交换 invertTree(root.Left) invertTree(root.Right) @@ -472,25 +470,14 @@ func invertTree(root *TreeNode) *TreeNode { 使用递归版本的前序遍历 ```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; + // 终止条件 + if (!root) { + return null; } - //确定递归函数的参数和返回值inverTree=function(root) - //确定终止条件 - if(root===null){ - return root; - } - //确定节点处理逻辑 交换 - inverNode(root.left,root.right); - invertTree(root.left); - invertTree(root.right); + // 交换左右节点 + const rightNode = root.right; + root.right = invertTree(root.left); + root.left = invertTree(rightNode); return root; }; ``` @@ -820,5 +807,53 @@ func invertTree(_ root: TreeNode?) -> TreeNode? { } ``` +### Scala + +深度优先遍历(前序遍历): +```scala +object Solution { + def invertTree(root: TreeNode): TreeNode = { + if (root == null) return root + // 递归 + def process(node: TreeNode): Unit = { + if (node == null) return + // 翻转节点 + val curNode = node.left + node.left = node.right + node.right = curNode + process(node.left) + process(node.right) + } + process(root) + root + } +} +``` + +广度优先遍历(层序遍历): +```scala +object Solution { + import scala.collection.mutable + def invertTree(root: TreeNode): TreeNode = { + if (root == null) return root + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + while (!queue.isEmpty) { + val len = queue.size + for (i <- 0 until len) { + var curNode = queue.dequeue() + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + // 翻转 + var tmpNode = curNode.left + curNode.left = curNode.right + curNode.right = tmpNode + } + } + root + } +} +``` + -----------------------
diff --git a/problems/0232.用栈实现队列.md b/problems/0232.用栈实现队列.md index d58dc55f..dcad6a79 100644 --- a/problems/0232.用栈实现队列.md +++ b/problems/0232.用栈实现队列.md @@ -9,7 +9,7 @@ # 232.用栈实现队列 -[力扣题目链接](https://leetcode-cn.com/problems/implement-queue-using-stacks/) +[力扣题目链接](https://leetcode.cn/problems/implement-queue-using-stacks/) 使用栈实现队列的下列操作: @@ -496,6 +496,7 @@ void myQueueFree(MyQueue* obj) { } ``` + C#: ```csharp public class MyQueue { @@ -537,5 +538,94 @@ public class MyQueue { ``` + + +PHP: +```php +// SplStack 类通过使用一个双向链表来提供栈的主要功能。[PHP 5 >= 5.3.0, PHP 7, PHP 8] +// https://www.php.net/manual/zh/class.splstack.php +class MyQueue { + // 双栈模拟队列:In栈存储数据;Out栈辅助处理 + private $stackIn; + private $stackOut; + + function __construct() { + $this->stackIn = new SplStack(); + $this->stackOut = new SplStack(); + } + + // In: 1 2 3 <= push + function push($x) { + $this->stackIn->push($x); + } + + function pop() { + $this->peek(); + return $this->stackOut->pop(); + } + + function peek() { + if($this->stackOut->isEmpty()){ + $this->shift(); + } + return $this->stackOut->top(); + } + + function empty() { + return $this->stackOut->isEmpty() && $this->stackIn->isEmpty(); + } + + // 如果Out栈为空,把In栈数据压入Out栈 + // In: 1 2 3 => pop push => 1 2 3 :Out + private function shift(){ + while(!$this->stackIn->isEmpty()){ + $this->stackOut->push($this->stackIn->pop()); + } + } + } +``` + +Scala: +```scala +class MyQueue() { + import scala.collection.mutable + val stackIn = mutable.Stack[Int]() // 负责出栈 + val stackOut = mutable.Stack[Int]() // 负责入栈 + + // 添加元素 + def push(x: Int) { + stackIn.push(x) + } + + // 复用代码,如果stackOut为空就把stackIn的所有元素都压入StackOut + def dumpStackIn(): Unit = { + if (!stackOut.isEmpty) return + while (!stackIn.isEmpty) { + stackOut.push(stackIn.pop()) + } + } + + // 弹出元素 + def pop(): Int = { + dumpStackIn() + stackOut.pop() + } + + // 获取队头 + def peek(): Int = { + dumpStackIn() + val res: Int = stackOut.pop() + stackOut.push(res) + res + } + + // 判断是否为空 + def empty(): Boolean = { + stackIn.isEmpty && stackOut.isEmpty + } + +} +``` + -----------------------
diff --git a/problems/0234.回文链表.md b/problems/0234.回文链表.md index db910d4e..1f515623 100644 --- a/problems/0234.回文链表.md +++ b/problems/0234.回文链表.md @@ -7,7 +7,7 @@ # 234.回文链表 -[力扣题目链接](https://leetcode-cn.com/problems/palindrome-linked-list/) +[力扣题目链接](https://leetcode.cn/problems/palindrome-linked-list/) 请判断一个链表是否为回文链表。 @@ -218,65 +218,115 @@ class Solution { ```python #数组模拟 class Solution: - def isPalindrome(self, head: ListNode) -> bool: - length = 0 - tmp = head - while tmp: #求链表长度 - length += 1 - tmp = tmp.next - - result = [0] * length - tmp = head - index = 0 - while tmp: #链表元素加入数组 - result[index] = tmp.val - index += 1 - tmp = tmp.next - - i, j = 0, length - 1 - while i < j: # 判断回文 - if result[i] != result[j]: + def isPalindrome(self, head: Optional[ListNode]) -> bool: + list=[] + while head: + list.append(head.val) + head=head.next + l,r=0, len(list)-1 + while l<=r: + if list[l]!=list[r]: return False - i += 1 - j -= 1 - return True - + l+=1 + r-=1 + return True + #反转后半部分链表 class Solution: - def isPalindrome(self, head: ListNode) -> bool: - if head == None or head.next == None: - return True - slow, fast = head, head - while fast and fast.next: - pre = slow - slow = slow.next - fast = fast.next.next - - pre.next = None # 分割链表 - cur1 = head # 前半部分 - cur2 = self.reverseList(slow) # 反转后半部分,总链表长度如果是奇数,cur2比cur1多一个节点 - while cur1: - if cur1.val != cur2.val: - return False - cur1 = cur1.next - cur2 = cur2.next - return True + def isPalindrome(self, head: Optional[ListNode]) -> bool: + fast = slow = head - def reverseList(self, head: ListNode) -> ListNode: - cur = head - pre = None - while(cur!=None): - temp = cur.next # 保存一下cur的下一个节点 - cur.next = pre # 反转 - pre = cur - cur = temp - return pre + # find mid point which including (first) mid point into the first half linked list + while fast and fast.next: + fast = fast.next.next + slow = slow.next + node = None + + # reverse second half linked list + while slow: + slow.next, slow, node = node, slow.next, slow + + # compare reversed and original half; must maintain reversed linked list is shorter than 1st half + while node: + if node.val != head.val: + return False + node = node.next + head = head.next + return True ``` -## Go +### Go ```go +/** + * Definition for singly-linked list. + * type ListNode struct { + * Val int + * Next *ListNode + * } + */ +//方法一,使用数组 +func isPalindrome(head *ListNode) bool{ + //计算切片长度,避免切片频繁扩容 + cur,ln:=head,0 + for cur!=nil{ + ln++ + cur=cur.Next + } + nums:=make([]int,ln) + index:=0 + for head!=nil{ + nums[index]=head.Val + index++ + head=head.Next + } + //比较回文切片 + for i,j:=0,ln-1;i<=j;i,j=i+1,j-1{ + if nums[i]!=nums[j]{return false} + } + return true +} +// 方法二,快慢指针 +func isPalindrome(head *ListNode) bool { + if head==nil&&head.Next==nil{return true} + //慢指针,找到链表中间分位置,作为分割 + slow:=head + fast:=head + //记录慢指针的前一个节点,用来分割链表 + pre:=head + for fast!=nil && fast.Next!=nil{ + pre=slow + slow=slow.Next + fast=fast.Next.Next + } + //分割链表 + pre.Next=nil + //前半部分 + cur1:=head + //反转后半部分,总链表长度如果是奇数,cur2比cur1多一个节点 + cur2:=ReverseList(slow) + + //开始两个链表的比较 + for cur1!=nil{ + if cur1.Val!=cur2.Val{return false} + cur1=cur1.Next + cur2=cur2.Next + } + return true +} +//反转链表 +func ReverseList(head *ListNode) *ListNode{ + var pre *ListNode + cur:=head + for cur!=nil{ + tmp:=cur.Next + cur.Next=pre + pre=cur + cur=tmp + } + return pre +} ``` ### JavaScript @@ -319,6 +369,63 @@ var isPalindrome = function(head) { }; ``` +### TypeScript + +> 数组模拟 + +```typescript +function isPalindrome(head: ListNode | null): boolean { + const helperArr: number[] = []; + let curNode: ListNode | null = head; + while (curNode !== null) { + helperArr.push(curNode.val); + curNode = curNode.next; + } + let left: number = 0, + right: number = helperArr.length - 1; + while (left < right) { + if (helperArr[left++] !== helperArr[right--]) return false; + } + return true; +}; +``` + +> 反转后半部分链表 + +```typescript +function isPalindrome(head: ListNode | null): boolean { + if (head === null || head.next === null) return true; + let fastNode: ListNode | null = head, + slowNode: ListNode = head, + preNode: ListNode = head; + while (fastNode !== null && fastNode.next !== null) { + preNode = slowNode; + slowNode = slowNode.next!; + fastNode = fastNode.next.next; + } + preNode.next = null; + let cur1: ListNode | null = head; + let cur2: ListNode | null = reverseList(slowNode); + while (cur1 !== null) { + if (cur1.val !== cur2!.val) return false; + cur1 = cur1.next; + cur2 = cur2!.next; + } + return true; +}; +function reverseList(head: ListNode | null): ListNode | null { + let curNode: ListNode | null = head, + preNode: ListNode | null = null; + while (curNode !== null) { + let tempNode: ListNode | null = curNode.next; + curNode.next = preNode; + preNode = curNode; + curNode = tempNode; + } + return preNode; +} +``` + ----------------------- diff --git a/problems/0235.二叉搜索树的最近公共祖先.md b/problems/0235.二叉搜索树的最近公共祖先.md index f7f1427a..ee86d02f 100644 --- a/problems/0235.二叉搜索树的最近公共祖先.md +++ b/problems/0235.二叉搜索树的最近公共祖先.md @@ -7,7 +7,7 @@ # 235. 二叉搜索树的最近公共祖先 -[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/) +[力扣题目链接](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-search-tree/) 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 @@ -381,7 +381,36 @@ function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: Tree }; ``` +## Scala +递归: + +```scala +object Solution { + def lowestCommonAncestor(root: TreeNode, p: TreeNode, q: TreeNode): TreeNode = { + // scala中每个关键字都有其返回值,于是可以不写return + if (root.value > p.value && root.value > q.value) lowestCommonAncestor(root.left, p, q) + else if (root.value < p.value && root.value < q.value) lowestCommonAncestor(root.right, p, q) + else root + } +} +``` + +迭代: + +```scala +object Solution { + def lowestCommonAncestor(root: TreeNode, p: TreeNode, q: TreeNode): TreeNode = { + var curNode = root // 因为root是不可变量,所以要赋值给curNode一个可变量 + while(curNode != null){ + if(curNode.value > p.value && curNode.value > q.value) curNode = curNode.left + else if(curNode.value < p.value && curNode.value < q.value) curNode = curNode.right + else return curNode + } + null + } +} +``` ----------------------- diff --git a/problems/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md index 69a6d0d6..c3e2ae7a 100644 --- a/problems/0236.二叉树的最近公共祖先.md +++ b/problems/0236.二叉树的最近公共祖先.md @@ -9,7 +9,7 @@ # 236. 二叉树的最近公共祖先 -[力扣题目链接](https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/) 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 @@ -343,7 +343,25 @@ function lowestCommonAncestor(root: TreeNode | null, p: TreeNode | null, q: Tree }; ``` +## Scala +```scala +object Solution { + def lowestCommonAncestor(root: TreeNode, p: TreeNode, q: TreeNode): TreeNode = { + // 递归结束条件 + if (root == null || root == p || root == q) { + return root + } + + var left = lowestCommonAncestor(root.left, p, q) + var right = lowestCommonAncestor(root.right, p, q) + + if (left != null && right != null) return root + if (left == null) return right + left + } +} +``` -----------------------
diff --git a/problems/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md index 35ca1eed..474b3a75 100644 --- a/problems/0239.滑动窗口最大值.md +++ b/problems/0239.滑动窗口最大值.md @@ -10,7 +10,7 @@ # 239. 滑动窗口最大值 -[力扣题目链接](https://leetcode-cn.com/problems/sliding-window-maximum/) +[力扣题目链接](https://leetcode.cn/problems/sliding-window-maximum/) 给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 @@ -630,6 +630,131 @@ func maxSlidingWindow(_ nums: [Int], _ k: Int) -> [Int] { return result } ``` +Scala: +```scala +import scala.collection.mutable.ArrayBuffer +object Solution { + def maxSlidingWindow(nums: Array[Int], k: Int): Array[Int] = { + var len = nums.length - k + 1 // 滑动窗口长度 + var res: Array[Int] = new Array[Int](len) // 声明存储结果的数组 + var index = 0 // 结果数组指针 + val queue: MyQueue = new MyQueue // 自定义队列 + // 将前k个添加到queue + for (i <- 0 until k) { + queue.add(nums(i)) + } + res(index) = queue.peek // 第一个滑动窗口的最大值 + index += 1 + for (i <- k until nums.length) { + queue.poll(nums(i - k)) // 首先移除第i-k个元素 + queue.add(nums(i)) // 添加当前数字到队列 + res(index) = queue.peek() // 赋值 + index+=1 + } + // 最终返回res,return关键字可以省略 + res + } + } + +class MyQueue { + var queue = ArrayBuffer[Int]() + + // 移除元素,如果传递进来的跟队头相等,那么移除 + def poll(value: Int): Unit = { + if (!queue.isEmpty && queue.head == value) { + queue.remove(0) + } + } + + // 添加元素,当队尾大于当前元素就删除 + def add(value: Int): Unit = { + while (!queue.isEmpty && value > queue.last) { + queue.remove(queue.length - 1) + } + queue.append(value) + } + + def peek(): Int = queue.head +} +``` + + +PHP: +```php +class Solution { + /** + * @param Integer[] $nums + * @param Integer $k + * @return Integer[] + */ + function maxSlidingWindow($nums, $k) { + $myQueue = new MyQueue(); + // 先将前k的元素放进队列 + for ($i = 0; $i < $k; $i++) { + $myQueue->push($nums[$i]); + } + + $result = []; + $result[] = $myQueue->max(); // result 记录前k的元素的最大值 + + for ($i = $k; $i < count($nums); $i++) { + $myQueue->pop($nums[$i - $k]); // 滑动窗口移除最前面元素 + $myQueue->push($nums[$i]); // 滑动窗口前加入最后面的元素 + $result[]= $myQueue->max(); // 记录对应的最大值 + } + return $result; + } + +} + +// 单调对列构建 +class MyQueue{ + private $queue; + + public function __construct(){ + $this->queue = new SplQueue(); //底层是双向链表实现。 + } + + public function pop($v){ + // 判断当前对列是否为空 + // 比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。 + // bottom 从链表前端查看元素, dequeue 从双向链表的开头移动一个节点 + if(!$this->queue->isEmpty() && $v == $this->queue->bottom()){ + $this->queue->dequeue(); //弹出队列 + } + } + + public function push($v){ + // 判断当前对列是否为空 + // 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。 + // 这样就保持了队列里的数值是单调从大到小的了。 + while (!$this->queue->isEmpty() && $v > $this->queue->top()) { + $this->queue->pop(); // pop从链表末尾弹出一个元素, + } + $this->queue->enqueue($v); + } + + // 查询当前队列里的最大值 直接返回队首 + public function max(){ + // bottom 从链表前端查看元素, top从链表末尾查看元素 + return $this->queue->bottom(); + } + + // 辅助理解: 打印队列元素 + public function println(){ + // "迭代器移动到链表头部": 可理解为从头遍历链表元素做准备。 + // 【PHP中没有指针概念,所以就没说指针。从数据结构上理解,就是把指针指向链表头部】 + $this->queue->rewind(); + + echo "Println: "; + while($this->queue->valid()){ + echo $this->queue->current()," -> "; + $this->queue->next(); + } + echo "\n"; + } +} +``` C#: ```csharp diff --git a/problems/0242.有效的字母异位词.md b/problems/0242.有效的字母异位词.md index 878b2466..87302482 100644 --- a/problems/0242.有效的字母异位词.md +++ b/problems/0242.有效的字母异位词.md @@ -9,7 +9,7 @@ ## 242.有效的字母异位词 -[力扣题目链接](https://leetcode-cn.com/problems/valid-anagram/) +[力扣题目链接](https://leetcode.cn/problems/valid-anagram/) 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 @@ -27,6 +27,8 @@ ## 思路 +本题B站视频讲解版:[学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词](https://www.bilibili.com/video/BV1YG411p7BA) + 先看暴力的解法,两层for循环,同时还要记录字符是否重复出现,很明显时间复杂度是 O(n^2)。 暴力的方法这里就不做介绍了,直接看一下有没有更优的方式。 @@ -125,8 +127,6 @@ class Solution: if record[i] != 0: #record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。 return False - #如果有一个元素不为零,则可以判断字符串s和t不是字母异位词 - break return True ``` @@ -308,6 +308,32 @@ impl Solution { } ``` + +Scala: +```scala +object Solution { + def isAnagram(s: String, t: String): Boolean = { + // 如果两个字符串的长度不等,直接返回false + if (s.length != t.length) return false + val record = new Array[Int](26) // 记录每个单词出现了多少次 + // 遍历字符串,对于s字符串单词对应的记录+=1,t字符串对应的记录-=1 + for (i <- 0 until s.length) { + record(s(i) - 97) += 1 + record(t(i) - 97) -= 1 + } + // 如果不等于则直接返回false + for (i <- 0 until 26) { + if (record(i) != 0) { + return false + } + } + // 如果前面不返回false,说明匹配成功,返回true,return可以省略 + true + } +} +``` + + C#: ```csharp public bool IsAnagram(string s, string t) { @@ -326,6 +352,7 @@ C#: return true; } ``` + ## 相关题目 * 383.赎金信 diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index 1362897c..d84cc6e1 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -9,7 +9,7 @@ # 257. 二叉树的所有路径 -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-paths/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-paths/) 给定一个二叉树,返回所有从根节点到叶子节点的路径。 @@ -702,5 +702,35 @@ func binaryTreePaths(_ root: TreeNode?) -> [String] { } ``` +Scala: + +递归: +```scala +object Solution { + import scala.collection.mutable.ListBuffer + def binaryTreePaths(root: TreeNode): List[String] = { + val res = ListBuffer[String]() + def traversal(curNode: TreeNode, path: ListBuffer[Int]): Unit = { + path.append(curNode.value) + if (curNode.left == null && curNode.right == null) { + res.append(path.mkString("->")) // mkString函数: 将数组的所有值按照指定字符串拼接 + return // 处理完可以直接return + } + + if (curNode.left != null) { + traversal(curNode.left, path) + path.remove(path.size - 1) + } + if (curNode.right != null) { + traversal(curNode.right, path) + path.remove(path.size - 1) + } + } + traversal(root, ListBuffer[Int]()) + res.toList + } +} +``` + -----------------------
diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index 9bad2085..e8ec98c6 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -7,7 +7,7 @@ ## 279.完全平方数 -[力扣题目链接](https://leetcode-cn.com/problems/perfect-squares/) +[力扣题目链接](https://leetcode.cn/problems/perfect-squares/) 给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。 @@ -355,5 +355,24 @@ var numSquares2 = function(n) { }; ``` +TypeScript: + +```typescript +function numSquares(n: number): number { + const goodsNum: number = Math.floor(Math.sqrt(n)); + const dp: number[] = new Array(n + 1).fill(Infinity); + dp[0] = 0; + for (let i = 1; i <= goodsNum; i++) { + const tempVal: number = i * i; + for (let j = tempVal; j <= n; j++) { + dp[j] = Math.min(dp[j], dp[j - tempVal] + 1); + } + } + return dp[n]; +}; +``` + + + -----------------------
diff --git a/problems/0283.移动零.md b/problems/0283.移动零.md index ed59d2c4..fe8e41c1 100644 --- a/problems/0283.移动零.md +++ b/problems/0283.移动零.md @@ -8,7 +8,7 @@ # 283. 移动零 -[力扣题目链接](https://leetcode-cn.com/problems/move-zeroes/) +[力扣题目链接](https://leetcode.cn/problems/move-zeroes/) 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 @@ -133,6 +133,27 @@ var moveZeroes = function(nums) { }; ``` +TypeScript: + +```typescript +function moveZeroes(nums: number[]): void { + const length: number = nums.length; + let slowIndex: number = 0, + fastIndex: number = 0; + while (fastIndex < length) { + if (nums[fastIndex] !== 0) { + nums[slowIndex++] = nums[fastIndex]; + }; + fastIndex++; + } + while (slowIndex < length) { + nums[slowIndex++] = 0; + } +}; +``` + + + -----------------------
diff --git a/problems/0300.最长上升子序列.md b/problems/0300.最长上升子序列.md index dfdd5125..41c6a7ce 100644 --- a/problems/0300.最长上升子序列.md +++ b/problems/0300.最长上升子序列.md @@ -6,7 +6,7 @@ ## 300.最长递增子序列 -[力扣题目链接](https://leetcode-cn.com/problems/longest-increasing-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/longest-increasing-subsequence/) 给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 @@ -168,6 +168,56 @@ func lengthOfLIS(nums []int ) int { } ``` +```go +// 动态规划求解 +func lengthOfLIS(nums []int) int { + // dp数组的定义 dp[i]表示取第i个元素的时候,表示子序列的长度,其中包括 nums[i] 这个元素 + dp := make([]int, len(nums)) + + // 初始化,所有的元素都应该初始化为1 + for i := range dp { + dp[i] = 1 + } + + ans := dp[0] + for i := 1; i < len(nums); i++ { + for j := 0; j < i; j++ { + if nums[i] > nums[j] { + dp[i] = max(dp[i], dp[j] + 1) + } + } + if dp[i] > ans { + ans = dp[i] + } + } + return ans +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} +``` + +Rust: +```rust +pub fn length_of_lis(nums: Vec) -> i32 { + let mut dp = vec![1; nums.len() + 1]; + let mut result = 1; + for i in 1..nums.len() { + for j in 0..i { + if nums[j] < nums[i] { + dp[i] = dp[i].max(dp[j] + 1); + } + result = result.max(dp[i]); + } + } + result +} +``` + Javascript ```javascript const lengthOfLIS = (nums) => { @@ -187,6 +237,27 @@ const lengthOfLIS = (nums) => { }; ``` +TypeScript + +```typescript +function lengthOfLIS(nums: number[]): number { + /** + dp[i]: 前i个元素中,以nums[i]结尾,最长子序列的长度 + */ + const dp: number[] = new Array(nums.length).fill(1); + let resMax: number = 0; + for (let i = 0, length = nums.length; i < length; i++) { + for (let j = 0; j < i; j++) { + if (nums[i] > nums[j]) { + dp[i] = Math.max(dp[i], dp[j] + 1); + } + } + resMax = Math.max(resMax, dp[i]); + } + return resMax; +}; +``` + diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md index 9989300c..3791a71f 100644 --- a/problems/0309.最佳买卖股票时机含冷冻期.md +++ b/problems/0309.最佳买卖股票时机含冷冻期.md @@ -6,7 +6,7 @@ ## 309.最佳买卖股票时机含冷冻期 -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-cooldown/) 给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。 @@ -44,6 +44,8 @@ dp[i][j],第i天状态为j,所剩的最多现金为dp[i][j]。 * 状态三:今天卖出了股票 * 状态四:今天为冷冻期状态,但冷冻期状态不可持续,只有一天! +![](https://img-blog.csdnimg.cn/518d5baaf33f4b2698064f8efb42edbf.png) + j的状态为: * 0:状态一 @@ -57,7 +59,7 @@ j的状态为: **注意这里的每一个状态,例如状态一,是买入股票状态并不是说今天已经就买入股票,而是说保存买入股票的状态即:可能是前几天买入的,之后一直没操作,所以保持买入股票的状态**。 -2. 确定递推公式 +1. 确定递推公式 达到买入股票状态(状态一)即:dp[i][0],有两个具体操作: @@ -214,8 +216,8 @@ class Solution { for (int i = 2; i <= prices.length; i++) { /* - dp[i][0] 第i天未持有股票收益; - dp[i][1] 第i天持有股票收益; + dp[i][0] 第i天持有股票收益; + dp[i][1] 第i天不持有股票收益; 情况一:第i天是冷静期,不能以dp[i-1][1]购买股票,所以以dp[i - 2][1]买股票,没问题 情况二:第i天不是冷静期,理论上应该以dp[i-1][1]购买股票,但是第i天不是冷静期说明,第i-1天没有卖出股票, 则dp[i-1][1]=dp[i-2][1],所以可以用dp[i-2][1]买股票,没问题 @@ -325,6 +327,66 @@ const maxProfit = (prices) => { }; ``` +TypeScript: + +> 版本一,与本文思路一致 + +```typescript +function maxProfit(prices: number[]): number { + /** + dp[i][0]: 持股状态; + dp[i][1]: 无股状态,当天为非冷冻期; + dp[i][2]: 无股状态,当天卖出; + dp[i][3]: 无股状态,当天为冷冻期; + */ + const length: number = prices.length; + const dp: number[][] = new Array(length).fill(0).map(_ => []); + dp[0][0] = -prices[0]; + dp[0][1] = dp[0][2] = dp[0][3] = 0; + for (let i = 1; i < length; i++) { + dp[i][0] = Math.max( + dp[i - 1][0], + Math.max(dp[i - 1][1], dp[i - 1][3]) - prices[i] + ); + dp[i][1] = Math.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]; + } + const lastEl: number[] = dp[length - 1]; + return Math.max(lastEl[1], lastEl[2], lastEl[3]); +}; +``` + +> 版本二,状态定义略有不同,可以帮助理解 + +```typescript +function maxProfit(prices: number[]): number { + /** + dp[i][0]: 持股状态,当天买入; + dp[i][1]: 持股状态,当天未买入; + dp[i][2]: 无股状态,当天卖出; + dp[i][3]: 无股状态,当天未卖出; + + 买入有冷冻期限制,其实就是状态[0]只能由前一天的状态[3]得到; + 如果卖出有冷冻期限制,其实就是[2]由[1]得到。 + */ + const length: number = prices.length; + const dp: number[][] = new Array(length).fill(0).map(_ => []); + dp[0][0] = -prices[0]; + dp[0][1] = -Infinity; + dp[0][2] = dp[0][3] = 0; + for (let i = 1; i < length; i++) { + dp[i][0] = dp[i - 1][3] - prices[i]; + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0]); + dp[i][2] = Math.max(dp[i - 1][0], dp[i - 1][1]) + prices[i]; + dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2]); + } + return Math.max(dp[length - 1][2], dp[length - 1][3]); +}; +``` + + + -----------------------
diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index 3a8d0662..03c6a4e2 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -7,7 +7,7 @@ ## 322. 零钱兑换 -[力扣题目链接](https://leetcode-cn.com/problems/coin-change/) +[力扣题目链接](https://leetcode.cn/problems/coin-change/) 给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。 @@ -220,7 +220,7 @@ class Solution: 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: '''版本二''' # 初始化 @@ -302,6 +302,24 @@ func min(a, b int) int { ``` +Rust: + +```rust +pub fn coin_change(coins: Vec, amount: i32) -> i32 { + let amount = amount as usize; + let mut dp = vec![i32::MAX; amount + 1]; + dp[0] = 0; + for i in 0..coins.len() { + for j in coins[i] as usize..=amount { + if dp[j - coins[i] as usize] != i32::MAX { + dp[j] = dp[j].min(dp[j - coins[i] as usize] + 1); + } + } + } + if dp[amount] == i32::MAX { -1 } else { dp[amount] } +} +``` + Javascript: ```javascript const coinChange = (coins, amount) => { @@ -322,7 +340,21 @@ const coinChange = (coins, amount) => { } ``` +TypeScript: +```typescript +function coinChange(coins: number[], amount: number): number { + const dp: number[] = new Array(amount + 1).fill(Infinity); + dp[0] = 0; + for (let i = 0; i < coins.length; i++) { + for (let j = coins[i]; j <= amount; j++) { + if (dp[j - coins[i]] === Infinity) continue; + dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1); + } + } + return dp[amount] === Infinity ? -1 : dp[amount]; +}; +``` -----------------------
diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index c71b2a93..71942c79 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -9,7 +9,7 @@ # 332.重新安排行程 -[力扣题目链接](https://leetcode-cn.com/problems/reconstruct-itinerary/) +[力扣题目链接](https://leetcode.cn/problems/reconstruct-itinerary/) 给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。 diff --git a/problems/0337.打家劫舍III.md b/problems/0337.打家劫舍III.md index a4d8f6b2..d2add232 100644 --- a/problems/0337.打家劫舍III.md +++ b/problems/0337.打家劫舍III.md @@ -7,7 +7,7 @@ ## 337.打家劫舍 III -[力扣题目链接](https://leetcode-cn.com/problems/house-robber-iii/) +[力扣题目链接](https://leetcode.cn/problems/house-robber-iii/) 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。 @@ -429,7 +429,50 @@ const rob = root => { }; ``` +### TypeScript + +> 记忆化后序遍历 + +```typescript +const memory: Map = new Map(); +function rob(root: TreeNode | null): number { + if (root === null) return 0; + if (memory.has(root)) return memory.get(root); + // 不取当前节点 + const res1: number = rob(root.left) + rob(root.right); + // 取当前节点 + let res2: number = root.val; + if (root.left !== null) res2 += rob(root.left.left) + rob(root.left.right); + if (root.right !== null) res2 += rob(root.right.left) + rob(root.right.right); + const res: number = Math.max(res1, res2); + memory.set(root, res); + return res; +}; +``` + +> 状态标记化后序遍历 + +```typescript +function rob(root: TreeNode | null): number { + return Math.max(...robNode(root)); +}; +// [0]-不偷当前节点能获得的最大金额; [1]-偷~~ +type MaxValueArr = [number, number]; +function robNode(node: TreeNode | null): MaxValueArr { + if (node === null) return [0, 0]; + const leftArr: MaxValueArr = robNode(node.left); + const rightArr: MaxValueArr = robNode(node.right); + // 不偷 + const val1: number = Math.max(leftArr[0], leftArr[1]) + + Math.max(rightArr[0], rightArr[1]); + // 偷 + const val2: number = leftArr[0] + rightArr[0] + node.val; + return [val1, val2]; +} +``` + ### Go + ```go // 打家劫舍Ⅲ 动态规划 // 时间复杂度O(n) 空间复杂度O(logn) diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index 4a7ba6ab..a4d532fd 100644 --- a/problems/0343.整数拆分.md +++ b/problems/0343.整数拆分.md @@ -6,7 +6,7 @@ # 343. 整数拆分 -[力扣题目链接](https://leetcode-cn.com/problems/integer-break/) +[力扣题目链接](https://leetcode.cn/problems/integer-break/) 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 @@ -274,7 +274,33 @@ var integerBreak = function(n) { }; ``` -C: +### TypeScript + +```typescript +function integerBreak(n: number): number { + /** + dp[i]: i对应的最大乘积 + dp[2]: 1; + ... + dp[i]: max( + 1 * dp[i - 1], 1 * (i - 1), + 2 * dp[i - 2], 2 * (i - 2), + ..., (i - 2) * dp[2], (i - 2) * 2 + ); + */ + const dp: number[] = new Array(n + 1).fill(0); + dp[2] = 1; + for (let i = 3; i <= n; i++) { + for (let j = 1; j <= i - 2; j++) { + dp[i] = Math.max(dp[i], j * dp[i - j], j * (i - j)); + } + } + return dp[n]; +}; +``` + +### C + ```c //初始化DP数组 int *initDP(int num) { @@ -309,5 +335,22 @@ int integerBreak(int n){ } ``` +### Scala + +```scala +object Solution { + def integerBreak(n: Int): Int = { + var dp = new Array[Int](n + 1) + dp(2) = 1 + for (i <- 3 to n) { + for (j <- 1 until i - 1) { + dp(i) = math.max(dp(i), math.max(j * (i - j), j * dp(i - j))) + } + } + dp(n) + } +} +``` + -----------------------
diff --git a/problems/0344.反转字符串.md b/problems/0344.反转字符串.md index 58bada05..031bc0ad 100644 --- a/problems/0344.反转字符串.md +++ b/problems/0344.反转字符串.md @@ -10,7 +10,7 @@ # 344.反转字符串 -[力扣题目链接](https://leetcode-cn.com/problems/reverse-string/) +[力扣题目链接](https://leetcode.cn/problems/reverse-string/) 编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 @@ -190,13 +190,13 @@ javaScript: * @return {void} Do not return anything, modify s in-place instead. */ var reverseString = function(s) { - return s.reverse(); + //Do not return anything, modify s in-place instead. + reverse(s) }; -var reverseString = function(s) { +var reverse = function(s) { let l = -1, r = s.length; while(++l < --r) [s[l], s[r]] = [s[r], s[l]]; - return s; }; ``` @@ -238,6 +238,22 @@ func reverseString(_ s: inout [Character]) { ``` +Rust: +```Rust +impl Solution { + pub fn reverse_string(s: &mut Vec) { + let (mut left, mut right) = (0, s.len()-1); + while left < right { + let temp = s[left]; + s[left] = s[right]; + s[right] = temp; + left += 1; + right -= 1; + } + } +} +``` + C: ```c void reverseString(char* s, int sSize){ @@ -267,5 +283,51 @@ public class Solution } ``` + +PHP: +```php +// 双指针 +// 一: +function reverseString(&$s) { + $left = 0; + $right = count($s)-1; + while($left<$right){ + $temp = $s[$left]; + $s[$left] = $s[$right]; + $s[$right] = $temp; + $left++; + $right--; + } +} + +// 二: +function reverseString(&$s) { + $this->reverse($s,0,count($s)-1); +} +// 按指定位置交换元素 +function reverse(&$s, $start, $end) { + for ($i = $start, $j = $end; $i < $j; $i++, $j--) { + $tmp = $s[$i]; + $s[$i] = $s[$j]; + $s[$j] = $tmp; + } + } +``` + +Scala: +```scala +object Solution { + def reverseString(s: Array[Char]): Unit = { + var (left, right) = (0, s.length - 1) + while (left < right) { + var tmp = s(left) + s(left) = s(right) + s(right) = tmp + left += 1 + right -= 1 + } + } +} +``` -----------------------
diff --git a/problems/0347.前K个高频元素.md b/problems/0347.前K个高频元素.md index c570672f..430dcea1 100644 --- a/problems/0347.前K个高频元素.md +++ b/problems/0347.前K个高频元素.md @@ -11,7 +11,7 @@ # 347.前 K 个高频元素 -[力扣题目链接](https://leetcode-cn.com/problems/top-k-frequent-elements/) +[力扣题目链接](https://leetcode.cn/problems/top-k-frequent-elements/) 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 @@ -141,13 +141,10 @@ class Solution { } Set> entries = map.entrySet(); - // 根据map的value值正序排,相当于一个小顶堆 - PriorityQueue> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue()); + // 根据map的value值,构建于一个大顶堆(o1 - o2: 小顶堆, o2 - o1 : 大顶堆) + PriorityQueue> queue = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue()); for (Map.Entry entry : entries) { queue.offer(entry); - if (queue.size() > k) { - queue.poll(); - } } for (int i = k - 1; i >= 0; i--) { result[i] = queue.poll().getKey(); @@ -409,6 +406,52 @@ C#: } return res; } + +``` + +Scala: + +解法一: 优先级队列 +```scala +object Solution { + import scala.collection.mutable + def topKFrequent(nums: Array[Int], k: Int): Array[Int] = { + val map = mutable.HashMap[Int, Int]() + // 将所有元素都放入Map + for (num <- nums) { + map.put(num, map.getOrElse(num, 0) + 1) + } + // 声明一个优先级队列,在函数柯里化那块需要指明排序方式 + var queue = mutable.PriorityQueue[(Int, Int)]()(Ordering.fromLessThan((x, y) => x._2 > y._2)) + // 将map里面的元素送入优先级队列 + for (elem <- map) { + queue.enqueue(elem) + if(queue.size > k){ + queue.dequeue // 如果队列元素大于k个,出队 + } + } + // 最终只需要key的Array形式就可以了,return关键字可以省略 + queue.map(_._1).toArray + } +} +``` +解法二: 相当于一个wordCount程序 +```scala +object Solution { + def topKFrequent(nums: Array[Int], k: Int): Array[Int] = { + // 首先将数据变为(x,1),然后按照x分组,再使用map进行转换(x,sum),变换为Array + // 再使用sort针对sum进行排序,最后take前k个,再把数据变为x,y,z这种格式 + nums.map((_, 1)).groupBy(_._1) + .map { + case (x, arr) => (x, arr.map(_._2).sum) + } + .toArray + .sortWith(_._2 > _._2) + .take(k) + .map(_._1) + } +} + ``` ----------------------- diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md index 7f8958d2..61eac6cd 100644 --- a/problems/0349.两个数组的交集.md +++ b/problems/0349.两个数组的交集.md @@ -12,7 +12,7 @@ ## 349. 两个数组的交集 -[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-arrays/) +[力扣题目链接](https://leetcode.cn/problems/intersection-of-two-arrays/) 题意:给定两个数组,编写一个函数来计算它们的交集。 @@ -24,6 +24,8 @@ ## 思路 +关于本题,我录制了讲解视频:[学透哈希表,set使用有技巧!Leetcode:349. 两个数组的交集](https://www.bilibili.com/video/BV1ba411S7wu),看视频配合题解,事半功倍。 + 这道题目,主要要学会使用一种哈希数据结构:unordered_set,这个数据结构可以解决很多类似的问题。 注意题目特意说明:**输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序** @@ -48,7 +50,8 @@ std::set和std::multiset底层实现都是红黑树,std::unordered_set的底 思路如图所示: -![set哈希法](https://img-blog.csdnimg.cn/2020080918570417.png) + +![set哈希法](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707173513.png) C++代码如下: @@ -56,7 +59,7 @@ C++代码如下: class Solution { public: vector intersection(vector& nums1, vector& nums2) { - unordered_set result_set; // 存放结果 + unordered_set result_set; // 存放结果,之所以用set是为了给结果集去重 unordered_set nums_set(nums1.begin(), nums1.end()); for (int num : nums2) { // 发现nums2的元素 在nums_set里又出现过 @@ -77,6 +80,36 @@ public: 不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。 +## 后记 + +本题后面 力扣改了 题目描述 和 后台测试数据,增添了 数值范围: + +* 1 <= nums1.length, nums2.length <= 1000 +* 0 <= nums1[i], nums2[i] <= 1000 + +所以就可以 使用数组来做哈希表了, 因为数组都是 1000以内的。 + +对应C++代码如下: + +```c++ +class Solution { +public: + vector intersection(vector& nums1, vector& nums2) { + unordered_set result_set; // 存放结果,之所以用set是为了给结果集去重 + int hash[1005] = {0}; // 默认数值为0 + for (int num : nums1) { // nums1中出现的字母在hash数组中做记录 + hash[num] = 1; + } + for (int num : nums2) { // nums2中出现话,result记录 + if (hash[num] == 1) { + result_set.insert(num); + } + } + return vector(result_set.begin(), result_set.end()); + } +}; +``` + ## 其他语言版本 @@ -104,13 +137,8 @@ class Solution { resSet.add(i); } } - int[] resArr = new int[resSet.size()]; - int index = 0; //将结果几何转为数组 - for (int i : resSet) { - resArr[index++] = i; - } - return resArr; + return resSet.stream().mapToInt(x -> x).toArray(); } } ``` @@ -313,6 +341,52 @@ int* intersection1(int* nums1, int nums1Size, int* nums2, int nums2Size, int* re } ``` +Scala: + +正常解法: +```scala +object Solution { + def intersection(nums1: Array[Int], nums2: Array[Int]): Array[Int] = { + // 导入mutable + import scala.collection.mutable + // 临时Set,用于记录数组1出现的每个元素 + val tmpSet: mutable.HashSet[Int] = new mutable.HashSet[Int]() + // 结果Set,存储最终结果 + val resSet: mutable.HashSet[Int] = new mutable.HashSet[Int]() + // 遍历nums1,把每个元素添加到tmpSet + nums1.foreach(tmpSet.add(_)) + // 遍历nums2,如果在tmpSet存在就添加到resSet + nums2.foreach(elem => { + if (tmpSet.contains(elem)) { + resSet.add(elem) + } + }) + // 将结果转换为Array返回,return可以省略 + resSet.toArray + } +} +``` +骚操作1: +```scala +object Solution { + def intersection(nums1: Array[Int], nums2: Array[Int]): Array[Int] = { + // 先转为Set,然后取交集,最后转换为Array + (nums1.toSet).intersect(nums2.toSet).toArray + } +} +``` +骚操作2: +```scala +object Solution { + def intersection(nums1: Array[Int], nums2: Array[Int]): Array[Int] = { + // distinct去重,然后取交集 + (nums1.distinct).intersect(nums2.distinct) + } +} + +``` + + C#: ```csharp public int[] Intersection(int[] nums1, int[] nums2) { @@ -330,6 +404,7 @@ C#: } return one; } + ``` ## 相关题目 diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index 6822896e..a41a0f0a 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -9,7 +9,7 @@ # 376. 摆动序列 -[力扣题目链接](https://leetcode-cn.com/problems/wiggle-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/wiggle-subsequence/) 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。 @@ -88,8 +88,8 @@ public: }; ``` -* 时间复杂度:$O(n)$ -* 空间复杂度:$O(1)$ +* 时间复杂度:O(n) +* 空间复杂度:O(1) ## 思路2(动态规划) @@ -138,8 +138,8 @@ public: }; ``` -* 时间复杂度:$O(n^2)$ -* 空间复杂度:$O(n)$ +* 时间复杂度:O(n^2) +* 空间复杂度:O(n) **进阶** @@ -149,9 +149,9 @@ public: * 每次更新`dp[i][1]`,则在`tree2`的`nums[i]`位置值更新为`dp[i][1]` * 则dp转移方程中就没有必要j从0遍历到i-1,可以直接在线段树中查询指定区间的值即可。 -时间复杂度:$O(n\log n)$ +时间复杂度:O(nlog n) -空间复杂度:$O(n)$ +空间复杂度:O(n) ## 总结 @@ -228,6 +228,8 @@ class Solution { ### Python +**贪心** + ```python class Solution: def wiggleMaxLength(self, nums: List[int]) -> int: @@ -240,7 +242,30 @@ class Solution: return res ``` +**动态规划** + +```python +class Solution: + def wiggleMaxLength(self, nums: List[int]) -> int: + # 0 i 作为波峰的最大长度 + # 1 i 作为波谷的最大长度 + # dp是一个列表,列表中每个元素是长度为 2 的列表 + dp = [] + for i in range(len(nums)): + # 初始为[1, 1] + dp.append([1, 1]) + for j in range(i): + # nums[i] 为波谷 + if nums[j] > nums[i]: + dp[i][1] = max(dp[i][1], dp[j][0] + 1) + # nums[i] 为波峰 + if nums[j] < nums[i]: + dp[i][0] = max(dp[i][0], dp[j][1] + 1) + return max(dp[-1][0], dp[-1][1]) +``` + ### Go + ```golang func wiggleMaxLength(nums []int) int { var count,preDiff,curDiff int @@ -298,9 +323,33 @@ var wiggleMaxLength = function(nums) { }; ``` +### Rust +**贪心** +```Rust +impl Solution { + pub fn wiggle_max_length(nums: Vec) -> i32 { + let len = nums.len() as usize; + if len <= 1 { + return len as i32; + } + let mut preDiff = 0; + let mut curDiff = 0; + let mut result = 1; + for i in 0..len-1 { + curDiff = nums[i+1] - nums[i]; + if (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0) { + result += 1; + preDiff = curDiff; + } + } + result + } +} +``` ### C **贪心** + ```c int wiggleMaxLength(int* nums, int numsSize){ if(numsSize <= 1) @@ -326,6 +375,44 @@ int wiggleMaxLength(int* nums, int numsSize){ } ``` +**动态规划** + +```c +int max(int left, int right) +{ + return left > right ? left : right; +} +int wiggleMaxLength(int* nums, int numsSize){ + if(numsSize <= 1) + { + return numsSize; + } + // 0 i 作为波峰的最大长度 + // 1 i 作为波谷的最大长度 + int dp[numsSize][2]; + for(int i = 0; i < numsSize; i++) + { + dp[i][0] = 1; + dp[i][1] = 1; + for(int j = 0; j < i; j++) + { + // nums[i] 为山谷 + if(nums[j] > nums[i]) + { + dp[i][1] = max(dp[i][1], dp[j][0] + 1); + } + // nums[i] 为山峰 + if(nums[j] < nums[i]) + { + dp[i][0] = max(dp[i][0], dp[j][1] + 1); + } + } + } + return max(dp[numsSize - 1][0], dp[numsSize - 1][1]); +} +``` + + ### TypeScript @@ -375,7 +462,31 @@ function wiggleMaxLength(nums: number[]): number { }; ``` +### Scala +```scala +object Solution { + def wiggleMaxLength(nums: Array[Int]): Int = { + if (nums.length <= 1) return nums.length + var result = 1 + var curDiff = 0 // 当前一对的差值 + var preDiff = 0 // 前一对的差值 + + for (i <- 1 until nums.length) { + curDiff = nums(i) - nums(i - 1) // 计算当前这一对的差值 + // 当 curDiff > 0 的情况,preDiff <= 0 + // 当 curDiff < 0 的情况,preDiff >= 0 + // 这两种情况算是两个峰值 + if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) { + result += 1 // 结果集加 1 + preDiff = curDiff // 当前差值赋值给上一轮 + } + } + + result + } +} +``` -----------------------
diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index aaf27e61..f8e544a9 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -8,7 +8,7 @@ ## 377. 组合总和 Ⅳ -[力扣题目链接](https://leetcode-cn.com/problems/combination-sum-iv/) +[力扣题目链接](https://leetcode.cn/problems/combination-sum-iv/) 难度:中等 @@ -221,7 +221,27 @@ const combinationSum4 = (nums, target) => { }; ``` +TypeScript: + +```typescript +function combinationSum4(nums: number[], target: number): number { + const dp: number[] = new Array(target + 1).fill(0); + dp[0] = 1; + // 遍历背包 + for (let i = 1; i <= target; i++) { + // 遍历物品 + for (let j = 0, length = nums.length; j < length; j++) { + if (i >= nums[j]) { + dp[i] += dp[i - nums[j]]; + } + } + } + return dp[target]; +}; +``` + Rust + ```Rust impl Solution { pub fn combination_sum4(nums: Vec, target: i32) -> i32 { diff --git a/problems/0383.赎金信.md b/problems/0383.赎金信.md index 6d6efc55..3cde5472 100644 --- a/problems/0383.赎金信.md +++ b/problems/0383.赎金信.md @@ -10,7 +10,7 @@ # 383. 赎金信 -[力扣题目链接](https://leetcode-cn.com/problems/ransom-note/) +[力扣题目链接](https://leetcode.cn/problems/ransom-note/) 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。 @@ -114,23 +114,25 @@ Java: ```Java class Solution { public boolean canConstruct(String ransomNote, String magazine) { - //记录杂志字符串出现的次数 - int[] arr = new int[26]; - int temp; - for (int i = 0; i < magazine.length(); i++) { - temp = magazine.charAt(i) - 'a'; - arr[temp]++; + // 定义一个哈希映射数组 + int[] record = new int[26]; + + // 遍历 + for(char c : magazine.toCharArray()){ + record[c - 'a'] += 1; } - for (int i = 0; i < ransomNote.length(); i++) { - temp = ransomNote.charAt(i) - 'a'; - //对于金信中的每一个字符都在数组中查找 - //找到相应位减一,否则找不到返回false - if (arr[temp] > 0) { - arr[temp]--; - } else { + + for(char c : ransomNote.toCharArray()){ + record[c - 'a'] -= 1; + } + + // 如果数组中存在负数,说明ransomNote字符串总存在magazine中没有的字符 + for(int i : record){ + if(i < 0){ return false; } } + return true; } } @@ -361,6 +363,71 @@ impl Solution { } ``` +Scala: + +版本一: 使用数组作为哈希表 +```scala +object Solution { + def canConstruct(ransomNote: String, magazine: String): Boolean = { + // 如果magazine的长度小于ransomNote的长度,必然是false + if (magazine.length < ransomNote.length) { + return false + } + // 定义一个数组,存储magazine字符出现的次数 + val map: Array[Int] = new Array[Int](26) + // 遍历magazine字符串,对应的字符+=1 + for (i <- magazine.indices) { + map(magazine(i) - 'a') += 1 + } + // 遍历ransomNote + for (i <- ransomNote.indices) { + if (map(ransomNote(i) - 'a') > 0) + map(ransomNote(i) - 'a') -= 1 + else return false + } + // 如果上面没有返回false,直接返回true,关键字return可以省略 + true + } +} +``` + +```scala +object Solution { + import scala.collection.mutable + def canConstruct(ransomNote: String, magazine: String): Boolean = { + // 如果magazine的长度小于ransomNote的长度,必然是false + if (magazine.length < ransomNote.length) { + return false + } + // 定义map,key是字符,value是字符出现的次数 + val map = new mutable.HashMap[Char, Int]() + // 遍历magazine,把所有的字符都记录到map里面 + for (i <- magazine.indices) { + val tmpChar = magazine(i) + // 如果map包含该字符,那么对应的value++,否则添加该字符 + if (map.contains(tmpChar)) { + map.put(tmpChar, map.get(tmpChar).get + 1) + } else { + map.put(tmpChar, 1) + } + } + // 遍历ransomNote + for (i <- ransomNote.indices) { + val tmpChar = ransomNote(i) + // 如果map包含并且该字符的value大于0,则匹配成功,map对应的--,否则直接返回false + if (map.contains(tmpChar) && map.get(tmpChar).get > 0) { + map.put(tmpChar, map.get(tmpChar).get - 1) + } else { + return false + } + } + // 如果上面没有返回false,直接返回true,关键字return可以省略 + true + } +} +``` + + C#: ```csharp public bool CanConstruct(string ransomNote, string magazine) { @@ -377,6 +444,7 @@ public bool CanConstruct(string ransomNote, string magazine) { } return true; } + ``` -----------------------
diff --git a/problems/0392.判断子序列.md b/problems/0392.判断子序列.md index 671576f7..dedeb0ad 100644 --- a/problems/0392.判断子序列.md +++ b/problems/0392.判断子序列.md @@ -7,7 +7,7 @@ ## 392.判断子序列 -[力扣题目链接](https://leetcode-cn.com/problems/is-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/is-subsequence/) 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 @@ -31,7 +31,7 @@ ## 思路 -(这道题可以用双指针的思路来实现,时间复杂度就是$O(n)$) +(这道题可以用双指针的思路来实现,时间复杂度就是O(n)) 这道题应该算是编辑距离的入门题目,因为从题意中我们也可以发现,只需要计算删除的情况,不用考虑增加和替换的情况。 @@ -122,8 +122,8 @@ public: }; ``` -* 时间复杂度:$O(n × m)$ -* 空间复杂度:$O(n × m)$ +* 时间复杂度:O(n × m) +* 空间复杂度:O(n × m) ## 总结 @@ -201,7 +201,32 @@ const isSubsequence = (s, t) => { }; ``` +TypeScript: + +```typescript +function isSubsequence(s: string, t: string): boolean { + /** + dp[i][j]: s的前i-1个,t的前j-1个,最长公共子序列的长度 + */ + const sLen: number = s.length, + tLen: number = t.length; + const dp: number[][] = new Array(sLen + 1).fill(0) + .map(_ => new Array(tLen + 1).fill(0)); + for (let i = 1; i <= sLen; i++) { + for (let j = 1; j <= tLen; j++) { + if (s[i - 1] === t[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[sLen][tLen] === s.length; +}; +``` + Go: + ```go func isSubsequence(s string, t string) bool { dp := make([][]int,len(s)+1) diff --git a/problems/0404.左叶子之和.md b/problems/0404.左叶子之和.md index 6420da81..8485cdac 100644 --- a/problems/0404.左叶子之和.md +++ b/problems/0404.左叶子之和.md @@ -7,7 +7,7 @@ # 404.左叶子之和 -[力扣题目链接](https://leetcode-cn.com/problems/sum-of-left-leaves/) +[力扣题目链接](https://leetcode.cn/problems/sum-of-left-leaves/) 计算给定二叉树的所有左叶子之和。 @@ -336,8 +336,8 @@ var sumOfLeftLeaves = function(root) { if(node===null){ return 0; } - let leftValue = sumOfLeftLeaves(node.left); - let rightValue = sumOfLeftLeaves(node.right); + let leftValue = nodesSum(node.left); + let rightValue = nodesSum(node.right); // 3. 单层递归逻辑 let midValue = 0; if(node.left&&node.left.left===null&&node.left.right===null){ @@ -466,7 +466,94 @@ func sumOfLeftLeaves(_ root: TreeNode?) -> Int { } ``` +## C +递归法: +```c +int sumOfLeftLeaves(struct TreeNode* root){ + // 递归结束条件:若当前结点为空,返回0 + if(!root) + return 0; + + // 递归取左子树的左结点和和右子树的左结点和 + int leftValue = sumOfLeftLeaves(root->left); + int rightValue = sumOfLeftLeaves(root->right); + // 若当前结点的左结点存在,且其为叶子结点。取它的值 + int midValue = 0; + if(root->left && (!root->left->left && !root->left->right)) + midValue = root->left->val; + + return leftValue + rightValue + midValue; +} +``` + +迭代法: +```c +int sumOfLeftLeaves(struct TreeNode* root){ + struct TreeNode* stack[1000]; + int stackTop = 0; + + // 若传入root结点不为空,将其入栈 + if(root) + stack[stackTop++] = root; + + int sum = 0; + //若栈不为空,进行循环 + while(stackTop) { + // 出栈栈顶元素 + struct TreeNode *topNode = stack[--stackTop]; + // 若栈顶元素的左孩子为左叶子结点,将其值加入sum中 + if(topNode->left && (!topNode->left->left && !topNode->left->right)) + sum += topNode->left->val; + + // 若当前栈顶结点有左右孩子。将他们加入栈中进行遍历 + if(topNode->right) + stack[stackTop++] = topNode->right; + if(topNode->left) + stack[stackTop++] = topNode->left; + } + return sum; +} +``` + +## Scala + +**递归:** +```scala +object Solution { + def sumOfLeftLeaves(root: TreeNode): Int = { + if(root == null) return 0 + var midValue = 0 + if(root.left != null && root.left.left == null && root.left.right == null){ + midValue = root.left.value + } + // return关键字可以省略 + midValue + sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right) + } +} +``` + +**迭代:** +```scala +object Solution { + import scala.collection.mutable + def sumOfLeftLeaves(root: TreeNode): Int = { + val stack = mutable.Stack[TreeNode]() + if (root == null) return 0 + stack.push(root) + var sum = 0 + while (!stack.isEmpty) { + val curNode = stack.pop() + if (curNode.left != null && curNode.left.left == null && curNode.left.right == null) { + sum += curNode.left.value // 如果满足条件就累加 + } + if (curNode.right != null) stack.push(curNode.right) + if (curNode.left != null) stack.push(curNode.left) + } + sum + } +} +``` -----------------------
diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index ecb05301..827b7481 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -7,7 +7,7 @@ # 406.根据身高重建队列 -[力扣题目链接](https://leetcode-cn.com/problems/queue-reconstruction-by-height/) +[力扣题目链接](https://leetcode.cn/problems/queue-reconstruction-by-height/) 假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 @@ -290,6 +290,54 @@ var reconstructQueue = function(people) { }; ``` + +### C +```c +int cmp(const void *p1, const void *p2) { + int *pp1 = *(int**)p1; + int *pp2 = *(int**)p2; + // 若身高相同,则按照k从小到大排列 + // 若身高不同,按身高从大到小排列 + return pp1[0] == pp2[0] ? pp1[1] - pp2[1] : pp2[0] - pp1[0]; +} + +// 将start与end中间的元素都后移一位 +// start为将要新插入元素的位置 +void moveBack(int **people, int peopleSize, int start, int end) { + int i; + for(i = end; i > start; i--) { + people[i] = people[i-1]; + } +} + +int** reconstructQueue(int** people, int peopleSize, int* peopleColSize, int* returnSize, int** returnColumnSizes){ + int i; + // 将people按身高从大到小排列(若身高相同,按k从小到大排列) + qsort(people, peopleSize, sizeof(int*), cmp); + + for(i = 0; i < peopleSize; ++i) { + // people[i]要插入的位置 + int position = people[i][1]; + int *temp = people[i]; + // 将position到i中间的元素后移一位 + // 注:因为已经排好序,position不会比i大。(举例:排序后people最后一位元素最小,其可能的k最大值为peopleSize-2,小于此时的i) + moveBack(people, peopleSize, position, i); + // 将temp放置到position处 + people[position] = temp; + + } + + + // 设置返回二维数组的大小以及里面每个一维数组的长度 + *returnSize = peopleSize; + *returnColumnSizes = (int*)malloc(sizeof(int) * peopleSize); + for(i = 0; i < peopleSize; ++i) { + (*returnColumnSizes)[i] = 2; + } + return people; +} +``` + ### TypeScript ```typescript @@ -306,7 +354,27 @@ function reconstructQueue(people: number[][]): number[][] { }; ``` +### Scala +```scala +object Solution { + import scala.collection.mutable + def reconstructQueue(people: Array[Array[Int]]): Array[Array[Int]] = { + val person = people.sortWith((a, b) => { + if (a(0) == b(0)) a(1) < b(1) + else a(0) > b(0) + }) + + var que = mutable.ArrayBuffer[Array[Int]]() + + for (per <- person) { + que.insert(per(1), per) + } + + que.toArray + } +} +``` ----------------------- diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 6e93ae8e..83b267ac 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -6,7 +6,7 @@ ## 416. 分割等和子集 -[力扣题目链接](https://leetcode-cn.com/problems/partition-equal-subset-sum/) +[力扣题目链接](https://leetcode.cn/problems/partition-equal-subset-sum/) 题目难易:中等 @@ -183,7 +183,7 @@ public: ## 其他语言版本 -Java: +### Java: ```Java class Solution { public boolean canPartition(int[] nums) { @@ -316,7 +316,7 @@ class Solution { } } ``` -Python: +### Python: ```python class Solution: def canPartition(self, nums: List[int]) -> bool: @@ -329,7 +329,7 @@ class Solution: dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) return target == dp[target] ``` -Go: +### Go: ```go // 分割等和子集 动态规划 // 时间复杂度O(n^2) 空间复杂度O(n) @@ -397,7 +397,7 @@ func canPartition(nums []int) bool { } ``` -javaScript: +### javaScript: ```js var canPartition = function(nums) { @@ -419,5 +419,208 @@ var canPartition = function(nums) { + +### C: + +二维dp: +```c +/** +1. dp数组含义:dp[i][j]为背包重量为j时,从[0-i]元素和最大值 +2. 递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]) +3. 初始化:dp[i][0]初始化为0。因为背包重量为0时,不可能放入元素。dp[0][j] = nums[0],当j >= nums[0] && j < target时 +4. 遍历顺序:先遍历物品,再遍历背包 +*/ +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +int getSum(int* nums, int numsSize) { + int sum = 0; + + int i; + for(i = 0; i < numsSize; ++i) { + sum += nums[i]; + } + return sum; +} + +bool canPartition(int* nums, int numsSize){ + // 求出元素总和 + int sum = getSum(nums, numsSize); + // 若元素总和为奇数,则不可能得到两个和相等的子数组 + if(sum % 2) + return false; + + // 若子数组的和等于target,则nums可以被分割 + int target = sum / 2; + // 初始化dp数组 + int dp[numsSize][target + 1]; + // dp[j][0]都应被设置为0。因为当背包重量为0时,不可放入元素 + memset(dp, 0, sizeof(int) * numsSize * (target + 1)); + + int i, j; + // 当背包重量j大于nums[0]时,可以在dp[0][j]中放入元素nums[0] + for(j = nums[0]; j <= target; ++j) { + dp[0][j] = nums[0]; + } + + for(i = 1; i < numsSize; ++i) { + for(j = 1; j <= target; ++j) { + // 若当前背包重量j小于nums[i],则其值等于只考虑0到i-1物品时的值 + if(j < nums[i]) + dp[i][j] = dp[i - 1][j]; + // 否则,背包重量等于在背包中放入num[i]/不放入nums[i]的较大值 + else + dp[i][j] = MAX(dp[i - 1][j], dp[i - 1][j - nums[i]] + nums[i]); + } + } + // 判断背包重量为target,且考虑到所有物品时,放入的元素和是否等于target + return dp[numsSize - 1][target] == target; +} +``` +滚动数组: +```c +/** +1. dp数组含义:dp[j]为背包重量为j时,其中可放入元素的最大值 +2. 递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) +3. 初始化:均初始化为0即可 +4. 遍历顺序:先遍历物品,再后序遍历背包 +*/ +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +int getSum(int* nums, int numsSize) { + int sum = 0; + + int i; + for(i = 0; i < numsSize; ++i) { + sum += nums[i]; + } + return sum; +} + +bool canPartition(int* nums, int numsSize){ + // 求出元素总和 + int sum = getSum(nums, numsSize); + // 若元素总和为奇数,则不可能得到两个和相等的子数组 + if(sum % 2) + return false; + // 背包容量 + int target = sum / 2; + + // 初始化dp数组,元素均为0 + int dp[target + 1]; + memset(dp, 0, sizeof(int) * (target + 1)); + + int i, j; + // 先遍历物品,后遍历背包 + for(i = 0; i < numsSize; ++i) { + for(j = target; j >= nums[i]; --j) { + dp[j] = MAX(dp[j], dp[j - nums[i]] + nums[i]); + } + } + + // 查看背包容量为target时,元素总和是否等于target + return dp[target] == target; +} +``` + +### TypeScript: + +> 一维数组,简洁 + +```typescript +function canPartition(nums: number[]): boolean { + const sum: number = nums.reduce((pre, cur) => pre + cur); + if (sum % 2 === 1) return false; + const bagSize: number = sum / 2; + const goodsNum: number = nums.length; + const dp: number[] = new Array(bagSize + 1).fill(0); + for (let i = 0; i < goodsNum; i++) { + for (let j = bagSize; j >= nums[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); + } + } + return dp[bagSize] === bagSize; +}; +``` + +> 二维数组,易懂 + +```typescript +function canPartition(nums: number[]): boolean { + /** + weightArr = nums; + valueArr = nums; + bagSize = sum / 2; (sum为nums各元素总和); + 按照0-1背包处理 + */ + const sum: number = nums.reduce((pre, cur) => pre + cur); + if (sum % 2 === 1) return false; + const bagSize: number = sum / 2; + const weightArr: number[] = nums; + const valueArr: number[] = nums; + const goodsNum: number = weightArr.length; + const dp: number[][] = new Array(goodsNum) + .fill(0) + .map(_ => new Array(bagSize + 1).fill(0)); + for (let i = weightArr[0]; i <= bagSize; i++) { + dp[0][i] = valueArr[0]; + } + for (let i = 1; i < goodsNum; i++) { + for (let j = 0; j <= bagSize; j++) { + if (j < weightArr[i]) { + dp[i][j] = dp[i - 1][j]; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weightArr[i]] + valueArr[i]); + } + } + } + return dp[goodsNum - 1][bagSize] === bagSize; +}; +``` + +### Scala + +滚动数组: +```scala +object Solution { + def canPartition(nums: Array[Int]): Boolean = { + var sum = nums.sum + if (sum % 2 != 0) return false + var half = sum / 2 + var dp = new Array[Int](half + 1) + + // 遍历 + for (i <- 0 until nums.length; j <- half to nums(i) by -1) { + dp(j) = math.max(dp(j), dp(j - nums(i)) + nums(i)) + } + + if (dp(half) == half) true else false + } +} +``` + +二维数组: +```scala +object Solution { + def canPartition(nums: Array[Int]): Boolean = { + var sum = nums.sum + if (sum % 2 != 0) return false + var half = sum / 2 + var dp = Array.ofDim[Int](nums.length, half + 1) + + // 初始化 + for (j <- nums(0) to half) dp(0)(j) = nums(0) + + // 遍历 + for (i <- 1 until nums.length; j <- 1 to half) { + if (j - nums(i) >= 0) dp(i)(j) = nums(i) + dp(i - 1)(j - nums(i)) + dp(i)(j) = math.max(dp(i)(j), dp(i - 1)(j)) + } + + // 如果等于half就返回ture,否则返回false + if (dp(nums.length - 1)(half) == half) true else false + } +} +``` + -----------------------
diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index cf58b9e2..3aa4eeb6 100644 --- a/problems/0435.无重叠区间.md +++ b/problems/0435.无重叠区间.md @@ -7,7 +7,7 @@ # 435. 无重叠区间 -[力扣题目链接](https://leetcode-cn.com/problems/non-overlapping-intervals/) +[力扣题目链接](https://leetcode.cn/problems/non-overlapping-intervals/) 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 @@ -93,7 +93,7 @@ public: }; ``` * 时间复杂度:O(nlog n) ,有一个快排 -* 空间复杂度:O(1) +* 空间复杂度:O(n),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间 大家此时会发现如此复杂的一个问题,代码实现却这么简单! @@ -264,7 +264,7 @@ func min(a,b int)int{ } return a } -``` +``` ### Javascript: - 按右边界排序 @@ -307,6 +307,75 @@ var eraseOverlapIntervals = function(intervals) { } ``` +### TypeScript + +> 按右边界排序,从左往右遍历 + +```typescript +function eraseOverlapIntervals(intervals: number[][]): number { + const length = intervals.length; + if (length === 0) return 0; + intervals.sort((a, b) => a[1] - b[1]); + let right: number = intervals[0][1]; + let count: number = 1; + for (let i = 1; i < length; i++) { + if (intervals[i][0] >= right) { + count++; + right = intervals[i][1]; + } + } + return length - count; +}; +``` + +> 按左边界排序,从左往右遍历 + +```typescript +function eraseOverlapIntervals(intervals: number[][]): number { + if (intervals.length === 0) return 0; + intervals.sort((a, b) => a[0] - b[0]); + let right: number = intervals[0][1]; + let tempInterval: number[]; + let resCount: number = 0; + for (let i = 1, length = intervals.length; i < length; i++) { + tempInterval = intervals[i]; + if (tempInterval[0] >= right) { + // 未重叠 + right = tempInterval[1]; + } else { + // 有重叠,移除当前interval和前一个interval中右边界更大的那个 + right = Math.min(right, tempInterval[1]); + resCount++; + } + } + return resCount; +}; +``` + +### Scala + +```scala +object Solution { + def eraseOverlapIntervals(intervals: Array[Array[Int]]): Int = { + var result = 0 + var interval = intervals.sortWith((a, b) => { + a(1) < b(1) + }) + var edge = Int.MinValue + for (i <- 0 until interval.length) { + if (edge <= interval(i)(0)) { + edge = interval(i)(1) + } else { + result += 1 + } + } + result + } +} +``` + + + -----------------------
diff --git a/problems/0450.删除二叉搜索树中的节点.md b/problems/0450.删除二叉搜索树中的节点.md index e8f7e54c..3bc8369c 100644 --- a/problems/0450.删除二叉搜索树中的节点.md +++ b/problems/0450.删除二叉搜索树中的节点.md @@ -9,7 +9,7 @@ # 450.删除二叉搜索树中的节点 -[力扣题目链接]( https://leetcode-cn.com/problems/delete-node-in-a-bst/) +[力扣题目链接]( https://leetcode.cn/problems/delete-node-in-a-bst/) 给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。 @@ -456,31 +456,42 @@ func deleteNode(root *TreeNode, key int) *TreeNode { * @param {number} key * @return {TreeNode} */ -var deleteNode = function (root, key) { - if (root === null) - return root; - if (root.val === key) { - if (!root.left) - return root.right; - else if (!root.right) - return root.left; - else { - let cur = root.right; - while (cur.left) { - cur = cur.left; - } - cur.left = root.left; - root = root.right; - delete root; - return root; - } - } - if (root.val > key) - root.left = deleteNode(root.left, key); - if (root.val < key) +var deleteNode = function(root, key) { + if (!root) return null; + if (key > root.val) { root.right = deleteNode(root.right, key); - return root; + return root; + } else if (key < root.val) { + root.left = deleteNode(root.left, key); + return root; + } else { + // 场景1: 该节点是叶节点 + if (!root.left && !root.right) { + return null + } + // 场景2: 有一个孩子节点不存在 + if (root.left && !root.right) { + return root.left; + } else if (root.right && !root.left) { + return root.right; + } + // 场景3: 左右节点都存在 + const rightNode = root.right; + // 获取最小值节点 + const minNode = getMinNode(rightNode); + // 将待删除节点的值替换为最小值节点值 + root.val = minNode.val; + // 删除最小值节点 + root.right = deleteNode(root.right, minNode.val); + return root; + } }; +function getMinNode(root) { + while (root.left) { + root = root.left; + } + return root; +} ``` 迭代 @@ -582,7 +593,35 @@ function deleteNode(root: TreeNode | null, key: number): TreeNode | null { }; ``` +## Scala +```scala +object Solution { + def deleteNode(root: TreeNode, key: Int): TreeNode = { + if (root == null) return root // 第一种情况,没找到删除的节点,遍历到空节点直接返回 + if (root.value == key) { + // 第二种情况: 左右孩子都为空,直接删除节点,返回null + if (root.left == null && root.right == null) return null + // 第三种情况: 左孩子为空,右孩子不为空,右孩子补位 + else if (root.left == null && root.right != null) return root.right + // 第四种情况: 左孩子不为空,右孩子为空,左孩子补位 + else if (root.left != null && root.right == null) return root.left + // 第五种情况: 左右孩子都不为空,将删除节点的左子树头节点(左孩子)放到 + // 右子树的最左边节点的左孩子上,返回删除节点的右孩子 + else { + var tmp = root.right + while (tmp.left != null) tmp = tmp.left + tmp.left = root.left + return root.right + } + } + if (root.value > key) root.left = deleteNode(root.left, key) + if (root.value < key) root.right = deleteNode(root.right, key) + + root // 返回根节点,return关键字可以省略 + } +} +``` -----------------------
diff --git a/problems/0452.用最少数量的箭引爆气球.md b/problems/0452.用最少数量的箭引爆气球.md index 2ab14b61..7b5f387d 100644 --- a/problems/0452.用最少数量的箭引爆气球.md +++ b/problems/0452.用最少数量的箭引爆气球.md @@ -7,7 +7,7 @@ # 452. 用最少数量的箭引爆气球 -[力扣题目链接](https://leetcode-cn.com/problems/minimum-number-of-arrows-to-burst-balloons/) +[力扣题目链接](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/) 在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 @@ -105,8 +105,8 @@ public: }; ``` -* 时间复杂度:$O(n\log n)$,因为有一个快排 -* 空间复杂度:$O(1)$ +* 时间复杂度:O(nlog n),因为有一个快排 +* 空间复杂度:O(1),有一个快排,最差情况(倒序)时,需要n次递归调用。因此确实需要O(n)的栈空间 可以看出代码并不复杂。 @@ -136,17 +136,29 @@ public: ### Java ```java +/** +时间复杂度 : O(NlogN) 排序需要 O(NlogN) 的复杂度 + +空间复杂度 : O(logN) java所使用的内置函数用的是快速排序需要 logN 的空间 +*/ class Solution { public int findMinArrowShots(int[][] points) { if (points.length == 0) return 0; - Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[0])); - + //用x[0] - y[0] 会大于2147483647 造成整型溢出 + Arrays.sort(points, (x, y) -> Integer.compare(x[0], y[0])); + //count = 1 因为最少需要一个箭来射击第一个气球 int count = 1; - for (int i = 1; i < points.length; i++) { - if (points[i][0] > points[i - 1][1]) { + //重叠气球的最小右边界 + int leftmostRightBound = points[0][1]; + //如果下一个气球的左边界大于最小右边界 + for(int i = 1; i < points.length; i++){ + if (points[i][0] > leftmostRightBound ) { + //增加一次射击 count++; + leftmostRightBound = points[i][1]; + //不然就更新最小右边界 } else { - points[i][1] = Math.min(points[i][1],points[i - 1][1]); + leftmostRightBound = Math.min(leftmostRightBound , points[i][1]); } } return count; @@ -288,5 +300,30 @@ impl Solution { } } ``` + +### Scala + +```scala +object Solution { + def findMinArrowShots(points: Array[Array[Int]]): Int = { + if (points.length == 0) return 0 + // 排序 + var point = points.sortWith((a, b) => { + a(0) < b(0) + }) + + var result = 1 // points不为空就至少需要一只箭 + for (i <- 1 until point.length) { + if (point(i)(0) > point(i - 1)(1)) { + result += 1 + } else { + point(i)(1) = math.min(point(i - 1)(1), point(i)(1)) + } + } + result // 返回结果 + } +} +``` + -----------------------
diff --git a/problems/0454.四数相加II.md b/problems/0454.四数相加II.md index 962fe7a5..b735c106 100644 --- a/problems/0454.四数相加II.md +++ b/problems/0454.四数相加II.md @@ -9,7 +9,7 @@ # 第454题.四数相加II -[力扣题目链接](https://leetcode-cn.com/problems/4sum-ii/) +[力扣题目链接](https://leetcode.cn/problems/4sum-ii/) 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。 @@ -18,14 +18,19 @@ **例如:** 输入: -A = [ 1, 2] -B = [-2,-1] -C = [-1, 2] -D = [ 0, 2] +* 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 2. (1, 1, 0, 0) -> A[1] + B[1] + C[0] + D[0] = 2 + (-1) + (-1) + 0 = 0 @@ -318,6 +323,44 @@ impl Solution { } ``` + +Scala: +```scala +object Solution { + // 导包 + import scala.collection.mutable + def fourSumCount(nums1: Array[Int], nums2: Array[Int], nums3: Array[Int], nums4: Array[Int]): Int = { + // 定义一个HashMap,key存储值,value存储该值出现的次数 + val map = new mutable.HashMap[Int, Int]() + // 遍历前两个数组,把他们所有可能的情况都记录到map + for (i <- nums1.indices) { + for (j <- nums2.indices) { + val tmp = nums1(i) + nums2(j) + // 如果包含该值,则对他的key加1,不包含则添加进去 + if (map.contains(tmp)) { + map.put(tmp, map.get(tmp).get + 1) + } else { + map.put(tmp, 1) + } + } + } + var res = 0 // 结果变量 + // 遍历后两个数组 + for (i <- nums3.indices) { + for (j <- nums4.indices) { + val tmp = -(nums3(i) + nums4(j)) + // 如果map中存在该值,结果就+=value + if (map.contains(tmp)) { + res += map.get(tmp).get + } + } + } + // 返回最终结果,可以省略关键字return + res + } +} +``` + C#: ```csharp public int FourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) { diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md index d95a407a..f9e8e7f1 100644 --- a/problems/0455.分发饼干.md +++ b/problems/0455.分发饼干.md @@ -7,7 +7,7 @@ # 455.分发饼干 -[力扣题目链接](https://leetcode-cn.com/problems/assign-cookies/) +[力扣题目链接](https://leetcode.cn/problems/assign-cookies/) 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 @@ -106,7 +106,7 @@ public: ## 其他语言版本 -### Java +### Java ```java class Solution { // 思路1:优先考虑饼干,小饼干先喂饱小胃口 @@ -145,7 +145,7 @@ class Solution { } ``` -### Python +### Python ```python class Solution: # 思路1:优先考虑胃饼干 @@ -166,13 +166,13 @@ class Solution: s.sort() start, count = len(s) - 1, 0 for index in range(len(g) - 1, -1, -1): # 先喂饱大胃口 - if start >= 0 and g[index] <= s[start]: + if start >= 0 and g[index] <= s[start]: start -= 1 count += 1 return count ``` -### Go +### Go ```golang //排序后,局部最优 func findContentChildren(g []int, s []int) int { @@ -191,7 +191,27 @@ func findContentChildren(g []int, s []int) int { } ``` -### Javascript +### Rust +```rust +pub fn find_content_children(children: Vec, cookie: Vec) -> i32 { + let mut children = children; + let mut cookies = cookie; + children.sort(); + cookies.sort(); + + let (mut child, mut cookie) = (0usize, 0usize); + while child < children.len() && cookie < cookies.len() { + // 优先选择最小饼干喂饱孩子 + if children[child] <= cookies[cookie] { + child += 1; + } + cookie += 1 + } + child as i32 +} +``` + +### Javascript ```js var findContentChildren = function(g, s) { g = g.sort((a, b) => a - b) @@ -203,7 +223,7 @@ var findContentChildren = function(g, s) { result++ index-- } - } + } return result }; @@ -251,7 +271,7 @@ function findContentChildren(g: number[], s: number[]): number { }; ``` -### C +### C ```c int cmp(int* a, int* b) { @@ -261,7 +281,7 @@ int cmp(int* a, int* b) { int findContentChildren(int* g, int gSize, int* s, int sSize){ if(sSize == 0) return 0; - + //将两个数组排序为升序 qsort(g, gSize, sizeof(int), cmp); qsort(s, sSize, sizeof(int), cmp); @@ -276,5 +296,26 @@ int findContentChildren(int* g, int gSize, int* s, int sSize){ } ``` +### Scala + +```scala +object Solution { + def findContentChildren(g: Array[Int], s: Array[Int]): Int = { + var result = 0 + var children = g.sorted + var cookie = s.sorted + // 遍历饼干 + var j = 0 + for (i <- cookie.indices) { + if (j < children.size && cookie(i) >= children(j)) { + j += 1 + result += 1 + } + } + result + } +} +``` + -----------------------
diff --git a/problems/0459.重复的子字符串.md b/problems/0459.重复的子字符串.md index a51c68ee..0c662148 100644 --- a/problems/0459.重复的子字符串.md +++ b/problems/0459.重复的子字符串.md @@ -11,7 +11,7 @@ # 459.重复的子字符串 -[力扣题目链接](https://leetcode-cn.com/problems/repeated-substring-pattern/) +[力扣题目链接](https://leetcode.cn/problems/repeated-substring-pattern/) 给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。 @@ -49,7 +49,7 @@ 数组长度为:len。 -如果len % (len - (next[len - 1] + 1)) == 0 ,则说明 (数组长度-最长相等前后缀的长度) 正好可以被 数组的长度整除,说明有该字符串有重复的子字符串。 +如果len % (len - (next[len - 1] + 1)) == 0 ,则说明数组的长度正好可以被 (数组长度-最长相等前后缀的长度) 整除 ,说明该字符串有重复的子字符串。 **数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。** @@ -63,7 +63,7 @@ next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf的最长相同前后缀的长度。 -(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。 +(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 12 正好可以被 4 整除,所以说明有重复的子字符串(asdf)。 C++代码如下:(这里使用了前缀表统一减一的实现方式) @@ -505,5 +505,37 @@ Swift: } ``` +Rust: + +>前缀表统一不减一 +```Rust +impl Solution { + pub fn get_next(next: &mut Vec, s: &Vec) { + let len = s.len(); + let mut j = 0; + for i in 1..len { + while j > 0 && s[i] != s[j] { + j = next[j - 1]; + } + if s[i] == s[j] { + j += 1; + } + next[i] = j; + } + } + + pub fn repeated_substring_pattern(s: String) -> bool { + let s = s.chars().collect::>(); + let len = s.len(); + if len == 0 { return false; }; + let mut next = vec![0; len]; + Self::get_next(&mut next, &s); + if next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0 { return true; } + return false; + } +} +``` + + -----------------------
diff --git a/problems/0463.岛屿的周长.md b/problems/0463.岛屿的周长.md index 9911dfe5..ea038140 100644 --- a/problems/0463.岛屿的周长.md +++ b/problems/0463.岛屿的周长.md @@ -5,7 +5,7 @@

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

# 463. 岛屿的周长 -[力扣题目链接](https://leetcode-cn.com/problems/island-perimeter/) +[力扣题目链接](https://leetcode.cn/problems/island-perimeter/) ## 思路 diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index 964df4a8..8ebe303d 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -7,7 +7,7 @@ ## 474.一和零 -[力扣题目链接](https://leetcode-cn.com/problems/ones-and-zeroes/) +[力扣题目链接](https://leetcode.cn/problems/ones-and-zeroes/) 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 @@ -163,7 +163,7 @@ public: ## 其他语言版本 -Java: +### Java ```Java class Solution { public int findMaxForm(String[] strs, int m, int n) { @@ -192,7 +192,7 @@ class Solution { } ``` -Python: +### Python ```python class Solution: def findMaxForm(self, strs: List[str], m: int, n: int) -> int: @@ -208,7 +208,7 @@ class Solution: return dp[m][n] ``` -Go: +### Go ```go func findMaxForm(strs []string, m int, n int) int { // 定义数组 @@ -294,7 +294,7 @@ func getMax(a,b int)int{ } ``` -Javascript: +### Javascript ```javascript const findMaxForm = (strs, m, n) => { const dp = Array.from(Array(m+1), () => Array(n+1).fill(0)); @@ -323,7 +323,203 @@ const findMaxForm = (strs, m, n) => { }; ``` +### TypeScript +> 滚动数组,二维数组法 + +```typescript +type BinaryInfo = { numOfZero: number, numOfOne: number }; +function findMaxForm(strs: string[], m: number, n: number): number { + const goodsNum: number = strs.length; + const dp: number[][] = new Array(m + 1).fill(0) + .map(_ => new Array(n + 1).fill(0)); + for (let i = 0; i < goodsNum; i++) { + const { numOfZero, numOfOne } = countBinary(strs[i]); + for (let j = m; j >= numOfZero; j--) { + for (let k = n; k >= numOfOne; k--) { + dp[j][k] = Math.max(dp[j][k], dp[j - numOfZero][k - numOfOne] + 1); + } + } + } + return dp[m][n]; +}; +function countBinary(str: string): BinaryInfo { + let numOfZero: number = 0, + numOfOne: number = 0; + for (let s of str) { + if (s === '0') { + numOfZero++; + } else { + numOfOne++; + } + } + return { numOfZero, numOfOne }; +} +``` + +> 传统背包,三维数组法 + +```typescript +type BinaryInfo = { numOfZero: number, numOfOne: number }; +function findMaxForm(strs: string[], m: number, n: number): number { + /** + dp[i][j][k]: 前i个物品中, 背包的0容量为j, 1容量为k, 最多能放的物品数量 + */ + const goodsNum: number = strs.length; + const dp: number[][][] = new Array(goodsNum).fill(0) + .map(_ => new Array(m + 1) + .fill(0) + .map(_ => new Array(n + 1).fill(0)) + ); + const { numOfZero, numOfOne } = countBinary(strs[0]); + for (let i = numOfZero; i <= m; i++) { + for (let j = numOfOne; j <= n; j++) { + dp[0][i][j] = 1; + } + } + for (let i = 1; i < goodsNum; i++) { + const { numOfZero, numOfOne } = countBinary(strs[i]); + for (let j = 0; j <= m; j++) { + for (let k = 0; k <= n; k++) { + if (j < numOfZero || k < numOfOne) { + dp[i][j][k] = dp[i - 1][j][k]; + } else { + dp[i][j][k] = Math.max(dp[i - 1][j][k], dp[i - 1][j - numOfZero][k - numOfOne] + 1); + } + } + } + } + return dp[dp.length - 1][m][n]; +}; +function countBinary(str: string): BinaryInfo { + let numOfZero: number = 0, + numOfOne: number = 0; + for (let s of str) { + if (s === '0') { + numOfZero++; + } else { + numOfOne++; + } + } + return { numOfZero, numOfOne }; +} +``` + +> 回溯法(会超时) + +```typescript +function findMaxForm(strs: string[], m: number, n: number): number { + /** + 思路:暴力枚举strs的所有子集,记录符合条件子集的最大长度 + */ + let resMax: number = 0; + backTrack(strs, m, n, 0, []); + return resMax; + function backTrack( + strs: string[], m: number, n: number, + startIndex: number, route: string[] + ): void { + if (startIndex === strs.length) return; + for (let i = startIndex, length = strs.length; i < length; i++) { + route.push(strs[i]); + if (isValidSubSet(route, m, n)) { + resMax = Math.max(resMax, route.length); + backTrack(strs, m, n, i + 1, route); + } + route.pop(); + } + } +}; +function isValidSubSet(strs: string[], m: number, n: number): boolean { + let zeroNum: number = 0, + oneNum: number = 0; + strs.forEach(str => { + for (let s of str) { + if (s === '0') { + zeroNum++; + } else { + oneNum++; + } + } + }); + return zeroNum <= m && oneNum <= n; +} +``` + +### Scala + +背包: +```scala +object Solution { + def findMaxForm(strs: Array[String], m: Int, n: Int): Int = { + var dp = Array.ofDim[Int](m + 1, n + 1) + + var (oneNum, zeroNum) = (0, 0) + + for (str <- strs) { + oneNum = 0 + zeroNum = 0 + for (i <- str.indices) { + if (str(i) == '0') zeroNum += 1 + else oneNum += 1 + } + + for (i <- m to zeroNum by -1) { + for (j <- n to oneNum by -1) { + dp(i)(j) = math.max(dp(i)(j), dp(i - zeroNum)(j - oneNum) + 1) + } + } + } + + dp(m)(n) + } +} +``` + +回溯法(超时): +```scala +object Solution { + import scala.collection.mutable + + var res = Int.MinValue + + def test(str: String): (Int, Int) = { + var (zero, one) = (0, 0) + for (i <- str.indices) { + if (str(i) == '1') one += 1 + else zero += 1 + } + (zero, one) + } + + def travsel(strs: Array[String], path: mutable.ArrayBuffer[String], m: Int, n: Int, startIndex: Int): Unit = { + if (startIndex > strs.length) { + return + } + + res = math.max(res, path.length) + + for (i <- startIndex until strs.length) { + + var (zero, one) = test(strs(i)) + + // 如果0的个数小于m,1的个数小于n,则可以回溯 + if (zero <= m && one <= n) { + path.append(strs(i)) + travsel(strs, path, m - zero, n - one, i + 1) + path.remove(path.length - 1) + } + } + } + + def findMaxForm(strs: Array[String], m: Int, n: Int): Int = { + res = Int.MinValue + var path = mutable.ArrayBuffer[String]() + travsel(strs, path, m, n, 0) + res + } +} +``` -----------------------
diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 3ea2382b..080984f2 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -9,7 +9,7 @@ # 491.递增子序列 -[力扣题目链接](https://leetcode-cn.com/problems/increasing-subsequences/) +[力扣题目链接](https://leetcode.cn/problems/increasing-subsequences/) 给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。 @@ -522,5 +522,39 @@ func findSubsequences(_ nums: [Int]) -> [[Int]] { ``` +## Scala + +```scala +object Solution { + import scala.collection.mutable + def findSubsequences(nums: Array[Int]): List[List[Int]] = { + var result = mutable.ListBuffer[List[Int]]() + var path = mutable.ListBuffer[Int]() + + def backtracking(startIndex: Int): Unit = { + // 集合元素大于1,添加到结果集 + if (path.size > 1) { + result.append(path.toList) + } + + var used = new Array[Boolean](201) + // 使用循环守卫,当前层没有用过的元素才有资格进入回溯 + for (i <- startIndex until nums.size if !used(nums(i) + 100)) { + // 如果path没元素或 当前循环的元素比path的最后一个元素大,则可以进入回溯 + if (path.size == 0 || (!path.isEmpty && nums(i) >= path(path.size - 1))) { + used(nums(i) + 100) = true + path.append(nums(i)) + backtracking(i + 1) + path.remove(path.size - 1) + } + } + } + + backtracking(0) + result.toList + } +} +``` + -----------------------
diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index 99b76834..28fe3865 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -7,7 +7,7 @@ ## 494. 目标和 -[力扣题目链接](https://leetcode-cn.com/problems/target-sum/) +[力扣题目链接](https://leetcode.cn/problems/target-sum/) 难度:中等 @@ -213,6 +213,7 @@ public: if (abs(S) > sum) return 0; // 此时没有方案 if ((S + sum) % 2 == 1) return 0; // 此时没有方案 int bagSize = (S + sum) / 2; + if (bagsize < 0) return 0; vector dp(bagSize + 1, 0); dp[0] = 1; for (int i = 0; i < nums.size(); i++) { @@ -250,7 +251,7 @@ dp[j] += dp[j - nums[i]]; ## 其他语言版本 -Java: +### Java ```java class Solution { public int findTargetSumWays(int[] nums, int target) { @@ -271,7 +272,7 @@ class Solution { } ``` -Python: +### Python ```python class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: @@ -287,7 +288,7 @@ class Solution: return dp[bagSize] ``` -Go: +### Go ```go func findTargetSumWays(nums []int, target int) int { sum := 0 @@ -322,7 +323,7 @@ func abs(x int) int { } ``` -Javascript: +### Javascript ```javascript const findTargetSumWays = (nums, target) => { @@ -352,6 +353,49 @@ const findTargetSumWays = (nums, target) => { ``` +### TypeScript + +TypeScript: + +```ts +function findTargetSumWays(nums: number[], target: number): number { + // 把数组分成两个组合left, right.left + right = sum, left - right = target. + const sum: number = nums.reduce((a: number, b: number): number => a + b); + if ((sum + target) % 2 || Math.abs(target) > sum) return 0; + const left: number = (sum + target) / 2; + + // 将问题转化为装满容量为left的背包有多少种方法 + // dp[i]表示装满容量为i的背包有多少种方法 + const dp: number[] = new Array(left + 1).fill(0); + dp[0] = 1; // 装满容量为0的背包有1种方法(什么也不装) + for (let i: number = 0; i < nums.length; i++) { + for (let j: number = left; j >= nums[i]; j--) { + dp[j] += dp[j - nums[i]]; + } + } + return dp[left]; +}; +``` + +### Scala + +```scala +object Solution { + def findTargetSumWays(nums: Array[Int], target: Int): Int = { + var sum = nums.sum + if (math.abs(target) > sum) return 0 // 此时没有方案 + if ((sum + target) % 2 == 1) return 0 // 此时没有方案 + var bagSize = (sum + target) / 2 + var dp = new Array[Int](bagSize + 1) + dp(0) = 1 + for (i <- 0 until nums.length; j <- bagSize to nums(i) by -1) { + dp(j) += dp(j - nums(i)) + } + + dp(bagSize) + } +} +``` -----------------------
diff --git a/problems/0496.下一个更大元素I.md b/problems/0496.下一个更大元素I.md index f9dfa308..3c948815 100644 --- a/problems/0496.下一个更大元素I.md +++ b/problems/0496.下一个更大元素I.md @@ -6,7 +6,7 @@ # 496.下一个更大元素 I -[力扣题目链接](https://leetcode-cn.com/problems/next-greater-element-i/) +[力扣题目链接](https://leetcode.cn/problems/next-greater-element-i/) 给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。 @@ -244,6 +244,39 @@ class Solution: ``` Go: + +> 未精简版本 +```go +func nextGreaterElement(nums1 []int, nums2 []int) []int { + res := make([]int, len(nums1)) + for i := range res { res[i] = -1 } + m := make(map[int]int, len(nums1)) + for k, v := range nums1 { m[v] = k } + + stack := []int{0} + for i := 1; i < len(nums2); i++ { + top := stack[len(stack)-1] + if nums2[i] < nums2[top] { + stack = append(stack, i) + } else if nums2[i] == nums2[top] { + stack = append(stack, i) + } else { + for len(stack) != 0 && nums2[i] > nums2[top] { + if v, ok := m[nums2[top]]; ok { + res[v] = nums2[i] + } + stack = stack[:len(stack)-1] + if len(stack) != 0 { + top = stack[len(stack)-1] + } + } + stack = append(stack, i) + } + } + return res +} +``` +> 精简版本 ```go func nextGreaterElement(nums1 []int, nums2 []int) []int { res := make([]int, len(nums1)) @@ -299,5 +332,36 @@ var nextGreaterElement = function (nums1, nums2) { }; ``` +TypeScript: + +```typescript +function nextGreaterElement(nums1: number[], nums2: number[]): number[] { + const resArr: number[] = new Array(nums1.length).fill(-1); + const stack: number[] = []; + const helperMap: Map = new Map(); + nums1.forEach((num, index) => { + helperMap.set(num, index); + }) + stack.push(0); + for (let i = 1, length = nums2.length; i < length; i++) { + let top = stack[stack.length - 1]; + while (stack.length > 0 && nums2[top] < nums2[i]) { + let index = helperMap.get(nums2[top]); + if (index !== undefined) { + resArr[index] = nums2[i]; + } + stack.pop(); + top = stack[stack.length - 1]; + } + if (helperMap.get(nums2[i]) !== undefined) { + stack.push(i); + } + } + return resArr; +}; +``` + + + -----------------------
diff --git a/problems/0501.二叉搜索树中的众数.md b/problems/0501.二叉搜索树中的众数.md index 9cb5d071..671d8061 100644 --- a/problems/0501.二叉搜索树中的众数.md +++ b/problems/0501.二叉搜索树中的众数.md @@ -9,7 +9,9 @@ # 501.二叉搜索树中的众数 -[力扣题目链接](https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/solution/) + +[力扣题目链接](https://leetcode.cn/problems/find-mode-in-binary-search-tree/) + 给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。 @@ -798,7 +800,76 @@ function findMode(root: TreeNode | null): number[] { }; ``` +## Scala +暴力: +```scala +object Solution { + // 导包 + import scala.collection.mutable // 集合包 + import scala.util.control.Breaks.{break, breakable} // 流程控制包 + def findMode(root: TreeNode): Array[Int] = { + var map = mutable.HashMap[Int, Int]() // 存储节点的值,和该值出现的次数 + def searchBST(curNode: TreeNode): Unit = { + if (curNode == null) return + var value = map.getOrElse(curNode.value, 0) + map.put(curNode.value, value + 1) + searchBST(curNode.left) + searchBST(curNode.right) + } + searchBST(root) // 前序遍历把每个节点的值加入到里面 + // 将map转换为list,随后根据元组的第二个值进行排序 + val list = map.toList.sortWith((map1, map2) => { + if (map1._2 > map2._2) true else false + }) + var res = mutable.ArrayBuffer[Int]() + res.append(list(0)._1) // 将第一个加入结果集 + breakable { + for (i <- 1 until list.size) { + // 如果值相同就加入结果集合,反之break + if (list(i)._2 == list(0)._2) res.append(list(i)._1) + else break + } + } + res.toArray // 最终返回res的Array格式,return关键字可以省略 + } +} +``` + +递归(利用二叉搜索树的性质): +```scala +object Solution { + import scala.collection.mutable + def findMode(root: TreeNode): Array[Int] = { + var maxCount = 0 // 最大频率 + var count = 0 // 统计频率 + var pre: TreeNode = null + var result = mutable.ArrayBuffer[Int]() + + def searchBST(cur: TreeNode): Unit = { + if (cur == null) return + searchBST(cur.left) + if (pre == null) count = 1 // 等于空置为1 + else if (pre.value == cur.value) count += 1 // 与上一个节点的值相同加1 + else count = 1 // 与上一个节点的值不同 + pre = cur + + // 如果和最大值相同,则放入结果集 + if (count == maxCount) result.append(cur.value) + + // 如果当前计数大于最大值频率,更新最大值,清空结果集 + if (count > maxCount) { + maxCount = count + result.clear() + result.append(cur.value) + } + searchBST(cur.right) + } + searchBST(root) + result.toArray // return关键字可以省略 + } +} +``` ----------------------- diff --git a/problems/0503.下一个更大元素II.md b/problems/0503.下一个更大元素II.md index ace4d40b..0bd0fb82 100644 --- a/problems/0503.下一个更大元素II.md +++ b/problems/0503.下一个更大元素II.md @@ -6,7 +6,7 @@ # 503.下一个更大元素II -[力扣题目链接](https://leetcode-cn.com/problems/next-greater-element-ii/) +[力扣题目链接](https://leetcode.cn/problems/next-greater-element-ii/) 给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。 @@ -182,5 +182,31 @@ var nextGreaterElements = function (nums) { return res; }; ``` +TypeScript: + +```typescript +function nextGreaterElements(nums: number[]): number[] { + const length: number = nums.length; + const stack: number[] = []; + stack.push(0); + const resArr: number[] = new Array(length).fill(-1); + for (let i = 1; i < length * 2; i++) { + const index = i % length; + let top = stack[stack.length - 1]; + while (stack.length > 0 && nums[top] < nums[index]) { + resArr[top] = nums[index]; + stack.pop(); + top = stack[stack.length - 1]; + } + if (i < length) { + stack.push(i); + } + } + return resArr; +}; +``` + + + -----------------------
diff --git a/problems/0509.斐波那契数.md b/problems/0509.斐波那契数.md index d339940c..0b53e698 100644 --- a/problems/0509.斐波那契数.md +++ b/problems/0509.斐波那契数.md @@ -6,7 +6,7 @@ # 509. 斐波那契数 -[力扣题目链接](https://leetcode-cn.com/problems/fibonacci-number/) +[力扣题目链接](https://leetcode.cn/problems/fibonacci-number/) 斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 @@ -234,6 +234,7 @@ func fib(n int) int { } ``` ### Javascript +解法一 ```Javascript var fib = function(n) { let dp = [0, 1] @@ -244,8 +245,47 @@ var fib = function(n) { return dp[n] }; ``` +解法二:时间复杂度O(N),空间复杂度O(1) +```Javascript +var fib = function(n) { + // 动规状态转移中,当前结果只依赖前两个元素的结果,所以只要两个变量代替dp数组记录状态过程。将空间复杂度降到O(1) + let pre1 = 1 + let pre2 = 0 + let temp + if (n === 0) return 0 + if (n === 1) return 1 + for(let i = 2; i <= n; i++) { + temp = pre1 + pre1 = pre1 + pre2 + pre2 = temp + } + return pre1 +}; +``` + +### TypeScript + +```typescript +function fib(n: number): number { + /** + dp[i]: 第i个斐波那契数 + dp[0]: 0; + dp[1]:1; + ... + dp[i] = dp[i - 1] + dp[i - 2]; + */ + const dp: number[] = []; + dp[0] = 0; + dp[1] = 1; + for (let i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + return dp[n]; +}; +``` ### C + 动态规划: ```c int fib(int n){ @@ -302,5 +342,33 @@ pub fn fib(n: i32) -> i32 { return fib(n - 1) + fib(n - 2); } ``` + +### Scala + +动态规划: +```scala +object Solution { + def fib(n: Int): Int = { + if (n <= 1) return n + var dp = new Array[Int](n + 1) + dp(1) = 1 + for (i <- 2 to n) { + dp(i) = dp(i - 1) + dp(i - 2) + } + dp(n) + } +} +``` + +递归: +```scala +object Solution { + def fib(n: Int): Int = { + if (n <= 1) return n + fib(n - 1) + fib(n - 2) + } +} +``` + -----------------------
diff --git a/problems/0513.找树左下角的值.md b/problems/0513.找树左下角的值.md index 12c62c70..817c22c9 100644 --- a/problems/0513.找树左下角的值.md +++ b/problems/0513.找树左下角的值.md @@ -7,7 +7,7 @@ # 513.找树左下角的值 -[力扣题目链接](https://leetcode-cn.com/problems/find-bottom-left-tree-value/) +[力扣题目链接](https://leetcode.cn/problems/find-bottom-left-tree-value/) 给定一个二叉树,在树的最后一行找到最左边的值。 @@ -546,7 +546,50 @@ func findBottomLeftValue(_ root: TreeNode?) -> Int { } ``` +## Scala +递归版本: +```scala +object Solution { + def findBottomLeftValue(root: TreeNode): Int = { + var maxLeftValue = 0 + var maxLen = Int.MinValue + // 递归方法 + def traversal(node: TreeNode, leftLen: Int): Unit = { + // 如果左右都为空并且当前深度大于最大深度,记录最左节点的值 + if (node.left == null && node.right == null && leftLen > maxLen) { + maxLen = leftLen + maxLeftValue = node.value + } + if (node.left != null) traversal(node.left, leftLen + 1) + if (node.right != null) traversal(node.right, leftLen + 1) + } + traversal(root, 0) // 调用方法 + maxLeftValue // return关键字可以省略 + } +} +``` + +层序遍历: +```scala + import scala.collection.mutable + + def findBottomLeftValue(root: TreeNode): Int = { + val queue = mutable.Queue[TreeNode]() + queue.enqueue(root) + var res = 0 // 记录每层最左侧结果 + while (!queue.isEmpty) { + val len = queue.size + for (i <- 0 until len) { + val curNode = queue.dequeue() + if (i == 0) res = curNode.value // 记录最最左侧的节点 + if (curNode.left != null) queue.enqueue(curNode.left) + if (curNode.right != null) queue.enqueue(curNode.right) + } + } + res // 最终返回结果,return关键字可以省略 + } +``` -----------------------
diff --git a/problems/0516.最长回文子序列.md b/problems/0516.最长回文子序列.md index 69536cef..60d5e920 100644 --- a/problems/0516.最长回文子序列.md +++ b/problems/0516.最长回文子序列.md @@ -5,7 +5,7 @@

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

## 516.最长回文子序列 -[力扣题目链接](https://leetcode-cn.com/problems/longest-palindromic-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/longest-palindromic-subsequence/) 给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。 @@ -186,29 +186,28 @@ class Solution: Go: ```Go func longestPalindromeSubseq(s string) int { - lenth:=len(s) - dp:=make([][]int,lenth) - for i:=0;i b { + return a + } + return b + } + dp := make([][]int, size) + for i := 0; i < size; i++ { + dp[i] = make([]int, size) + dp[i][i] = 1 + } + for i := size - 1; i >= 0; i-- { + for j := i + 1; j < size; j++ { + if s[i] == s[j] { + dp[i][j] = dp[i+1][j-1] + 2 + } else { + dp[i][j] = max(dp[i][j-1], dp[i+1][j]) } } } - for i:=lenth-1;i>=0;i--{ - for j:=i+1;j { }; ``` +TypeScript: + +```typescript +function longestPalindromeSubseq(s: string): number { + /** + dp[i][j]:[i,j]区间内,最长回文子序列的长度 + */ + const length: number = s.length; + const dp: number[][] = new Array(length).fill(0) + .map(_ => new Array(length).fill(0)); + for (let i = 0; i < length; i++) { + dp[i][i] = 1; + } + // 自下而上,自左往右遍历 + for (let i = length - 1; i >= 0; i--) { + for (let j = i + 1; j < length; j++) { + if (s[i] === s[j]) { + dp[i][j] = dp[i + 1][j - 1] + 2; + } else { + dp[i][j] = Math.max(dp[i][j - 1], dp[i + 1][j]); + } + } + } + return dp[0][length - 1]; +}; +``` + + + -----------------------
diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md index e72c5f85..222a10d7 100644 --- a/problems/0518.零钱兑换II.md +++ b/problems/0518.零钱兑换II.md @@ -7,7 +7,7 @@ ## 518. 零钱兑换 II -[力扣题目链接](https://leetcode-cn.com/problems/coin-change-2/) +[力扣题目链接](https://leetcode.cn/problems/coin-change-2/) 难度:中等 @@ -242,6 +242,22 @@ func change(amount int, coins []int) int { } ``` +Rust: +```rust +pub fn change(amount: i32, coins: Vec) -> i32 { + let amount = amount as usize; + let coins = coins.iter().map(|&c|c as usize).collect::>(); + let mut dp = vec![0usize; amount + 1]; + dp[0] = 1; + for i in 0..coins.len() { + for j in coins[i]..=amount { + dp[j] += dp[j - coins[i]]; + } + } + dp[amount] as i32 +} +``` + Javascript: ```javascript const change = (amount, coins) => { @@ -258,6 +274,21 @@ const change = (amount, coins) => { } ``` +TypeScript: + +```typescript +function change(amount: number, coins: number[]): number { + const dp: number[] = new Array(amount + 1).fill(0); + dp[0] = 1; + for (let i = 0, length = coins.length; i < length; i++) { + for (let j = coins[i]; j <= amount; j++) { + dp[j] += dp[j - coins[i]]; + } + } + return dp[amount]; +}; +``` + ----------------------- diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md index 77699c9f..809f500b 100644 --- a/problems/0530.二叉搜索树的最小绝对差.md +++ b/problems/0530.二叉搜索树的最小绝对差.md @@ -9,7 +9,7 @@ # 530.二叉搜索树的最小绝对差 -[力扣题目链接](https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/) +[力扣题目链接](https://leetcode.cn/problems/minimum-absolute-difference-in-bst/) 给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。 @@ -431,7 +431,84 @@ function getMinimumDifference(root: TreeNode | null): number { }; ``` +## Scala +构建二叉树的有序数组: + +```scala +object Solution { + import scala.collection.mutable + def getMinimumDifference(root: TreeNode): Int = { + val arr = mutable.ArrayBuffer[Int]() + def traversal(node: TreeNode): Unit = { + if (node == null) return + traversal(node.left) + arr.append(node.value) + traversal(node.right) + } + traversal(root) + // 在有序数组上求最小差值 + var result = Int.MaxValue + for (i <- 1 until arr.size) { + result = math.min(result, arr(i) - arr(i - 1)) + } + result // 返回最小差值 + } +} +``` + +递归记录前一个节点: + +```scala +object Solution { + def getMinimumDifference(root: TreeNode): Int = { + var result = Int.MaxValue // 初始化为最大值 + var pre: TreeNode = null // 记录前一个节点 + + def traversal(cur: TreeNode): Unit = { + if (cur == null) return + traversal(cur.left) + if (pre != null) { + // 对比result与节点之间的差值 + result = math.min(result, cur.value - pre.value) + } + pre = cur + traversal(cur.right) + } + + traversal(root) + result // return关键字可以省略 + } +} +``` + +迭代解决: + +```scala +object Solution { + import scala.collection.mutable + def getMinimumDifference(root: TreeNode): Int = { + var result = Int.MaxValue // 初始化为最大值 + var pre: TreeNode = null // 记录前一个节点 + var cur = root + var stack = mutable.Stack[TreeNode]() + while (cur != null || !stack.isEmpty) { + if (cur != null) { + stack.push(cur) + cur = cur.left + } else { + cur = stack.pop() + if (pre != null) { + result = math.min(result, cur.value - pre.value) + } + pre = cur + cur = cur.right + } + } + result // return关键字可以省略 + } +} +``` -----------------------
diff --git a/problems/0538.把二叉搜索树转换为累加树.md b/problems/0538.把二叉搜索树转换为累加树.md index 37eb7d0f..5c1e9e8c 100644 --- a/problems/0538.把二叉搜索树转换为累加树.md +++ b/problems/0538.把二叉搜索树转换为累加树.md @@ -7,7 +7,7 @@ # 538.把二叉搜索树转换为累加树 -[力扣题目链接](https://leetcode-cn.com/problems/convert-bst-to-greater-tree/) +[力扣题目链接](https://leetcode.cn/problems/convert-bst-to-greater-tree/) 给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。 @@ -352,6 +352,24 @@ function convertBST(root: TreeNode | null): TreeNode | null { }; ``` +## Scala + +```scala +object Solution { + def convertBST(root: TreeNode): TreeNode = { + var sum = 0 + def convert(node: TreeNode): Unit = { + if (node == null) return + convert(node.right) + sum += node.value + node.value = sum + convert(node.left) + } + convert(root) + root + } +} +``` ----------------------- diff --git a/problems/0541.反转字符串II.md b/problems/0541.反转字符串II.md index 8c13a390..f67e9847 100644 --- a/problems/0541.反转字符串II.md +++ b/problems/0541.反转字符串II.md @@ -10,7 +10,7 @@ # 541. 反转字符串II -[力扣题目链接](https://leetcode-cn.com/problems/reverse-string-ii/) +[力扣题目链接](https://leetcode.cn/problems/reverse-string-ii/) 给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。 @@ -53,16 +53,19 @@ public: // 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符 if (i + k <= s.size()) { reverse(s.begin() + i, s.begin() + i + k ); - continue; + } else { + // 3. 剩余字符少于 k 个,则将剩余字符全部反转。 + reverse(s.begin() + i, s.end()); } - // 3. 剩余字符少于 k 个,则将剩余字符全部反转。 - reverse(s.begin() + i, s.begin() + s.size()); } return s; } }; ``` + + + 那么我们也可以实现自己的reverse函数,其实和题目[344. 反转字符串](https://programmercarl.com/0344.反转字符串.html)道理是一样的。 下面我实现的reverse函数区间是左闭右闭区间,代码如下: @@ -92,7 +95,24 @@ public: ``` +另一种思路的解法 +```CPP +class Solution { +public: + string reverseStr(string s, int k) { + int n = s.size(),pos = 0; + while(pos < n){ + //剩余字符串大于等于k的情况 + if(pos + k < n) reverse(s.begin() + pos, s.begin() + pos + k); + //剩余字符串不足k的情况 + else reverse(s.begin() + pos,s.end()); + pos += 2 * k; + } + return s; + } +}; +``` @@ -347,6 +367,78 @@ public class Solution } } ``` +Scala: + +版本一: (正常解法) +```scala +object Solution { + def reverseStr(s: String, k: Int): String = { + val res = s.toCharArray // 转换为Array好处理 + for (i <- s.indices by 2 * k) { + // 如果i+k大于了res的长度,则需要全部翻转 + if (i + k > res.length) { + reverse(res, i, s.length - 1) + } else { + reverse(res, i, i + k - 1) + } + } + new String(res) + } + // 翻转字符串,从start到end + def reverse(s: Array[Char], start: Int, end: Int): Unit = { + var (left, right) = (start, end) + while (left < right) { + var tmp = s(left) + s(left) = s(right) + s(right) = tmp + left += 1 + right -= 1 + } + } +} +``` +版本二: 首先利用sliding每隔k个进行分割,随后转换为数组,再使用zipWithIndex添加每个数组的索引,紧接着利用map做变换,如果索引%2==0则说明需要翻转,否则原封不动,最后再转换为String +```scala +object Solution { + def reverseStr(s: String, k: Int): String = { + s.sliding(k, k) + .toArray + .zipWithIndex + .map(v => if (v._2 % 2 == 0) v._1.reverse else v._1) + .mkString + } +} +``` + +Rust: + +```Rust +impl Solution { + pub fn reverse(s: &mut Vec, mut begin: usize, mut end: usize){ + while begin < end { + let temp = s[begin]; + s[begin] = s[end]; + s[end] = temp; + begin += 1; + end -= 1; + } + } + pub fn reverse_str(s: String, k: i32) -> String { + let len = s.len(); + let k = k as usize; + let mut s = s.chars().collect::>(); + for i in (0..len).step_by(2 * k) { + if i + k < len { + Self::reverse(&mut s, i, i + k - 1); + } + else { + Self::reverse(&mut s, i, len - 1); + } + } + s.iter().collect::() + } +} +``` -----------------------
diff --git a/problems/0583.两个字符串的删除操作.md b/problems/0583.两个字符串的删除操作.md index 53c1a125..fc7e6f39 100644 --- a/problems/0583.两个字符串的删除操作.md +++ b/problems/0583.两个字符串的删除操作.md @@ -6,7 +6,7 @@ ## 583. 两个字符串的删除操作 -[力扣题目链接](https://leetcode-cn.com/problems/delete-operation-for-two-strings/) +[力扣题目链接](https://leetcode.cn/problems/delete-operation-for-two-strings/) 给定两个单词 word1 和 word2,找到使得 word1 和 word2 相同所需的最小步数,每步可以删除任意一个字符串中的一个字符。 @@ -229,6 +229,67 @@ const minDistance = (word1, word2) => { }; ``` +TypeScript: + +> dp版本一: + +```typescript +function minDistance(word1: string, word2: string): number { + /** + dp[i][j]: word1前i个字符,word2前j个字符,所需最小步数 + dp[0][0]=0: word1前0个字符为'', word2前0个字符为'' + */ + const length1: number = word1.length, + length2: number = word2.length; + const dp: number[][] = new Array(length1 + 1).fill(0) + .map(_ => new Array(length2 + 1).fill(0)); + for (let i = 0; i <= length1; i++) { + dp[i][0] = i; + } + for (let i = 0; i <= length2; i++) { + dp[0][i] = i; + } + for (let i = 1; i <= length1; i++) { + for (let j = 1; j <= length2; j++) { + if (word1[i - 1] === word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1]; + } else { + dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1; + } + } + } + return dp[length1][length2]; +}; +``` + +> dp版本二: + +```typescript +function minDistance(word1: string, word2: string): number { + /** + dp[i][j]: word1前i个字符,word2前j个字符,最长公共子序列的长度 + dp[0][0]=0: word1前0个字符为'', word2前0个字符为'' + */ + const length1: number = word1.length, + length2: number = word2.length; + const dp: number[][] = new Array(length1 + 1).fill(0) + .map(_ => new Array(length2 + 1).fill(0)); + for (let i = 1; i <= length1; i++) { + for (let j = 1; j <= length2; j++) { + if (word1[i - 1] === word2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); + } + } + } + const maxLen: number = dp[length1][length2]; + return length1 + length2 - maxLen * 2; +}; +``` + + + -----------------------
diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md index 55786ea9..6a843763 100644 --- a/problems/0617.合并二叉树.md +++ b/problems/0617.合并二叉树.md @@ -7,7 +7,7 @@ # 617.合并二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/merge-two-binary-trees/) +[力扣题目链接](https://leetcode.cn/problems/merge-two-binary-trees/) 给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。 @@ -262,10 +262,10 @@ class Solution { if (root1 == null) return root2; if (root2 == null) return root1; - TreeNode newRoot = new TreeNode(root1.val + root2.val); - newRoot.left = mergeTrees(root1.left,root2.left); - newRoot.right = mergeTrees(root1.right,root2.right); - return newRoot; + root1.val += root2.val; + root1.left = mergeTrees(root1.left,root2.left); + root1.right = mergeTrees(root1.right,root2.right); + return root1; } } ``` @@ -631,7 +631,60 @@ function mergeTrees(root1: TreeNode | null, root2: TreeNode | null): TreeNode | }; ``` +## Scala +递归: +```scala +object Solution { + def mergeTrees(root1: TreeNode, root2: TreeNode): TreeNode = { + if (root1 == null) return root2 // 如果root1为空,返回root2 + if (root2 == null) return root1 // 如果root2为空,返回root1 + // 新建一个节点,值为两个节点的和 + var node = new TreeNode(root1.value + root2.value) + // 往下递归 + node.left = mergeTrees(root1.left, root2.left) + node.right = mergeTrees(root1.right, root2.right) + node // 返回node,return关键字可以省略 + } +} +``` + +迭代: +```scala +object Solution { + import scala.collection.mutable + def mergeTrees(root1: TreeNode, root2: TreeNode): TreeNode = { + if (root1 == null) return root2 + if (root2 == null) return root1 + var stack = mutable.Stack[TreeNode]() + // 先放node2再放node1 + stack.push(root2) + stack.push(root1) + while (!stack.isEmpty) { + var node1 = stack.pop() + var node2 = stack.pop() + node1.value += node2.value + if (node1.right != null && node2.right != null) { + stack.push(node2.right) + stack.push(node1.right) + } else { + if(node1.right == null){ + node1.right = node2.right + } + } + if (node1.left != null && node2.left != null) { + stack.push(node2.left) + stack.push(node1.left) + } else { + if(node1.left == null){ + node1.left = node2.left + } + } + } + root1 + } +} +``` ----------------------- diff --git a/problems/0647.回文子串.md b/problems/0647.回文子串.md index 913aec65..fa9bf7b8 100644 --- a/problems/0647.回文子串.md +++ b/problems/0647.回文子串.md @@ -6,7 +6,7 @@ ## 647. 回文子串 -[力扣题目链接](https://leetcode-cn.com/problems/palindromic-substrings/) +[力扣题目链接](https://leetcode.cn/problems/palindromic-substrings/) 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 @@ -32,7 +32,7 @@ 两层for循环,遍历区间起始位置和终止位置,然后判断这个区间是不是回文。 -时间复杂度:$O(n^3)$ +时间复杂度:O(n^3) ## 动态规划 @@ -171,8 +171,8 @@ public: }; ``` -* 时间复杂度:$O(n^2)$ -* 空间复杂度:$O(n^2)$ +* 时间复杂度:O(n^2) +* 空间复杂度:O(n^2) ## 双指针法 @@ -213,8 +213,8 @@ public: }; ``` -* 时间复杂度:$O(n^2)$ -* 空间复杂度:$O(1)$ +* 时间复杂度:O(n^2) +* 空间复杂度:O(1) ## 其他语言版本 @@ -406,6 +406,63 @@ const countSubstrings = (s) => { } ``` +TypeScript: + +> 动态规划: + +```typescript +function countSubstrings(s: string): number { + /** + dp[i][j]: [i,j]区间内的字符串是否为回文(左闭右闭) + */ + const length: number = s.length; + const dp: boolean[][] = new Array(length).fill(0) + .map(_ => new Array(length).fill(false)); + let resCount: number = 0; + // 自下而上,自左向右遍历 + for (let i = length - 1; i >= 0; i--) { + for (let j = i; j < length; j++) { + if ( + s[i] === s[j] && + (j - i <= 1 || dp[i + 1][j - 1] === true) + ) { + dp[i][j] = true; + resCount++; + } + } + } + return resCount; +}; +``` + +> 双指针法: + +```typescript +function countSubstrings(s: string): number { + const length: number = s.length; + let resCount: number = 0; + for (let i = 0; i < length; i++) { + resCount += expandRange(s, i, i); + resCount += expandRange(s, i, i + 1); + } + return resCount; +}; +function expandRange(s: string, left: number, right: number): number { + let palindromeNum: number = 0; + while ( + left >= 0 && right < s.length && + s[left] === s[right] + ) { + palindromeNum++; + left--; + right++; + } + return palindromeNum; +} +``` + + + -----------------------
diff --git a/problems/0649.Dota2参议院.md b/problems/0649.Dota2参议院.md index 6e84c9fd..3b61a9fe 100644 --- a/problems/0649.Dota2参议院.md +++ b/problems/0649.Dota2参议院.md @@ -8,7 +8,7 @@ # 649. Dota2 参议院 -[力扣题目链接](https://leetcode-cn.com/problems/dota2-senate/) +[力扣题目链接](https://leetcode.cn/problems/dota2-senate/) Dota2 的世界里有两个阵营:Radiant(天辉)和 Dire(夜魇) @@ -244,6 +244,44 @@ var predictPartyVictory = function(senateStr) { }; ``` +## TypeScript + +```typescript +function predictPartyVictory(senate: string): string { + // 数量差:Count(Radiant) - Count(Dire) + let deltaRDCnt: number = 0; + let hasR: boolean = true, + hasD: boolean = true; + const senateArr: string[] = senate.split(''); + while (hasR && hasD) { + hasR = false; + hasD = false; + for (let i = 0, length = senateArr.length; i < length; i++) { + if (senateArr[i] === 'R') { + if (deltaRDCnt < 0) { + senateArr[i] = ''; + } else { + hasR = true; + } + deltaRDCnt++; + } else if (senateArr[i] === 'D') { + if (deltaRDCnt > 0) { + senateArr[i] = ''; + } else { + hasD = true; + } + deltaRDCnt--; + } + } + } + return hasR ? 'Radiant' : 'Dire'; +}; +``` + + + + + -----------------------
diff --git a/problems/0654.最大二叉树.md b/problems/0654.最大二叉树.md index 1c73354b..e2a64bcd 100644 --- a/problems/0654.最大二叉树.md +++ b/problems/0654.最大二叉树.md @@ -7,7 +7,7 @@ # 654.最大二叉树 -[力扣题目地址](https://leetcode-cn.com/problems/maximum-binary-tree/) +[力扣题目地址](https://leetcode.cn/problems/maximum-binary-tree/) 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下: @@ -476,7 +476,33 @@ func traversal(_ nums: inout [Int], _ left: Int, _ right: Int) -> TreeNode? { } ``` +## Scala +```scala +object Solution { + def constructMaximumBinaryTree(nums: Array[Int]): TreeNode = { + if (nums.size == 0) return null + // 找到数组最大值 + var maxIndex = 0 + var maxValue = Int.MinValue + for (i <- nums.indices) { + if (nums(i) > maxValue) { + maxIndex = i + maxValue = nums(i) + } + } + + // 构建一棵树 + var root = new TreeNode(maxValue, null, null) + + // 递归寻找左右子树 + root.left = constructMaximumBinaryTree(nums.slice(0, maxIndex)) + root.right = constructMaximumBinaryTree(nums.slice(maxIndex + 1, nums.length)) + + root // 返回root + } +} +``` -----------------------
diff --git a/problems/0657.机器人能否返回原点.md b/problems/0657.机器人能否返回原点.md index 4eb69a6c..3d91a5c3 100644 --- a/problems/0657.机器人能否返回原点.md +++ b/problems/0657.机器人能否返回原点.md @@ -7,7 +7,7 @@ # 657. 机器人能否返回原点 -[力扣题目链接](https://leetcode-cn.com/problems/robot-return-to-origin/) +[力扣题目链接](https://leetcode.cn/problems/robot-return-to-origin/) 在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在 (0, 0) 处结束。 diff --git a/problems/0669.修剪二叉搜索树.md b/problems/0669.修剪二叉搜索树.md index 385b2268..a286315d 100644 --- a/problems/0669.修剪二叉搜索树.md +++ b/problems/0669.修剪二叉搜索树.md @@ -10,7 +10,7 @@ # 669. 修剪二叉搜索树 -[力扣题目链接](https://leetcode-cn.com/problems/trim-a-binary-search-tree/) +[力扣题目链接](https://leetcode.cn/problems/trim-a-binary-search-tree/) 给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。 @@ -453,7 +453,21 @@ function trimBST(root: TreeNode | null, low: number, high: number): TreeNode | n }; ``` +## Scala +递归法: +```scala +object Solution { + def trimBST(root: TreeNode, low: Int, high: Int): TreeNode = { + if (root == null) return null + if (root.value < low) return trimBST(root.right, low, high) + if (root.value > high) return trimBST(root.left, low, high) + root.left = trimBST(root.left, low, high) + root.right = trimBST(root.right, low, high) + root + } +} +``` ----------------------- diff --git a/problems/0673.最长递增子序列的个数.md b/problems/0673.最长递增子序列的个数.md index 9a2c5db2..8a6a2d46 100644 --- a/problems/0673.最长递增子序列的个数.md +++ b/problems/0673.最长递增子序列的个数.md @@ -8,7 +8,7 @@ # 673.最长递增子序列的个数 -[力扣题目链接](https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/number-of-longest-increasing-subsequence/) 给定一个未排序的整数数组,找到最长递增子序列的个数。 diff --git a/problems/0674.最长连续递增序列.md b/problems/0674.最长连续递增序列.md index e941d242..5865a68d 100644 --- a/problems/0674.最长连续递增序列.md +++ b/problems/0674.最长连续递增序列.md @@ -6,7 +6,7 @@ ## 674. 最长连续递增序列 -[力扣题目链接](https://leetcode-cn.com/problems/longest-continuous-increasing-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/longest-continuous-increasing-subsequence/) 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 @@ -218,6 +218,7 @@ class Solution: return result ``` + > 贪心法: ```python class Solution: @@ -236,6 +237,63 @@ class Solution: ``` Go: +> 动态规划: +```go +func findLengthOfLCIS(nums []int) int { + if len(nums) == 0 {return 0} + res, count := 1, 1 + for i := 0; i < len(nums)-1; i++ { + if nums[i+1] > nums[i] { + count++ + }else { + count = 1 + } + if count > res { + res = count + } + } + return res +} +``` + +> 贪心算法: +```go +func findLengthOfLCIS(nums []int) int { + if len(nums) == 0 {return 0} + dp := make([]int, len(nums)) + for i := 0; i < len(dp); i++ { + dp[i] = 1 + } + res := 1 + for i := 0; i < len(nums)-1; i++ { + if nums[i+1] > nums[i] { + dp[i+1] = dp[i] + 1 + } + if dp[i+1] > res { + res = dp[i+1] + } + } + return res +} +``` + +Rust: +```rust +pub fn find_length_of_lcis(nums: Vec) -> i32 { + if nums.is_empty() { + return 0; + } + let mut result = 1; + let mut dp = vec![1; nums.len()]; + for i in 1..nums.len() { + if nums[i - 1] < nums[i] { + dp[i] = dp[i - 1] + 1; + result = result.max(dp[i]); + } + } + result +} +``` Javascript: @@ -280,6 +338,45 @@ const findLengthOfLCIS = (nums) => { }; ``` +TypeScript: + +> 动态规划: + +```typescript +function findLengthOfLCIS(nums: number[]): number { + /** + dp[i]: 前i个元素,以nums[i]结尾,最长连续子序列的长度 + */ + const dp: number[] = new Array(nums.length).fill(1); + let resMax: number = 1; + for (let i = 1, length = nums.length; i < length; i++) { + if (nums[i] > nums[i - 1]) { + dp[i] = dp[i - 1] + 1; + } + resMax = Math.max(resMax, dp[i]); + } + return resMax; +}; +``` + +> 贪心: + +```typescript +function findLengthOfLCIS(nums: number[]): number { + let resMax: number = 1; + let count: number = 1; + for (let i = 0, length = nums.length; i < length - 1; i++) { + if (nums[i] < nums[i + 1]) { + count++; + } else { + count = 1; + } + resMax = Math.max(resMax, count); + } + return resMax; +}; +``` + diff --git a/problems/0684.冗余连接.md b/problems/0684.冗余连接.md index a16833bc..3928e051 100644 --- a/problems/0684.冗余连接.md +++ b/problems/0684.冗余连接.md @@ -9,7 +9,7 @@ # 684.冗余连接 -[力扣题目链接](https://leetcode-cn.com/problems/redundant-connection/) +[力扣题目链接](https://leetcode.cn/problems/redundant-connection/) 树可以看成是一个连通且 无环 的 无向 图。 diff --git a/problems/0685.冗余连接II.md b/problems/0685.冗余连接II.md index d96d4912..a8da1124 100644 --- a/problems/0685.冗余连接II.md +++ b/problems/0685.冗余连接II.md @@ -7,7 +7,7 @@ # 685.冗余连接II -[力扣题目链接](https://leetcode-cn.com/problems/redundant-connection-ii/) +[力扣题目链接](https://leetcode.cn/problems/redundant-connection-ii/) 在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。 diff --git a/problems/0700.二叉搜索树中的搜索.md b/problems/0700.二叉搜索树中的搜索.md index 40cf4ea1..a8dcc69f 100644 --- a/problems/0700.二叉搜索树中的搜索.md +++ b/problems/0700.二叉搜索树中的搜索.md @@ -7,7 +7,7 @@ # 700.二叉搜索树中的搜索 -[力扣题目地址](https://leetcode-cn.com/problems/search-in-a-binary-search-tree/) +[力扣题目地址](https://leetcode.cn/problems/search-in-a-binary-search-tree/) 给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。 @@ -363,7 +363,34 @@ function searchBST(root: TreeNode | null, val: number): TreeNode | null { }; ``` +## Scala +递归: +```scala +object Solution { + def searchBST(root: TreeNode, value: Int): TreeNode = { + if (root == null || value == root.value) return root + // 相当于三元表达式,在Scala中if...else有返回值 + if (value < root.value) searchBST(root.left, value) else searchBST(root.right, value) + } +} +``` + +迭代: +```scala +object Solution { + def searchBST(root: TreeNode, value: Int): TreeNode = { + // 因为root是不可变量,所以需要赋值给一个可变量 + var node = root + while (node != null) { + if (value < node.value) node = node.left + else if (value > node.value) node = node.right + else return node + } + null // 没有返回就返回空 + } +} +``` -----------------------
diff --git a/problems/0701.二叉搜索树中的插入操作.md b/problems/0701.二叉搜索树中的插入操作.md index df6a3954..06e1c88f 100644 --- a/problems/0701.二叉搜索树中的插入操作.md +++ b/problems/0701.二叉搜索树中的插入操作.md @@ -7,7 +7,7 @@ # 701.二叉搜索树中的插入操作 -[力扣题目链接](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +[力扣题目链接](https://leetcode.cn/problems/insert-into-a-binary-search-tree/) 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。 @@ -279,7 +279,7 @@ class Solution: root.right = self.insertIntoBST(root.right, val) # 返回更新后的以当前root为根节点的新树 - return roo + return root ``` **递归法** - 无返回值 @@ -585,6 +585,43 @@ function insertIntoBST(root: TreeNode | null, val: number): TreeNode | null { ``` +## Scala + +递归: + +```scala +object Solution { + def insertIntoBST(root: TreeNode, `val`: Int): TreeNode = { + if (root == null) return new TreeNode(`val`) + if (`val` < root.value) root.left = insertIntoBST(root.left, `val`) + else root.right = insertIntoBST(root.right, `val`) + root // 返回根节点 + } +} +``` + +迭代: + +```scala +object Solution { + def insertIntoBST(root: TreeNode, `val`: Int): TreeNode = { + if (root == null) { + return new TreeNode(`val`) + } + var parent = root // 记录当前节点的父节点 + var curNode = root + while (curNode != null) { + parent = curNode + if(`val` < curNode.value) curNode = curNode.left + else curNode = curNode.right + } + if(`val` < parent.value) parent.left = new TreeNode(`val`) + else parent.right = new TreeNode(`val`) + root // 最终返回根节点 + } +} +``` + -----------------------
diff --git a/problems/0704.二分查找.md b/problems/0704.二分查找.md index 55625130..eec97a78 100644 --- a/problems/0704.二分查找.md +++ b/problems/0704.二分查找.md @@ -7,7 +7,7 @@ ## 704. 二分查找 -[力扣题目链接](https://leetcode-cn.com/problems/binary-search/) +[力扣题目链接](https://leetcode.cn/problems/binary-search/) 给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 @@ -36,6 +36,8 @@ ## 思路 +为了易于大家理解,我还录制了视频,可以看这里:[B站:手把手带你撕出正确的二分法](https://www.bilibili.com/video/BV1fA4y1o715) + **这道题目的前提是数组为有序数组**,同时题目还强调**数组中无重复元素**,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。 二分查找涉及的很多的边界条件,逻辑比较简单,但就是写不好。例如到底是 `while(left < right)` 还是 `while(left <= right)`,到底是`right = middle`呢,还是要`right = middle - 1`呢? @@ -218,19 +220,21 @@ class Solution: (版本二)左闭右开区间 -```python -class Solution: +```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 + if nums is None or len(nums)==0: + return -1 + l=0 + r=len(nums)-1 + while (l<=r): + m = round(l+(r-l)/2) + if nums[m] == target: + return m + elif nums[m] > target: + r=m-1 else: - return mid - return -1 + l=m+1 + return -1 ``` **Go:** @@ -611,6 +615,112 @@ public class Solution{ } ``` +**Kotlin:** +```kotlin +class Solution { + fun search(nums: IntArray, target: Int): Int { + // leftBorder + var left:Int = 0 + // rightBorder + var right:Int = nums.size - 1 + // 使用左闭右闭区间 + while (left <= right) { + var middle:Int = left + (right - left)/2 + // taget 在左边 + if (nums[middle] > target) { + right = middle - 1 + } + else { + // target 在右边 + if (nums[middle] < target) { + left = middle + 1 + } + // 找到了,返回 + else return middle + } + } + // 没找到,返回 + return -1 + } +} +``` + + + +**Kotlin:** +```Kotlin +// (版本一)左闭右开区间 +class Solution { + fun search(nums: IntArray, target: Int): Int { + var left = 0 + var right = nums.size // [left,right) 右侧为开区间,right 设置为 nums.size + while (left < right) { + val mid = (left + right) / 2 + if (nums[mid] < target) left = mid + 1 + else if (nums[mid] > target) right = mid // 代码的核心,循环中 right 是开区间,这里也应是开区间 + else return mid + } + return -1 // 没有找到 target ,返回 -1 + } +} +// (版本二)左闭右闭区间 +class Solution { + fun search(nums: IntArray, target: Int): Int { + var left = 0 + var right = nums.size - 1 // [left,right] 右侧为闭区间,right 设置为 nums.size - 1 + while (left <= right) { + val mid = (left + right) / 2 + if (nums[mid] < target) left = mid + 1 + else if (nums[mid] > target) right = mid - 1 // 代码的核心,循环中 right 是闭区间,这里也应是闭区间 + else return mid + } + return -1 // 没有找到 target ,返回 -1 + } +} +``` +**Scala:** + +(版本一)左闭右闭区间 +```scala +object Solution { + def search(nums: Array[Int], target: Int): Int = { + var left = 0 + var right = nums.length - 1 + while (left <= right) { + var mid = left + ((right - left) / 2) + if (target == nums(mid)) { + return mid + } else if (target < nums(mid)) { + right = mid - 1 + } else { + left = mid + 1 + } + } + -1 + } +} +``` +(版本二)左闭右开区间 +```scala +object Solution { + def search(nums: Array[Int], target: Int): Int = { + var left = 0 + var right = nums.length + while (left < right) { + val mid = left + (right - left) / 2 + if (target == nums(mid)) { + return mid + } else if (target < nums(mid)) { + right = mid + } else { + left = mid + 1 + } + } + -1 + } +} +``` + -----------------------
diff --git a/problems/0707.设计链表.md b/problems/0707.设计链表.md index 37ce15ad..00886039 100644 --- a/problems/0707.设计链表.md +++ b/problems/0707.设计链表.md @@ -9,7 +9,7 @@ # 707.设计链表 -[力扣题目链接](https://leetcode-cn.com/problems/design-linked-list/) +[力扣题目链接](https://leetcode.cn/problems/design-linked-list/) 题意: @@ -26,6 +26,8 @@ # 思路 +为了方便大家理解,我特意录制了视频:[帮你把链表操作学个通透!LeetCode:707.设计链表](https://www.bilibili.com/video/BV1FU4y1X7WD),结合视频在看本题解,事半功倍。 + 如果对链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://programmercarl.com/链表理论基础.html) 如果对链表的虚拟头结点不清楚,可以看这篇文章:[链表:听说用虚拟头节点会方便很多?](https://programmercarl.com/0203.移除链表元素.html) @@ -104,8 +106,9 @@ public: // 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。 // 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点 // 如果index大于链表的长度,则返回空 + // 如果index小于0,则置为0,作为链表的新头节点。 void addAtIndex(int index, int val) { - if (index > _size) { + if (index > _size || index < 0) { return; } LinkedNode* newNode = new LinkedNode(val); @@ -1154,7 +1157,75 @@ class MyLinkedList { } ``` +Scala: +```scala +class ListNode(_x: Int = 0, _next: ListNode = null) { + var next: ListNode = _next + var x: Int = _x +} +class MyLinkedList() { + + var size = 0 // 链表尺寸 + var dummy: ListNode = new ListNode(0) // 虚拟头节点 + + // 获取第index个节点的值 + def get(index: Int): Int = { + if (index < 0 || index >= size) { + return -1; + } + var cur = dummy + for (i <- 0 to index) { + cur = cur.next + } + cur.x // 返回cur的值 + } + + // 在链表最前面插入一个节点 + def addAtHead(`val`: Int) { + addAtIndex(0, `val`) + } + + // 在链表最后面插入一个节点 + def addAtTail(`val`: Int) { + addAtIndex(size, `val`) + } + + // 在第index个节点之前插入一个新节点 + // 如果index等于链表长度,则说明新插入的节点是尾巴 + // 如果index等于0,则说明新插入的节点是头 + // 如果index>链表长度,则说明为空 + def addAtIndex(index: Int, `val`: Int) { + if (index > size) { + return + } + var loc = index // 因为参数index是val不可变类型,所以需要赋值给一个可变类型 + if (index < 0) { + loc = 0 + } + size += 1 //链表尺寸+1 + var pre = dummy + for (i <- 0 until loc) { + pre = pre.next + } + val node: ListNode = new ListNode(`val`, pre.next) + pre.next = node + } + // 删除第index个节点 + def deleteAtIndex(index: Int) { + if (index < 0 || index >= size) { + return + } + size -= 1 + var pre = dummy + for (i <- 0 until index) { + pre = pre.next + } + pre.next = pre.next.next + } + +} +``` ----------------------- diff --git a/problems/0714.买卖股票的最佳时机含手续费.md b/problems/0714.买卖股票的最佳时机含手续费.md index 2f27d6ea..824e7937 100644 --- a/problems/0714.买卖股票的最佳时机含手续费.md +++ b/problems/0714.买卖股票的最佳时机含手续费.md @@ -7,7 +7,7 @@ # 714. 买卖股票的最佳时机含手续费 -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 @@ -153,7 +153,7 @@ public: ## 其他语言版本 -Java: +### Java ```java // 贪心思路 class Solution { @@ -198,7 +198,7 @@ class Solution { // 动态规划 -Python: +### Python ```python class Solution: # 贪心思路 @@ -216,7 +216,7 @@ class Solution: # 贪心思路 return result ``` -Go: +### Go ```golang func maxProfit(prices []int, fee int) int { var minBuy int = prices[0] //第一天买入 @@ -241,7 +241,7 @@ func maxProfit(prices []int, fee int) int { return res } ``` -Javascript: +### Javascript ```Javascript // 贪心思路 var maxProfit = function(prices, fee) { @@ -293,6 +293,70 @@ var maxProfit = function(prices, fee) { }; ``` +### TypeScript + +> 贪心 + +```typescript +function maxProfit(prices: number[], fee: number): number { + if (prices.length === 0) return 0; + let minPrice: number = prices[0]; + let profit: number = 0; + for (let i = 1, length = prices.length; i < length; i++) { + if (minPrice > prices[i]) { + minPrice = prices[i]; + } + if (minPrice + fee < prices[i]) { + profit += prices[i] - minPrice - fee; + minPrice = prices[i] - fee; + } + } + return profit; +}; +``` + +> 动态规划 + +```typescript +function maxProfit(prices: number[], fee: number): number { + /** + dp[i][1]: 第i天不持有股票的最大所剩现金 + dp[i][0]: 第i天持有股票的最大所剩现金 + */ + const length: number = prices.length; + const dp: number[][] = new Array(length).fill(0).map(_ => []); + dp[0][1] = 0; + dp[0][0] = -prices[0]; + for (let i = 1, length = prices.length; i < length; i++) { + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee); + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]); + } + return Math.max(dp[length - 1][0], dp[length - 1][1]); +}; +``` + +### Scala + +贪心思路: + +```scala +object Solution { + def maxProfit(prices: Array[Int], fee: Int): Int = { + var result = 0 + var minPrice = prices(0) + for (i <- 1 until prices.length) { + if (prices(i) < minPrice) { + minPrice = prices(i) // 比当前最小值还小 + } + if (prices(i) > minPrice + fee) { + result += prices(i) - minPrice - fee + minPrice = prices(i) - fee + } + } + result + } +} +``` -----------------------
diff --git a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md index 4ab63e79..7734450e 100644 --- a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md +++ b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md @@ -6,7 +6,7 @@ ## 714.买卖股票的最佳时机含手续费 -[力扣题目链接](https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) +[力扣题目链接](https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/) 给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。 @@ -200,6 +200,29 @@ const maxProfit = (prices,fee) => { } ``` +TypeScript: + +```typescript +function maxProfit(prices: number[], fee: number): number { + /** + dp[i][0]:持有股票 + dp[i][1]: 不持有 + */ + const length: number = prices.length; + if (length === 0) return 0; + const dp: number[][] = new Array(length).fill(0).map(_ => []); + dp[0][0] = -prices[0]; + dp[0][1] = 0; + for (let i = 1; i < length; i++) { + dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]); + dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i] - fee); + } + return dp[length - 1][1]; +}; +``` + + + -----------------------
diff --git a/problems/0718.最长重复子数组.md b/problems/0718.最长重复子数组.md index 87b1492a..6de8b80f 100644 --- a/problems/0718.最长重复子数组.md +++ b/problems/0718.最长重复子数组.md @@ -6,7 +6,7 @@ ## 718. 最长重复子数组 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/) +[力扣题目链接](https://leetcode.cn/problems/maximum-length-of-repeated-subarray/) 给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。 @@ -31,7 +31,7 @@ B: [3,2,1,4,7] 1. 确定dp数组(dp table)以及下标的含义 -dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 +dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 (**特别注意**: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 ) 此时细心的同学应该发现,那dp[0][0]是什么含义呢?总不能是以下标-1为结尾的A数组吧。 @@ -297,6 +297,56 @@ const findLength = (nums1, nums2) => { } ``` +TypeScript: + +> 动态规划: + +```typescript +function findLength(nums1: number[], nums2: number[]): number { + /** + dp[i][j]:nums[i-1]和nums[j-1]结尾,最长重复子数组的长度 + */ + const length1: number = nums1.length, + length2: number = nums2.length; + const dp: number[][] = new Array(length1 + 1).fill(0) + .map(_ => new Array(length2 + 1).fill(0)); + let resMax: number = 0; + for (let i = 1; i <= length1; i++) { + for (let j = 1; j <= length2; j++) { + if (nums1[i - 1] === nums2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + resMax = Math.max(resMax, dp[i][j]); + } + } + } + return resMax; +}; +``` + +> 滚动数组: + +```typescript +function findLength(nums1: number[], nums2: number[]): number { + const length1: number = nums1.length, + length2: number = nums2.length; + const dp: number[] = new Array(length1 + 1).fill(0); + let resMax: number = 0; + for (let i = 1; i <= length1; i++) { + for (let j = length2; j >= 1; j--) { + if (nums1[i - 1] === nums2[j - 1]) { + dp[j] = dp[j - 1] + 1; + resMax = Math.max(resMax, dp[j]); + } else { + dp[j] = 0; + } + } + } + return resMax; +}; +``` + + + -----------------------
diff --git a/problems/0724.寻找数组的中心索引.md b/problems/0724.寻找数组的中心索引.md index 14dcd2c0..5a24a884 100644 --- a/problems/0724.寻找数组的中心索引.md +++ b/problems/0724.寻找数组的中心索引.md @@ -7,7 +7,7 @@ # 724.寻找数组的中心下标 -[力扣题目链接](https://leetcode-cn.com/problems/find-pivot-index/) +[力扣题目链接](https://leetcode.cn/problems/find-pivot-index/) 给你一个整数数组 nums ,请计算数组的 中心下标 。 @@ -140,6 +140,24 @@ var pivotIndex = function(nums) { }; ``` +### TypeScript + +```typescript +function pivotIndex(nums: number[]): number { + const length: number = nums.length; + const sum: number = nums.reduce((a, b) => a + b); + let leftSum: number = 0; + for (let i = 0; i < length; i++) { + const rightSum: number = sum - leftSum - nums[i]; + if (leftSum === rightSum) return i; + leftSum += nums[i]; + } + return -1; +}; +``` + + + -----------------------
diff --git a/problems/0738.单调递增的数字.md b/problems/0738.单调递增的数字.md index c8ce8a2b..6b7381f3 100644 --- a/problems/0738.单调递增的数字.md +++ b/problems/0738.单调递增的数字.md @@ -6,7 +6,7 @@ # 738.单调递增的数字 -[力扣题目链接](https://leetcode-cn.com/problems/monotone-increasing-digits/) +[力扣题目链接](https://leetcode.cn/problems/monotone-increasing-digits/) 给定一个非负整数 N,找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。 @@ -225,6 +225,58 @@ var monotoneIncreasingDigits = function(n) { }; ``` +### TypeScript + +```typescript +function monotoneIncreasingDigits(n: number): number { + let strArr: number[] = String(n).split('').map(i => parseInt(i)); + const length = strArr.length; + let flag: number = length; + for (let i = length - 2; i >= 0; i--) { + if (strArr[i] > strArr[i + 1]) { + strArr[i] -= 1; + flag = i + 1; + } + } + for (let i = flag; i < length; i++) { + strArr[i] = 9; + } + return parseInt(strArr.join('')); +}; +``` + + +### Scala + +直接转换为了整数数组: +```scala +object Solution { + import scala.collection.mutable + def monotoneIncreasingDigits(n: Int): Int = { + var digits = mutable.ArrayBuffer[Int]() + // 提取每位数字 + var temp = n // 因为 参数n 是不可变量所以需要赋值给一个可变量 + while (temp != 0) { + digits.append(temp % 10) + temp = temp / 10 + } + // 贪心 + var flag = -1 + for (i <- 0 until (digits.length - 1) if digits(i) < digits(i + 1)) { + flag = i + digits(i + 1) -= 1 + } + for (i <- 0 to flag) digits(i) = 9 + + // 拼接 + var res = 0 + for (i <- 0 until digits.length) { + res += digits(i) * math.pow(10, i).toInt + } + res + } +} +``` -----------------------
diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index 5f53e412..4605de09 100644 --- a/problems/0739.每日温度.md +++ b/problems/0739.每日温度.md @@ -7,7 +7,7 @@ # 739. 每日温度 -[力扣题目链接](https://leetcode-cn.com/problems/daily-temperatures/) +[力扣题目链接](https://leetcode.cn/problems/daily-temperatures/) 请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。 @@ -34,7 +34,8 @@ 那么单调栈的原理是什么呢?为什么时间复杂度是O(n)就可以找到每一个元素的右边第一个比它大的元素位置呢? -单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素的元素,优点是只需要遍历一次。 + +单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是只需要遍历一次。 在使用单调栈的时候首先要明确如下几点: @@ -192,7 +193,7 @@ class Solution { 否则的话,可以直接入栈。 注意,单调栈里 加入的元素是 下标。 */ - Stackstack=new Stack<>(); + Deque stack=new LinkedList<>(); stack.push(0); for(int i=1;istack=new Stack<>(); + Deque stack=new LinkedList<>(); for(int i=0;itemperatures[stack.peek()]){ @@ -233,7 +234,7 @@ class Solution { } ``` Python: -``` Python3 +```python class Solution: def dailyTemperatures(self, temperatures: List[int]) -> List[int]: answer = [0]*len(temperatures) @@ -277,8 +278,36 @@ func dailyTemperatures(t []int) []int { } ``` -> 单调栈法 +> 单调栈法(未精简版本) +```go +func dailyTemperatures(temperatures []int) []int { + res := make([]int, len(temperatures)) + // 初始化栈顶元素为第一个下标索引0 + stack := []int{0} + + for i := 1; i < len(temperatures); i++ { + top := stack[len(stack)-1] + if temperatures[i] < temperatures[top] { + stack = append(stack, i) + } else if temperatures[i] == temperatures[top] { + stack = append(stack, i) + } else { + for len(stack) != 0 && temperatures[i] > temperatures[top] { + res[top] = i - top + stack = stack[:len(stack)-1] + if len(stack) != 0 { + top = stack[len(stack)-1] + } + } + stack = append(stack, i) + } + } + return res +} +``` + +> 单调栈法(精简版本) ```go // 单调递减栈 func dailyTemperatures(num []int) []int { @@ -343,6 +372,32 @@ var dailyTemperatures = function(temperatures) { }; ``` +TypeScript: + +> 精简版: + +```typescript +function dailyTemperatures(temperatures: number[]): number[] { + const length: number = temperatures.length; + const stack: number[] = []; + const resArr: number[] = new Array(length).fill(0); + stack.push(0); + for (let i = 1; i < length; i++) { + let top = stack[stack.length - 1]; + while ( + stack.length > 0 && + temperatures[top] < temperatures[i] + ) { + resArr[top] = i - top; + stack.pop(); + top = stack[stack.length - 1]; + } + stack.push(i); + } + return resArr; +}; +``` + diff --git a/problems/0746.使用最小花费爬楼梯.md b/problems/0746.使用最小花费爬楼梯.md index c356955a..92fb2920 100644 --- a/problems/0746.使用最小花费爬楼梯.md +++ b/problems/0746.使用最小花费爬楼梯.md @@ -6,7 +6,7 @@ # 746. 使用最小花费爬楼梯 -[力扣题目链接](https://leetcode-cn.com/problems/min-cost-climbing-stairs/) +[力扣题目链接](https://leetcode.cn/problems/min-cost-climbing-stairs/) 数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。 @@ -266,7 +266,48 @@ var minCostClimbingStairs = function(cost) { }; ``` +### TypeScript + +```typescript +function minCostClimbingStairs(cost: number[]): number { + /** + dp[i]: 走到第i阶需要花费的最少金钱 + dp[0]: cost[0]; + dp[1]: cost[1]; + ... + dp[i]: min(dp[i - 1], dp[i - 2]) + cost[i]; + */ + const dp: number[] = []; + const length: number = cost.length; + dp[0] = cost[0]; + dp[1] = cost[1]; + for (let i = 2; i <= length; i++) { + dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; + } + return Math.min(dp[length - 1], dp[length - 2]); +}; +``` + +### Rust + +```Rust +use std::cmp::min; +impl Solution { + pub fn min_cost_climbing_stairs(cost: Vec) -> i32 { + let len = cost.len(); + let mut dp = vec![0; len]; + dp[0] = cost[0]; + dp[1] = cost[1]; + for i in 2..len { + dp[i] = min(dp[i-1], dp[i-2]) + cost[i]; + } + min(dp[len-1], dp[len-2]) + } +} +``` + ### C + ```c int minCostClimbingStairs(int* cost, int costSize){ //开辟dp数组,大小为costSize @@ -282,5 +323,35 @@ int minCostClimbingStairs(int* cost, int costSize){ return dp[i-1] < dp[i-2] ? dp[i-1] : dp[i-2]; } ``` + +### Scala + +```scala +object Solution { + def minCostClimbingStairs(cost: Array[Int]): Int = { + var dp = new Array[Int](cost.length) + dp(0) = cost(0) + dp(1) = cost(1) + for (i <- 2 until cost.length) { + dp(i) = math.min(dp(i - 1), dp(i - 2)) + cost(i) + } + math.min(dp(cost.length - 1), dp(cost.length - 2)) + } +} +``` + +第二种思路: dp[i] 表示爬到第i-1层所需的最小花费,状态转移方程为: dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]) +```scala +object Solution { + def minCostClimbingStairs(cost: Array[Int]): Int = { + var dp = new Array[Int](cost.length + 1) + for (i <- 2 until cost.length + 1) { + dp(i) = math.min(dp(i - 1) + cost(i - 1), dp(i - 2) + cost(i - 2)) + } + dp(cost.length) + } +} +``` + -----------------------
diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index d350f255..ecf064d3 100644 --- a/problems/0763.划分字母区间.md +++ b/problems/0763.划分字母区间.md @@ -7,7 +7,7 @@ # 763.划分字母区间 -[力扣题目链接](https://leetcode-cn.com/problems/partition-labels/) +[力扣题目链接](https://leetcode.cn/problems/partition-labels/) 字符串 S 由小写字母组成。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。返回一个表示每个字符串片段的长度的列表。 @@ -158,6 +158,71 @@ class Solution { return list; } } + +class Solution{ + /*解法二: 上述c++补充思路的Java代码实现*/ + + public int[][] findPartitions(String s) { + List temp = new ArrayList<>(); + int[][] hash = new int[26][2];//26个字母2列 表示该字母对应的区间 + + for (int i = 0; i < s.length(); i++) { + //更新字符c对应的位置i + char c = s.charAt(i); + if (hash[c - 'a'][0] == 0) hash[c - 'a'][0] = i; + + hash[c - 'a'][1] = i; + + //第一个元素区别对待一下 + hash[s.charAt(0) - 'a'][0] = 0; + } + + + List> h = new LinkedList<>(); + //组装区间 + for (int i = 0; i < 26; i++) { + //if (hash[i][0] != hash[i][1]) { + temp.clear(); + temp.add(hash[i][0]); + temp.add(hash[i][1]); + //System.out.println(temp); + h.add(new ArrayList<>(temp)); + // } + } + // System.out.println(h); + // System.out.println(h.size()); + int[][] res = new int[h.size()][2]; + for (int i = 0; i < h.size(); i++) { + List list = h.get(i); + res[i][0] = list.get(0); + res[i][1] = list.get(1); + } + + return res; + + } + + public List partitionLabels(String s) { + int[][] partitions = findPartitions(s); + List res = new ArrayList<>(); + Arrays.sort(partitions, (o1, o2) -> Integer.compare(o1[0], o2[0])); + int right = partitions[0][1]; + int left = 0; + for (int i = 0; i < partitions.length; i++) { + if (partitions[i][0] > right) { + //左边界大于右边界即可纪委一次分割 + res.add(right - left + 1); + left = partitions[i][0]; + } + right = Math.max(right, partitions[i][1]); + + } + //最右端 + res.add(right - left + 1); + return res; + + } +} ``` ### Python @@ -229,6 +294,55 @@ var partitionLabels = function(s) { }; ``` +### TypeScript + +```typescript +function partitionLabels(s: string): number[] { + const length: number = s.length; + const resArr: number[] = []; + const helperMap: Map = new Map(); + for (let i = 0; i < length; i++) { + helperMap.set(s[i], i); + } + let left: number = 0; + let right: number = 0; + for (let i = 0; i < length; i++) { + right = Math.max(helperMap.get(s[i])!, right); + if (i === right) { + resArr.push(i - left + 1); + left = i + 1; + } + } + return resArr; +}; +``` + +### Scala + +```scala +object Solution { + import scala.collection.mutable + def partitionLabels(s: String): List[Int] = { + var hash = new Array[Int](26) + for (i <- s.indices) { + hash(s(i) - 'a') = i + } + + var res = mutable.ListBuffer[Int]() + var (left, right) = (0, 0) + for (i <- s.indices) { + right = math.max(hash(s(i) - 'a'), right) + if (i == right) { + res.append(right - left + 1) + left = i + 1 + } + } + + res.toList + } +} +``` + -----------------------
diff --git a/problems/0797.所有可能的路径.md b/problems/0797.所有可能的路径.md new file mode 100644 index 00000000..b990053e --- /dev/null +++ b/problems/0797.所有可能的路径.md @@ -0,0 +1,296 @@ + +看一下 算法4,深搜是怎么讲的 + +# 797.所有可能的路径 + +本题是一道 原汁原味的 深度优先搜索(dfs)模板题,那么用这道题目 来讲解 深搜最合适不过了。 + +接下来给大家详细讲解dfs: + +## dfs 与 bfs 区别 + +先来了解dfs的过程,很多录友可能对dfs(深度优先搜索),bfs(广度优先搜索)分不清。 + +先给大家说一下两者大概的区别: + +* dfs是可一个方向去搜,不到黄河不回头,直到遇到绝境了,搜不下去了,在换方向(换方向的过程就涉及到了回溯)。 +* bfs是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是广度,四面八方的搜索过程。 + +当然以上讲的是,大体可以这么理解,接下来 我们详细讲解dfs,(bfs在用单独一篇文章详细讲解) + +## dfs 搜索过程 + +上面说道dfs是可一个方向搜,不到黄河不回头。 那么我们来举一个例子。 + +如图一,是一个无向图,我们要搜索从节点1到节点6的所有路径。 + +![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707093643.png) + +那么dfs搜索的第一条路径是这样的: (假设第一次延默认方向,就找到了节点6),图二 + +![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707093807.png) + +此时我们找到了节点6,(遇到黄河了,是不是应该回头了),那么应该再去搜索其他方向了。 如图三: + +![图三](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707094011.png) + +路径2撤销了,改变了方向,走路径3(红色线), 接着也找到终点6。 那么撤销路径2,改为路径3,在dfs中其实就是回溯的过程(这一点很重要,很多录友,都不理解dfs代码中回溯是用来干什么的) + +又找到了一条从节点1到节点6的路径,又到黄河了,此时再回头,下图图四中,路径4撤销(回溯的过程),改为路径5。 + +![图四](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707094322.png) + +又找到了一条从节点1到节点6的路径,又到黄河了,此时再回头,下图图五,路径6撤销(回溯的过程),改为路径7,路径8 和 路径7,路径9, 结果发现死路一条,都走到了自己走过的节点。 + +![图五](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707094813.png) + +那么节点2所连接路径和节点3所链接的路径 都走过了,撤销路径只能向上回退,去选择撤销当初节点4的选择,也就是撤销路径5,改为路径10 。 如图图六: + +![图六](https://code-thinking-1253855093.file.myqcloud.com/pics/20220707095232.png) + + +上图演示中,其实我并没有把 所有的 从节点1 到节点6的dfs(深度优先搜索)的过程都画出来,那样太冗余了,但 已经把dfs 关键的地方都涉及到了,关键就两点: + +* 搜索方向,是认准一个方向搜,直到碰壁之后在换方向 +* 换方向是撤销原路径,改为节点链接的下一个路径,回溯的过程。 + + +## 代码框架 + +正式因为dfs搜索可一个方向,并需要回溯,所以用递归的方式来实现是最方便的。 + +很多录友对回溯很陌生,建议先看看码随想录,[回溯算法章节](https://programmercarl.com/回溯算法理论基础.html)。 + +有递归的地方就有回溯,那么回溯在哪里呢? + +就地递归函数的下面,例如如下代码: +``` +void dfs(参数) { + 处理节点 + dfs(图,选择的节点); // 递归 + 回溯,撤销处理结果 +} +``` + +可以看到回溯操作就在递归函数的下面,递归和回溯是相辅相成的。 + +在讲解[二叉树章节](https://programmercarl.com/二叉树理论基础.html)的时候,二叉树的递归法其实就是dfs,而二叉树的迭代法,就是bfs(广度优先搜索) + +所以**dfs,bfs其实是基础搜索算法,也广泛应用与其他数据结构与算法中**。 + +我们在回顾一下[回溯法](https://programmercarl.com/回溯算法理论基础.html)的代码框架: + +``` +void backtracking(参数) { + if (终止条件) { + 存放结果; + return; + } + for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { + 处理节点; + backtracking(路径,选择列表); // 递归 + 回溯,撤销处理结果 + } +} + +``` + +回溯算法,其实就是dfs的过程,这里给出dfs的代码框架: + +``` +void dfs(参数) { + if (终止条件) { + 存放结果; + return; + } + + for (选择:本节点所连接的其他节点) { + 处理节点; + dfs(图,选择的节点); // 递归 + 回溯,撤销处理结果 + } +} + +``` + +可以发现dfs的代码框架和回溯算法的代码框架是差不多的。 + +下面我在用 深搜三部曲,来解读 dfs的代码框架。 + +## 深搜三部曲 + +在 [二叉树递归讲解](https://programmercarl.com/%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%80%92%E5%BD%92%E9%81%8D%E5%8E%86.html)中,给出了递归三部曲。 + +[回溯算法](https://programmercarl.com/回溯算法理论基础.html)讲解中,给出了 回溯三部曲。 + +其实深搜也是一样的,深搜三部曲如下: + +1. 确认递归函数,参数 + +``` +void dfs(参数) +``` + +通常我们递归的时候,我们递归搜索需要了解哪些参数,其实也可以在写递归函数的时候,发现需要什么参数,再去补充就可以。 + +一般情况,深搜需要 二维数组数组结构保存所有路径,需要一维数组保存单一路径,这种保存结果的数组,我们可以定义一个全局遍历,避免让我们的函数参数过多。 + +例如这样: + +``` +vector> result; // 保存符合条件的所有路径 +vector path; // 起点到终点的路径 +void dfs (图,目前搜索的节点) +``` + +但这种写法看个人习惯,不强求。 + +2. 确认终止条件 + +终止条件很重要,很多同学写dfs的时候,之所以容易死循环,栈溢出等等这些问题,都是因为终止条件没有想清楚。 + +``` +if (终止条件) { + 存放结果; + return; +} +``` + +终止添加不仅是结束本层递归,同时也是我们收获结果的时候。 + +3. 处理目前搜索节点出发的路径 + +一般这里就是一个for循环的操作,去遍历 目前搜索节点 所能到的所有节点。 + +``` +for (选择:本节点所连接的其他节点) { + 处理节点; + dfs(图,选择的节点); // 递归 + 回溯,撤销处理结果 +} +``` + +不少录友疑惑的地方,都是 dfs代码框架中for循环里分明已经处理节点了,那么 dfs函数下面 为什么还要撤销的呢。 + +如图七所示, 路径2 已经走到了 目的地节点6,那么 路径2 是如何撤销,然后改为 路径3呢? 其实这就是 回溯的过程,撤销路径2,走换下一个方向。 + +![图七](https://code-thinking-1253855093.file.myqcloud.com/pics/20220708093544.png) + + +## 总结 + +我们讲解了,dfs 和 bfs的大体区别(bfs详细过程下篇来讲),dfs的搜索过程以及代码框架。 + +最后还有 深搜三部曲来解读这份代码框架。 + +以上如果大家都能理解了,其实搜索的代码就很好写,具体题目套用具体场景就可以了。 + +## 797. 所有可能的路径 + +### 思路 + +1. 确认递归函数,参数 + +首先我们dfs函数一定要存一个图,用来遍历的,还要存一个目前我们遍历的节点,定义为x + +至于 单一路径,和路径集合可以放在全局变量,那么代码是这样的: + +```c++ +vector> result; // 收集符合条件的路径 +vector path; // 0节点到终点的路径 +// x:目前遍历的节点 +// graph:存当前的图 +void dfs (vector>& graph, int x) +``` + +2. 确认终止条件 + +什么时候我们就找到一条路径了? + +当目前遍历的节点 为 最后一个节点的时候,就找到了一条,从 出发点到终止点的路径。 + +当前遍历的节点,我们定义为x,最后一点节点,就是 graph.size() - 1。 + +所以 但 x 等于 graph.size() - 1 的时候就找到一条有效路径。 代码如下: + + +```c++ +// 要求从节点 0 到节点 n-1 的路径并输出,所以是 graph.size() - 1 +if (x == graph.size() - 1) { // 找到符合条件的一条路径 + result.push_back(path); // 收集有效路径 + return; +} +``` + +3. 处理目前搜索节点出发的路径 + +接下来是走 当前遍历节点x的下一个节点。 + +首先是要找到 x节点链接了哪些节点呢? 遍历方式是这样的: + +```c++ +for (int i = 0; i < graph[x].size(); i++) { // 遍历节点n链接的所有节点 +``` + +接下来就是将 选中的x所连接的节点,加入到 单一路劲来。 + +```C++ +path.push_back(graph[x][i]); // 遍历到的节点加入到路径中来 + +``` + +当前遍历的节点就是 `graph[x][i]` 了,所以进入下一层递归 + +```C++ +dfs(graph, graph[x][i]); // 进入下一层递归 +``` + +最后就是回溯的过程,撤销本次添加节点的操作。 该过程整体代码: + +```C++ +for (int i = 0; i < graph[x].size(); i++) { // 遍历节点n链接的所有节点 + path.push_back(graph[x][i]); // 遍历到的节点加入到路径中来 + dfs(graph, graph[x][i]); // 进入下一层递归 + path.pop_back(); // 回溯,撤销本节点 +} +``` + + +### 本题代码 + +```c++ +class Solution { +private: + vector> result; // 收集符合条件的路径 + vector path; // 0节点到终点的路径 + // x:目前遍历的节点 + // graph:存当前的图 + void dfs (vector>& graph, int x) { + // 要求从节点 0 到节点 n-1 的路径并输出,所以是 graph.size() - 1 + if (x == graph.size() - 1) { // 找到符合条件的一条路径 + result.push_back(path); + return; + } + for (int i = 0; i < graph[x].size(); i++) { // 遍历节点n链接的所有节点 + path.push_back(graph[x][i]); // 遍历到的节点加入到路径中来 + dfs(graph, graph[x][i]); // 进入下一层递归 + path.pop_back(); // 回溯,撤销本节点 + } + } +public: + vector> allPathsSourceTarget(vector>& graph) { + path.push_back(0); // 无论什么路径已经是从0节点出发 + dfs(graph, 0); // 开始遍历 + return result; + } +}; + +``` + +## 其他语言版本 + +### Java + +### Python + +### Go diff --git a/problems/0841.钥匙和房间.md b/problems/0841.钥匙和房间.md index 1cd13065..58765a8f 100644 --- a/problems/0841.钥匙和房间.md +++ b/problems/0841.钥匙和房间.md @@ -8,7 +8,7 @@ # 841.钥匙和房间 -[力扣题目链接](https://leetcode-cn.com/problems/keys-and-rooms/) +[力扣题目链接](https://leetcode.cn/problems/keys-and-rooms/) 有 N 个房间,开始时你位于 0 号房间。每个房间有不同的号码:0,1,2,...,N-1,并且房间里可能有一些钥匙能使你进入下一个房间。 @@ -31,21 +31,182 @@ * 解释:我们不能进入 2 号房间。 -## 思 +## 思路 -其实这道题的本质就是判断各个房间所连成的有向图,说明不用访问所有的房间。 +本题其实给我们是一个有向图, 意识到这是有向图很重要! -如图所示: +图中给我的两个示例: `[[1],[2],[3],[]]` `[[1,3],[3,0,1],[2],[0]]`,画成对应的图如下: - +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220714101414.png) -示例1就可以访问所有的房间,因为通过房间里的key将房间连在了一起。 +我们可以看出图1的所有节点都是链接的,而图二中,节点2 是孤立的。 -示例2中,就不能访问所有房间,从图中就可以看出,房间2是一个孤岛,我们从0出发,无论怎么遍历,都访问不到房间2。 +这就很容易让我们想起岛屿问题,只要发现独立的岛,就是不能进入所有房间。 -认清本质问题之后,**使用 广度优先搜索(BFS) 还是 深度优先搜索(DFS) 都是可以的。** +此时也容易想到用并查集的方式去解决。 -BFS C++代码代码如下: +**但本题是有向图**,在有向图中,即使所有节点都是链接的,但依然不可能从0出发遍历所有边。 +给大家举一个例子: + +图3:[[5], [], [1, 3], [5]] ,如图: + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220714102201.png) + +在图3中,大家可以发现,节点0只能到节点5,然后就哪也去不了了。 + +所以本题是一个有向图搜索全路径的问题。 只能用深搜(BFS)或者广搜(DFS)来搜。 + +关于DFS的理论,如果大家有困惑,可以先看我这篇题解: [DFS理论基础](https://leetcode.cn/problems/all-paths-from-source-to-target/solution/by-carlsun-2-66pf) + +**以下dfs分析 大家一定要仔细看,本题有两种dfs的解法,很多题解没有讲清楚**。 看完之后 相信你对dfs会有更深的理解。 + +深搜三部曲: + +1. 确认递归函数,参数 + +需要传入二维数组rooms来遍历地图,需要知道当前我们拿到的key,以至于去下一个房间。 + +同时还需要一个数组,用来记录我们都走过了哪些房间,这样好知道最后有没有把所有房间都遍历的,可以定义一个一维数组。 + +所以 递归函数参数如下: + +```C++ +// key 当前得到的可以 +// visited 记录访问过的房间 +void dfs(const vector>& rooms, int key, vector& visited) { +``` + +2. 确认终止条件 + +遍历的时候,什么时候终止呢? + +这里有一个很重要的逻辑,就是在递归中,**我们是处理当前访问的节点,还是处理下一个要访问的节点**。 + +这决定 终止条件怎么写。 + +首先明确,本题中什么叫做处理,就是 visited数组来记录访问过的节点,那么把该节点默认 数组里元素都是false,把元素标记为true就是处理 本节点了。 + +如果我们是处理当前访问的节点,当前访问的节点如果是 true ,说明是访问过的节点,那就终止本层递归,如果不是true,我们就把它赋值为true,因为我们处理本层递归的节点。 + +代码就是这样: + +```C++ +// 写法一:处理当前访问的节点 +void dfs(const vector>& rooms, int key, vector& visited) { + if (visited[key]) { // 本层递归是true,说明访问过,立刻返回 + return; + } + visited[key] = true; // 给当前遍历的节点赋值true + vector keys = rooms[key]; + for (int key : keys) { + // 深度优先搜索遍历 + dfs(rooms, key, visited); + } +} +``` + +如果我们是处理下一层访问的节点,而不是当前层。那么就要在 深搜三部曲中第三步:处理目前搜索节点出发的路径 的时候对 节点进行处理。 + +这样的话,就不需要终止条件,而是在 搜索下一个节点的时候,直接判断 下一个节点是否是我们要搜的节点。 + +代码就是这样的: + +```C++ +// 写法二:处理下一个要访问的节点 +void dfs(const vector>& rooms, int key, vector& visited) { + // 这里 没有终止条件,而是在 处理下一层节点的时候来判断 + vector keys = rooms[key]; + for (int key : keys) { + if (visited[key] == false) { // 处理下一层节点,判断是否要进行递归 + visited[key] = true; + dfs(rooms, key, visited); + } + } +} +``` + +可以看出,如果看待 我们要访问的节点,直接决定了两种不一样的写法,很多同学对这一块很模糊,其实做过这道题,也没有思考到这个维度上。 + + +3. 处理目前搜索节点出发的路径 + +其实在上面,深搜三部曲 第二部,就已经讲了,因为终止条件的两种写法, 直接决定了两种不一样的递归写法。 + +这里还有细节: + +看上面两个版本的写法中, 好像没有发现回溯的逻辑。 + +我们都知道,有递归就有回溯,回溯就在递归函数的下面, 那么之前我们做的dfs题目,都需要回溯操作,例如:[797.所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/solution/by-carlsun-2-66pf/), **为什么本题就没有回溯呢?** + +代码中可以看到dfs函数下面并没有回溯的操作。 + +此时就要在思考本题的要求了,本题是需要判断 0节点是否能到所有节点,那么我们就没有必要回溯去撤销操作了,只要遍历过的节点一律都标记上。 + +**那什么时候需要回溯操作呢?** + +当我们需要搜索一条可行路径的时候,就需要回溯操作了,因为没有回溯,就没法“调头”, 如果不理解的话,去看我写的 [797.所有可能的路径](https://leetcode.cn/problems/all-paths-from-source-to-target/solution/by-carlsun-2-66pf/) 的题解。 + + +以上分析完毕,DFS整体实现C++代码如下: + +```CPP +// 写法一:处理当前访问的节点 +class Solution { +private: + void dfs(const vector>& rooms, int key, vector& visited) { + if (visited[key]) { + return; + } + visited[key] = true; + vector keys = rooms[key]; + for (int key : keys) { + // 深度优先搜索遍历 + dfs(rooms, key, visited); + } + } +public: + bool canVisitAllRooms(vector>& rooms) { + vector visited(rooms.size(), false); + dfs(rooms, 0, visited); + //检查是否都访问到了 + for (int i : visited) { + if (i == false) return false; + } + return true; + } +}; + +``` + +```c++ +写法二:处理下一个要访问的节点 +class Solution { +private: + void dfs(const vector>& rooms, int key, vector& visited) { + vector keys = rooms[key]; + for (int key : keys) { + if (visited[key] == false) { + visited[key] = true; + dfs(rooms, key, visited); + } + } + } +public: + bool canVisitAllRooms(vector>& rooms) { + vector visited(rooms.size(), false); + visited[0] = true; // 0 节点是出发节点,一定被访问过 + dfs(rooms, 0, visited); + //检查是否都访问到了 + for (int i : visited) { + if (i == false) return false; + } + return true; + } +}; + +``` + +本题我也给出 BFS C++代码,至于BFS,我后面会有单独文章来讲,代码如下: ```CPP class Solution { @@ -80,39 +241,11 @@ public: }; ``` -DFS C++代码如下: - -```CPP -class Solution { -private: - void dfs(int key, const vector>& rooms, vector& visited) { - if (visited[key]) { - return; - } - visited[key] = 1; - vector keys = rooms[key]; - for (int key : keys) { - // 深度优先搜索遍历 - dfs(key, rooms, visited); - } - } -public: - bool canVisitAllRooms(vector>& rooms) { - vector visited(rooms.size(), 0); - dfs(0, rooms, visited); - //检查是否都访问到了 - for (int i : visited) { - if (i == 0) return false; - } - return true; - } -}; -``` -# 其他语言版本 +## 其他语言版本 -Java: +### Java ```java class Solution { @@ -120,24 +253,19 @@ class Solution { if (visited.get(key)) { return; } - visited.set(key, true); for (int k : rooms.get(key)) { // 深度优先搜索遍历 dfs(k, rooms, visited); } } - - public boolean canVisitAllRooms(List> rooms) { List visited = new ArrayList(){{ for(int i = 0 ; i < rooms.size(); i++){ add(false); } }}; - dfs(0, rooms, visited); - //检查是否都访问到了 for (boolean flag : visited) { if (!flag) { @@ -149,20 +277,14 @@ class Solution { } ``` - - - - -python3 +### python3 ```python class Solution: - def dfs(self, key: int, rooms: List[List[int]] , visited : List[bool] ) : if visited[key] : return - visited[key] = True keys = rooms[key] for i in range(len(keys)) : @@ -183,7 +305,7 @@ class Solution: ``` -Go: +### Go ```go @@ -201,11 +323,8 @@ func dfs(key int, rooms [][]int, visited []bool ) { } func canVisitAllRooms(rooms [][]int) bool { - visited := make([]bool, len(rooms)); - dfs(0, rooms, visited); - //检查是否都访问到了 for i := 0; i < len(visited); i++ { if !visited[i] { @@ -216,7 +335,7 @@ func canVisitAllRooms(rooms [][]int) bool { } ``` -JavaScript: +### JavaScript ```javascript //DFS var canVisitAllRooms = function(rooms) { diff --git a/problems/0844.比较含退格的字符串.md b/problems/0844.比较含退格的字符串.md index dccc5404..f7823e59 100644 --- a/problems/0844.比较含退格的字符串.md +++ b/problems/0844.比较含退格的字符串.md @@ -7,7 +7,7 @@ # 844.比较含退格的字符串 -[力扣题目链接](https://leetcode-cn.com/problems/backspace-string-compare/) +[力扣题目链接](https://leetcode.cn/problems/backspace-string-compare/) 给定 S 和 T 两个字符串,当它们分别被输入到空白的文本编辑器后,判断二者是否相等,并返回结果。 # 代表退格字符。 @@ -399,6 +399,71 @@ var backspaceCompare = function(s, t) { ``` +### TypeScript + +> 双栈法: + +```typescript +function backspaceCompare(s: string, t: string): boolean { + const stack1: string[] = [], + stack2: string[] = []; + for (let c of s) { + if (c === '#') { + stack1.pop(); + } else { + stack1.push(c); + } + } + for (let c of t) { + if (c === '#') { + stack2.pop(); + } else { + stack2.push(c); + } + } + if (stack1.length !== stack2.length) return false; + for (let i = 0, length = stack1.length; i < length; i++) { + if (stack1[i] !== stack2[i]) return false; + } + return true; +}; +``` + +> 双指针法: + +```typescript +function backspaceCompare(s: string, t: string): boolean { + let sIndex: number = s.length - 1, + tIndex: number = t.length - 1; + while (true) { + sIndex = getIndexAfterDel(s, sIndex); + tIndex = getIndexAfterDel(t, tIndex); + if (sIndex < 0 || tIndex < 0) break; + if (s[sIndex] !== t[tIndex]) return false; + sIndex--; + tIndex--; + } + return sIndex === -1 && tIndex === -1; +}; +function getIndexAfterDel(s: string, startIndex: number): number { + let backspaceNum: number = 0; + while (startIndex >= 0) { + // 不可消除 + if (s[startIndex] !== '#' && backspaceNum === 0) break; + // 可消除 + if (s[startIndex] === '#') { + backspaceNum++; + } else { + backspaceNum--; + } + startIndex--; + } + return startIndex; +} +``` + + + -----------------------
diff --git a/problems/0860.柠檬水找零.md b/problems/0860.柠檬水找零.md index 5d5d6ad2..42e8b19a 100644 --- a/problems/0860.柠檬水找零.md +++ b/problems/0860.柠檬水找零.md @@ -7,7 +7,7 @@ # 860.柠檬水找零 -[力扣题目链接](https://leetcode-cn.com/problems/lemonade-change/) +[力扣题目链接](https://leetcode.cn/problems/lemonade-change/) 在柠檬水摊上,每一杯柠檬水的售价为 5 美元。 @@ -250,6 +250,49 @@ var lemonadeChange = function(bills) { return true }; +``` +### C +```c +bool lemonadeChange(int* bills, int billsSize){ + // 分别记录五元、十元的数量(二十元不用记录,因为不会用到20元找零) + int fiveCount = 0; int tenCount = 0; + + int i; + for(i = 0; i < billsSize; ++i) { + // 分情况讨论每位顾客的付款 + switch(bills[i]) { + // 情况一:直接收款五元 + case 5: + fiveCount++; + break; + // 情况二:收款十元 + case 10: + // 若没有五元找零,返回false + if(fiveCount == 0) + return false; + // 收款十元并找零五元 + fiveCount--; + tenCount++; + break; + // 情况三:收款二十元 + case 20: + // 若可以,优先用十元和五元找零(因为十元只能找零20,所以需要尽量用掉。而5元能找零十元和二十元) + if(fiveCount > 0 && tenCount > 0) { + fiveCount--; + tenCount--; + } + // 若没有十元,但是有三张五元。用三张五元找零 + else if(fiveCount >= 3) + fiveCount-=3; + // 无法找开,返回false + else + return false; + break; + } + } + // 全部可以找开,返回true + return true; +} ``` ### TypeScript @@ -285,6 +328,37 @@ function lemonadeChange(bills: number[]): boolean { ``` +### Scala + +```scala +object Solution { + def lemonadeChange(bills: Array[Int]): Boolean = { + var fiveNum = 0 + var tenNum = 0 + + for (i <- bills) { + if (i == 5) fiveNum += 1 + if (i == 10) { + if (fiveNum <= 0) return false + tenNum += 1 + fiveNum -= 1 + } + if (i == 20) { + if (fiveNum > 0 && tenNum > 0) { + tenNum -= 1 + fiveNum -= 1 + } else if (fiveNum >= 3) { + fiveNum -= 3 + } else { + return false + } + } + } + true + } +} +``` + -----------------------
diff --git a/problems/0922.按奇偶排序数组II.md b/problems/0922.按奇偶排序数组II.md index cb564fb6..49547a15 100644 --- a/problems/0922.按奇偶排序数组II.md +++ b/problems/0922.按奇偶排序数组II.md @@ -9,7 +9,7 @@ # 922. 按奇偶排序数组II -[力扣题目链接](https://leetcode-cn.com/problems/sort-array-by-parity-ii/) +[力扣题目链接](https://leetcode.cn/problems/sort-array-by-parity-ii/) 给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。 @@ -260,6 +260,75 @@ var sortArrayByParityII = function(nums) { }; ``` +### TypeScript + +> 方法一: + +```typescript +function sortArrayByParityII(nums: number[]): number[] { + const evenArr: number[] = [], + oddArr: number[] = []; + for (let num of nums) { + if (num % 2 === 0) { + evenArr.push(num); + } else { + oddArr.push(num); + } + } + const resArr: number[] = []; + for (let i = 0, length = nums.length / 2; i < length; i++) { + resArr.push(evenArr[i]); + resArr.push(oddArr[i]); + } + return resArr; +}; +``` + +> 方法二: + +```typescript +function sortArrayByParityII(nums: number[]): number[] { + const length: number = nums.length; + const resArr: number[] = []; + let evenIndex: number = 0, + oddIndex: number = 1; + for (let i = 0; i < length; i++) { + if (nums[i] % 2 === 0) { + resArr[evenIndex] = nums[i]; + evenIndex += 2; + } else { + resArr[oddIndex] = nums[i]; + oddIndex += 2; + } + } + return resArr; +}; +``` + +> 方法三: + +```typescript +function sortArrayByParityII(nums: number[]): number[] { + const length: number = nums.length; + let oddIndex: number = 1; + for (let evenIndex = 0; evenIndex < length; evenIndex += 2) { + if (nums[evenIndex] % 2 === 1) { + // 在偶数位遇到了奇数 + while (oddIndex < length && nums[oddIndex] % 2 === 1) { + oddIndex += 2; + } + // 在奇数位遇到了偶数,交换 + let temp = nums[evenIndex]; + nums[evenIndex] = nums[oddIndex]; + nums[oddIndex] = temp; + } + } + return nums; +}; +``` + + + -----------------------
diff --git a/problems/0925.长按键入.md b/problems/0925.长按键入.md index 0ef5a3d7..3924c181 100644 --- a/problems/0925.长按键入.md +++ b/problems/0925.长按键入.md @@ -6,7 +6,7 @@ # 925.长按键入 -[力扣题目链接](https://leetcode-cn.com/problems/long-pressed-name/) +[力扣题目链接](https://leetcode.cn/problems/long-pressed-name/) 你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。 @@ -129,29 +129,21 @@ class Solution { ``` ### Python ```python -class Solution: - def isLongPressedName(self, name: str, typed: str) -> bool: - i, j = 0, 0 - m, n = len(name) , len(typed) - while i< m and j < n: - if name[i] == typed[j]: # 相同时向后匹配 - i += 1 - j += 1 - else: # 不相同 - if j == 0: return False # 如果第一位不相同,直接返回false - # 判断边界为n-1,若为n会越界,例如name:"kikcxmvzi" typed:"kiikcxxmmvvzzz" - while j < n - 1 and typed[j] == typed[j-1]: j += 1 - if name[i] == typed[j]: - i += 1 - j += 1 - else: return False - # 说明name没有匹配完 - if i < m: return False - # 说明type没有匹配完 - while j < n: - if typed[j] == typed[j-1]: j += 1 - else: return False - return True + i = j = 0 + while(i diff --git a/problems/0941.有效的山脉数组.md b/problems/0941.有效的山脉数组.md index 4b7a978c..fb7935a8 100644 --- a/problems/0941.有效的山脉数组.md +++ b/problems/0941.有效的山脉数组.md @@ -7,7 +7,7 @@ # 941.有效的山脉数组 -[力扣题目链接](https://leetcode-cn.com/problems/valid-mountain-array/) +[力扣题目链接](https://leetcode.cn/problems/valid-mountain-array/) 给定一个整数数组 arr,如果它是有效的山脉数组就返回 true,否则返回 false。 @@ -157,7 +157,44 @@ var validMountainArray = function(arr) { }; ``` +## TypeScript +```typescript +function validMountainArray(arr: number[]): boolean { + const length: number = arr.length; + if (length < 3) return false; + let left: number = 0, + right: number = length - 1; + while (left < (length - 1) && arr[left] < arr[left + 1]) { + left++; + } + while (right > 0 && arr[right] < arr[right - 1]) { + right--; + } + if (left === right && left !== 0 && right !== length - 1) + return true; + return false; +}; +``` + +## C# + +```csharp +public class Solution { + public bool ValidMountainArray(int[] arr) { + if (arr.Length < 3) return false; + + int left = 0; + int right = arr.Length - 1; + + while (left + 1< arr.Length && arr[left] < arr[left + 1]) left ++; + while (right > 0 && arr[right] < arr[right - 1]) right --; + if (left == right && left != 0 && right != arr.Length - 1) return true; + + return false; + } +} +``` ----------------------- diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index 35c3ccdc..b17ff080 100644 --- a/problems/0968.监控二叉树.md +++ b/problems/0968.监控二叉树.md @@ -7,7 +7,7 @@ # 968.监控二叉树 -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-cameras/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-cameras/) 给定一个二叉树,我们在树的节点上安装摄像头。 @@ -476,7 +476,35 @@ var minCameraCover = function(root) { }; ``` +### TypeScript + +```typescript +function minCameraCover(root: TreeNode | null): number { + /** 0-无覆盖, 1-有摄像头, 2-有覆盖 */ + type statusCode = 0 | 1 | 2; + let resCount: number = 0; + if (recur(root) === 0) resCount++; + return resCount; + function recur(node: TreeNode | null): statusCode { + if (node === null) return 2; + const left: statusCode = recur(node.left), + right: statusCode = recur(node.right); + let resStatus: statusCode = 0; + if (left === 0 || right === 0) { + resStatus = 1; + resCount++; + } else if (left === 1 || right === 1) { + resStatus = 2; + } else { + resStatus = 0; + } + return resStatus; + } +}; +``` + ### C + ```c /* **函数后序遍历二叉树。判断一个结点状态时,根据其左右孩子结点的状态进行判断 @@ -516,5 +544,40 @@ int minCameraCover(struct TreeNode* root){ } ``` +### Scala + +```scala +object Solution { + def minCameraCover(root: TreeNode): Int = { + var result = 0 + def traversal(cur: TreeNode): Int = { + // 空节点,该节点有覆盖 + if (cur == null) return 2 + var left = traversal(cur.left) + var right = traversal(cur.right) + // 情况1,左右节点都有覆盖 + if (left == 2 && right == 2) { + return 0 + } + // 情况2 + if (left == 0 || right == 0) { + result += 1 + return 1 + } + // 情况3 + if (left == 1 || right == 1) { + return 2 + } + -1 + } + + if (traversal(root) == 0) { + result += 1 + } + result + } +} +``` + -----------------------
diff --git a/problems/0977.有序数组的平方.md b/problems/0977.有序数组的平方.md index 24276bcf..7bc84e85 100644 --- a/problems/0977.有序数组的平方.md +++ b/problems/0977.有序数组的平方.md @@ -8,7 +8,7 @@ # 977.有序数组的平方 -[力扣题目链接](https://leetcode-cn.com/problems/squares-of-a-sorted-array/) +[力扣题目链接](https://leetcode.cn/problems/squares-of-a-sorted-array/) 给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 @@ -23,6 +23,8 @@ # 思路 +为了易于大家理解,我还特意录制了视频,[本题视频讲解](https://www.bilibili.com/video/BV1QB4y1D7ep) + ## 暴力排序 最直观的想法,莫过于:每个数平方之后,排个序,美滋滋,代码如下: @@ -39,6 +41,15 @@ public: } }; ``` +```python3 +class Solution: + def sortedSquares(self, nums: List[int]) -> List[int]: + res=[] + for num in nums: + res.append(num**2) + return sorted(res) +``` + 这个时间复杂度是 O(n + nlogn), 可以说是O(nlogn)的时间复杂度,但为了和下面双指针法算法时间复杂度有鲜明对比,我记为 O(n + nlog n)。 @@ -104,6 +115,7 @@ class Solution { 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 { @@ -359,7 +371,111 @@ class Solution { } ``` +Kotlin: +双指针法 +```kotlin +class Solution { + // 双指针法 + fun sortedSquares(nums: IntArray): IntArray { + var res = IntArray(nums.size) + var left = 0 // 指向数组的最左端 + var right = nums.size - 1 // 指向数组端最右端 + // 选择平方数更大的那一个往 res 数组中倒序填充 + for (index in nums.size - 1 downTo 0) { + if (nums[left] * nums[left] > nums[right] * nums[right]) { + res[index] = nums[left] * nums[left] + left++ + } else { + res[index] = nums[right] * nums[right] + right-- + } + } + return res + } +} +``` +骚操作(暴力思路) +```kotlin +class Solution { + fun sortedSquares(nums: IntArray): IntArray { + // left 与 right 用来控制循环,类似于滑动窗口 + var left: Int = 0; + var right: Int = nums.size - 1; + // 将每个数字的平方经过排序后加入result数值 + var result: IntArray = IntArray(nums.size); + var k: Int = nums.size - 1; + while (left <= right) { + // 从大到小,从后向前填满数组 + // [left, right] 控制循环 + if (nums[left] * nums[left] > nums[right] * nums[right]) { + result[k--] = nums[left] * nums[left] + left++ + } + else { + result[k--] = nums[right] * nums[right] + right-- + } + } + return result + } +} +``` +Scala: + +双指针: +```scala +object Solution { + def sortedSquares(nums: Array[Int]): Array[Int] = { + val res: Array[Int] = new Array[Int](nums.length) + var top = nums.length - 1 + var i = 0 + var j = nums.length - 1 + while (i <= j) { + if (nums(i) * nums(i) <= nums(j) * nums(j)) { + // 当左侧平方小于等于右侧,res数组顶部放右侧的平方,并且top下移,j左移 + res(top) = nums(j) * nums(j) + top -= 1 + j -= 1 + } else { + // 当左侧平方大于右侧,res数组顶部放左侧的平方,并且top下移,i右移 + res(top) = nums(i) * nums(i) + top -= 1 + i += 1 + } + } + res + } +} +``` +骚操作(暴力思路): +```scala +object Solution { + def sortedSquares(nums: Array[Int]): Array[Int] = { + nums.map(x=>{x*x}).sortWith(_ < _) + } +} +``` + +C#: +```csharp +public class Solution { + public int[] SortedSquares(int[] nums) { + int k = nums.Length - 1; + int[] result = new int[nums.Length]; + for (int i = 0, j = nums.Length - 1;i <= j;){ + if (nums[i] * nums[i] < nums[j] * nums[j]) { + result[k--] = nums[j] * nums[j]; + j--; + } else { + result[k--] = nums[i] * nums[i]; + i++; + } + } + return result; + } +} +``` -----------------------
diff --git a/problems/1002.查找常用字符.md b/problems/1002.查找常用字符.md index 36937b0b..6f54c098 100644 --- a/problems/1002.查找常用字符.md +++ b/problems/1002.查找常用字符.md @@ -8,7 +8,7 @@ # 1002. 查找常用字符 -[力扣题目链接](https://leetcode-cn.com/problems/find-common-characters/) +[力扣题目链接](https://leetcode.cn/problems/find-common-characters/) 给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。 @@ -418,6 +418,38 @@ char ** commonChars(char ** words, int wordsSize, int* returnSize){ return ret; } ``` - +Scala: +```scala +object Solution { + def commonChars(words: Array[String]): List[String] = { + // 声明返回结果的不可变List集合,因为res要重新赋值,所以声明为var + var res = List[String]() + var hash = new Array[Int](26) // 统计字符出现的最小频率 + // 统计第一个字符串中字符出现的次数 + for (i <- 0 until words(0).length) { + hash(words(0)(i) - 'a') += 1 + } + // 统计其他字符串出现的频率 + for (i <- 1 until words.length) { + // 统计其他字符出现的频率 + var hashOtherStr = new Array[Int](26) + for (j <- 0 until words(i).length) { + hashOtherStr(words(i)(j) - 'a') += 1 + } + // 更新hash,取26个字母最小出现的频率 + for (k <- 0 until 26) { + hash(k) = math.min(hash(k), hashOtherStr(k)) + } + } + // 根据hash的结果转换输出的形式 + for (i <- 0 until 26) { + for (j <- 0 until hash(i)) { + res = res :+ (i + 'a').toChar.toString + } + } + res + } +} +``` -----------------------
diff --git a/problems/1005.K次取反后最大化的数组和.md b/problems/1005.K次取反后最大化的数组和.md index 79767deb..8d721b9f 100644 --- a/problems/1005.K次取反后最大化的数组和.md +++ b/problems/1005.K次取反后最大化的数组和.md @@ -7,7 +7,7 @@ # 1005.K次取反后最大化的数组和 -[力扣题目链接](https://leetcode-cn.com/problems/maximize-sum-of-array-after-k-negations/) +[力扣题目链接](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/) 给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。) @@ -209,6 +209,22 @@ var largestSumAfterKNegations = function(nums, k) { return a + b }) }; + +// 版本二 (优化: 一次遍历) +var largestSumAfterKNegations = function(nums, k) { + nums.sort((a, b) => Math.abs(b) - Math.abs(a)); // 排序 + let sum = 0; + for(let i = 0; i < nums.length; i++) { + if(nums[i] < 0 && k-- > 0) { // 负数取反(k 数量足够时) + nums[i] = -nums[i]; + } + sum += nums[i]; // 求和 + } + if(k % 2 > 0) { // k 有多余的(k若消耗完则应为 -1) + sum -= 2 * nums[nums.length - 1]; // 减去两倍的最小值(因为之前加过一次) + } + return sum; +}; ``` @@ -273,6 +289,28 @@ function largestSumAfterKNegations(nums: number[], k: number): number { }; ``` +### Scala + +```scala +object Solution { + def largestSumAfterKNegations(nums: Array[Int], k: Int): Int = { + var num = nums.sortWith(math.abs(_) > math.abs(_)) + + var kk = k // 因为k是不可变量,所以要赋值给一个可变量 + for (i <- num.indices) { + if (num(i) < 0 && kk > 0) { + num(i) *= -1 // 取反 + kk -= 1 + } + } + + // kk对2取余,结果为0则为偶数不需要取反,结果为1为奇数,只需要对最后的数字进行反转就可以 + if (kk % 2 == 1) num(num.size - 1) *= -1 + + num.sum // 最后返回数字的和 + } +} +``` diff --git a/problems/1035.不相交的线.md b/problems/1035.不相交的线.md index 0602e111..83ccb08c 100644 --- a/problems/1035.不相交的线.md +++ b/problems/1035.不相交的线.md @@ -6,7 +6,7 @@ ## 1035.不相交的线 -[力扣题目链接](https://leetcode-cn.com/problems/uncrossed-lines/) +[力扣题目链接](https://leetcode.cn/problems/uncrossed-lines/) 我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。 @@ -111,7 +111,6 @@ class Solution: Golang: ```go - func maxUncrossedLines(A []int, B []int) int { m, n := len(A), len(B) dp := make([][]int, m+1) @@ -140,7 +139,26 @@ func max(a, b int) int { } ``` +Rust: +```rust +pub fn max_uncrossed_lines(nums1: Vec, nums2: Vec) -> i32 { + let (n, m) = (nums1.len(), nums2.len()); + let mut last = vec![0; m + 1]; // 记录滚动数组 + let mut dp = vec![0; m + 1]; + for i in 1..=n { + dp.swap_with_slice(&mut last); + for j in 1..=m { + if nums1[i - 1] == nums2[j - 1] { + dp[j] = last[j - 1] + 1; + } else { + dp[j] = last[j].max(dp[j - 1]); + } + } + } + dp[m] +} +``` JavaScript: @@ -165,6 +183,30 @@ const maxUncrossedLines = (nums1, nums2) => { }; ``` +TypeScript: + +```typescript +function maxUncrossedLines(nums1: number[], nums2: number[]): number { + /** + dp[i][j]: nums1前i-1个,nums2前j-1个,最大连线数 + */ + const length1: number = nums1.length, + length2: number = nums2.length; + const dp: number[][] = new Array(length1 + 1).fill(0) + .map(_ => new Array(length2 + 1).fill(0)); + for (let i = 1; i <= length1; i++) { + for (let j = 1; j <= length2; j++) { + if (nums1[i - 1] === nums2[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[length1][length2]; +}; +``` + diff --git a/problems/1047.删除字符串中的所有相邻重复项.md b/problems/1047.删除字符串中的所有相邻重复项.md index 9237acdb..2c51bbb3 100644 --- a/problems/1047.删除字符串中的所有相邻重复项.md +++ b/problems/1047.删除字符串中的所有相邻重复项.md @@ -11,7 +11,7 @@ # 1047. 删除字符串中的所有相邻重复项 -[力扣题目链接](https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/) +[力扣题目链接](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/) 给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 @@ -375,6 +375,7 @@ func removeDuplicates(_ s: String) -> String { } ``` + C#: ```csharp public string RemoveDuplicates(string s) { @@ -392,5 +393,56 @@ public string RemoveDuplicates(string s) { return res.ToString(); } ``` + + +PHP: +```php +class Solution { + function removeDuplicates($s) { + $stack = new SplStack(); + for($i=0;$iisEmpty() || $s[$i] != $stack->top()){ + $stack->push($s[$i]); + }else{ + $stack->pop(); + } + } + + $result = ""; + while(!$stack->isEmpty()){ + $result.= $stack->top(); + $stack->pop(); + } + + // 此时字符串需要反转一下 + return strrev($result); + } +} +``` + + +Scala: +```scala +object Solution { + import scala.collection.mutable + def removeDuplicates(s: String): String = { + var stack = mutable.Stack[Int]() + var str = "" // 保存最终结果 + for (i <- s.indices) { + var tmp = s(i) + // 如果栈非空并且栈顶元素等于当前字符,那么删掉栈顶和字符串最后一个元素 + if (!stack.isEmpty && tmp == stack.head) { + str = str.take(str.length - 1) + stack.pop() + } else { + stack.push(tmp) + str += tmp + } + } + str + } +} +``` + -----------------------
diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md index ee0ddef2..b6a674aa 100644 --- a/problems/1049.最后一块石头的重量II.md +++ b/problems/1049.最后一块石头的重量II.md @@ -3,11 +3,10 @@

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

-# 动态规划:最后一块石头的重量 II ## 1049. 最后一块石头的重量 II -[力扣题目链接](https://leetcode-cn.com/problems/last-stone-weight-ii/) +[力扣题目链接](https://leetcode.cn/problems/last-stone-weight-ii/) 题目难度:中等 @@ -152,7 +151,7 @@ public: ## 其他语言版本 -Java: +### Java: 一维数组版本 ```Java @@ -212,7 +211,7 @@ class Solution { ``` -Python: +### Python: ```python class Solution: def lastStoneWeightII(self, stones: List[int]) -> int: @@ -225,7 +224,7 @@ class Solution: return sumweight - 2 * dp[target] ``` -Go: +### Go: ```go func lastStoneWeightII(stones []int) int { // 15001 = 30 * 1000 /2 +1 @@ -254,7 +253,7 @@ func max(a, b int) int { } ``` -JavaScript版本 +### JavaScript ```javascript /** @@ -277,5 +276,99 @@ var lastStoneWeightII = function (stones) { }; ``` +### C +```c +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +int getSum(int *stones, int stoneSize) { + int sum = 0, i; + for (i = 0; i < stoneSize; ++i) + sum += stones[i]; + return sum; +} + +int lastStoneWeightII(int* stones, int stonesSize){ + int sum = getSum(stones, stonesSize); + int target = sum / 2; + int i, j; + + // 初始化dp数组 + int *dp = (int*)malloc(sizeof(int) * (target + 1)); + memset(dp, 0, sizeof(int) * (target + 1)); + for (j = stones[0]; j <= target; ++j) + dp[j] = stones[0]; + + // 递推公式:dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]) + for (i = 1; i < stonesSize; ++i) { + for (j = target; j >= stones[i]; --j) + dp[j] = MAX(dp[j], dp[j - stones[i]] + stones[i]); + } + return sum - dp[target] - dp[target]; +} +``` + +### TypeScript: + +```ts +function lastStoneWeightII(stones: number[]): number { + const sum: number = stones.reduce((a: number, b:number): number => a + b); + const target: number = Math.floor(sum / 2); + const n: number = stones.length; + // dp[j]表示容量(总数和)为j的背包所能装下的数(下标[0, i]之间任意取)的总和(<= 容量)的最大值 + const dp: number[] = new Array(target + 1).fill(0); + for (let i: number = 0; i < n; i++ ) { + for (let j: number = target; j >= stones[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]); + } + } + return sum - dp[target] - dp[target]; +}; +``` + +### Scala + +滚动数组: +```scala +object Solution { + def lastStoneWeightII(stones: Array[Int]): Int = { + var sum = stones.sum + var half = sum / 2 + var dp = new Array[Int](half + 1) + + // 遍历 + for (i <- 0 until stones.length; j <- half to stones(i) by -1) { + dp(j) = math.max(dp(j), dp(j - stones(i)) + stones(i)) + } + + sum - 2 * dp(half) + } +} +``` + +二维数组: +```scala +object Solution { + def lastStoneWeightII(stones: Array[Int]): Int = { + var sum = stones.sum + var half = sum / 2 + var dp = Array.ofDim[Int](stones.length, half + 1) + + // 初始化 + for (j <- stones(0) to half) dp(0)(j) = stones(0) + + // 遍历 + for (i <- 1 until stones.length; j <- 1 to half) { + if (j - stones(i) >= 0) dp(i)(j) = stones(i) + dp(i - 1)(j - stones(i)) + dp(i)(j) = math.max(dp(i)(j), dp(i - 1)(j)) + } + + sum - 2 * dp(stones.length - 1)(half) + } +} +``` + + + + -----------------------
diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index fdcc7619..4f4cb32a 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -4,40 +4,40 @@

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

-## 1143.最长公共子序列 +## 1143.最长公共子序列 -[力扣题目链接](https://leetcode-cn.com/problems/longest-common-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/longest-common-subsequence/) -给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 +给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 -一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 +一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。 -例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 +例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。 -若这两个字符串没有公共子序列,则返回 0。 +若这两个字符串没有公共子序列,则返回 0。 -示例 1: +示例 1: -输入:text1 = "abcde", text2 = "ace" -输出:3 -解释:最长公共子序列是 "ace",它的长度为 3。 +输入:text1 = "abcde", text2 = "ace" +输出:3 +解释:最长公共子序列是 "ace",它的长度为 3。 -示例 2: -输入:text1 = "abc", text2 = "abc" -输出:3 -解释:最长公共子序列是 "abc",它的长度为 3。 +示例 2: +输入:text1 = "abc", text2 = "abc" +输出:3 +解释:最长公共子序列是 "abc",它的长度为 3。 -示例 3: -输入:text1 = "abc", text2 = "def" -输出:0 -解释:两个字符串没有公共子序列,返回 0。 +示例 3: +输入:text1 = "abc", text2 = "def" +输出:0 +解释:两个字符串没有公共子序列,返回 0。 -提示: +提示: * 1 <= text1.length <= 1000 * 1 <= text2.length <= 1000 输入的字符串只含有小写英文字符。 -## 思路 +## 思路 本题和[动态规划:718. 最长重复子数组](https://programmercarl.com/0718.最长重复子数组.html)区别在于这里不要求是连续的了,但要有相对顺序,即:"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。 @@ -45,21 +45,21 @@ 1. 确定dp数组(dp table)以及下标的含义 -dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j] +dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j] -有同学会问:为什么要定义长度为[0, i - 1]的字符串text1,定义为长度为[0, i]的字符串text1不香么? +有同学会问:为什么要定义长度为[0, i - 1]的字符串text1,定义为长度为[0, i]的字符串text1不香么? 这样定义是为了后面代码实现方便,如果非要定义为为长度为[0, i]的字符串text1也可以,大家可以试一试! 2. 确定递推公式 -主要就是两大情况: text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同 +主要就是两大情况: text1[i - 1] 与 text2[j - 1]相同,text1[i - 1] 与 text2[j - 1]不相同 -如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1; +如果text1[i - 1] 与 text2[j - 1]相同,那么找到了一个公共元素,所以dp[i][j] = dp[i - 1][j - 1] + 1; 如果text1[i - 1] 与 text2[j - 1]不相同,那就看看text1[0, i - 2]与text2[0, j - 1]的最长公共子序列 和 text1[0, i - 1]与text2[0, j - 2]的最长公共子序列,取最大的。 -即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); +即:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); 代码如下: @@ -71,9 +71,9 @@ if (text1[i - 1] == text2[j - 1]) { } ``` -3. dp数组如何初始化 +3. dp数组如何初始化 -先看看dp[i][0]应该是多少呢? +先看看dp[i][0]应该是多少呢? test1[0, i-1]和空串的最长公共子序列自然是0,所以dp[i][0] = 0; @@ -101,7 +101,7 @@ vector> dp(text1.size() + 1, vector(text2.size() + 1, 0)); ![1143.最长公共子序列1](https://img-blog.csdnimg.cn/20210210150215918.jpg) -最后红框dp[text1.size()][text2.size()]为最终结果 +最后红框dp[text1.size()][text2.size()]为最终结果 以上分析完毕,C++代码如下: @@ -129,6 +129,9 @@ public: Java: ```java +/* + 二维dp数组 +*/ class Solution { public int longestCommonSubsequence(String text1, String text2) { int[][] dp = new int[text1.length() + 1][text2.length() + 1]; // 先对dp数组做初始化操作 @@ -146,6 +149,47 @@ class Solution { return dp[text1.length()][text2.length()]; } } + + + +/** + 一维dp数组 +*/ +class Solution { + public int longestCommonSubsequence(String text1, String text2) { + int n1 = text1.length(); + int n2 = text2.length(); + + // 多从二维dp数组过程分析 + // 关键在于 如果记录 dp[i - 1][j - 1] + // 因为 dp[i - 1][j - 1] dp[j - 1] <=> dp[i][j - 1] + int [] dp = new int[n2 + 1]; + + for(int i = 1; i <= n1; i++){ + + // 这里pre相当于 dp[i - 1][j - 1] + int pre = dp[0]; + for(int j = 1; j <= n2; j++){ + + //用于给pre赋值 + int cur = dp[j]; + if(text1.charAt(i - 1) == text2.charAt(j - 1)){ + //这里pre相当于dp[i - 1][j - 1] 千万不能用dp[j - 1] !! + dp[j] = pre + 1; + } else{ + // dp[j] 相当于 dp[i - 1][j] + // dp[j - 1] 相当于 dp[i][j - 1] + dp[j] = Math.max(dp[j], dp[j - 1]); + } + + //更新dp[i - 1][j - 1], 为下次使用做准备 + pre = cur; + } + } + + return dp[n2]; + } +} ``` Python: @@ -158,7 +202,7 @@ class Solution: for i in range(1, len2): for j in range(1, len1): # 开始列出状态转移方程 if text1[j-1] == text2[i-1]: - dp[i][j] = dp[i-1][j-1]+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] @@ -189,10 +233,32 @@ func longestCommonSubsequence(text1 string, text2 string) int { func max(a,b int)int { if a>b{ - return a + return a } return b } + +``` + +Rust: +```rust +pub fn longest_common_subsequence(text1: String, text2: String) -> i32 { + let (n, m) = (text1.len(), text2.len()); + let (s1, s2) = (text1.as_bytes(), text2.as_bytes()); + let mut dp = vec![0; m + 1]; + let mut last = vec![0; m + 1]; + for i in 1..=n { + dp.swap_with_slice(&mut last); + for j in 1..=m { + dp[j] = if s1[i - 1] == s2[j - 1] { + last[j - 1] + 1 + } else { + last[j].max(dp[j - 1]) + }; + } + } + dp[m] +} ``` Javascript: @@ -214,6 +280,32 @@ const longestCommonSubsequence = (text1, text2) => { }; ``` +TypeScript: + +```typescript +function longestCommonSubsequence(text1: string, text2: string): number { + /** + dp[i][j]: text1中前i-1个和text2中前j-1个,最长公共子序列的长度 + */ + const length1: number = text1.length, + length2: number = text2.length; + const dp: number[][] = new Array(length1 + 1).fill(0) + .map(_ => new Array(length2 + 1).fill(0)); + for (let i = 1; i <= length1; i++) { + for (let j = 1; j <= length2; j++) { + if (text1[i - 1] === text2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]); + } + } + } + return dp[length1][length2]; +}; +``` + + + -----------------------
diff --git a/problems/1207.独一无二的出现次数.md b/problems/1207.独一无二的出现次数.md index ba92552a..5298f5be 100644 --- a/problems/1207.独一无二的出现次数.md +++ b/problems/1207.独一无二的出现次数.md @@ -6,7 +6,7 @@ # 1207.独一无二的出现次数 -[力扣题目链接](https://leetcode-cn.com/problems/unique-number-of-occurrences/) +[力扣题目链接](https://leetcode.cn/problems/unique-number-of-occurrences/) 给你一个整数数组 arr,请你帮忙统计数组中每个数的出现次数。 @@ -150,5 +150,39 @@ var uniqueOccurrences = function(arr) { }; ``` +TypeScript: + +> 借用数组: + +```typescript +function uniqueOccurrences(arr: number[]): boolean { + const countArr: number[] = new Array(2001).fill(0); + for (let i = 0, length = arr.length; i < length; i++) { + countArr[arr[i] + 1000]++; + } + const flagArr: boolean[] = new Array(1001).fill(false); + for (let count of countArr) { + if (count === 0) continue; + if (flagArr[count] === true) return false; + flagArr[count] = true; + } + return true; +}; +``` + +> 借用map、set + +```typescript +function uniqueOccurrences(arr: number[]): boolean { + const countMap: Map = new Map(); + arr.forEach(val => { + countMap.set(val, (countMap.get(val) || 0) + 1); + }) + return countMap.size === new Set(countMap.values()).size; +}; +``` + + + -----------------------
diff --git a/problems/1221.分割平衡字符串.md b/problems/1221.分割平衡字符串.md index 1a9b34a2..857df7ef 100644 --- a/problems/1221.分割平衡字符串.md +++ b/problems/1221.分割平衡字符串.md @@ -6,7 +6,7 @@ # 1221. 分割平衡字符串 -[力扣题目链接](https://leetcode-cn.com/problems/split-a-string-in-balanced-strings/) +[力扣题目链接](https://leetcode.cn/problems/split-a-string-in-balanced-strings/) 在一个 平衡字符串 中,'L' 和 'R' 字符的数量是相同的。 @@ -108,11 +108,38 @@ class Solution { ### Python ```python +class Solution: + def balancedStringSplit(self, s: str) -> int: + diff = 0 #右左差值 + ans = 0 + for c in s: + if c == "L": + diff -= 1 + else: + diff += 1 + if tilt == 0: + ans += 1 + return ans ``` ### Go ```go +func balancedStringSplit(s string) int { + diff := 0 // 右左差值 + ans := 0 + for _, c := range s { + if c == 'L' { + diff-- + }else { + diff++ + } + if diff == 0 { + ans++ + } + } + return ans +} ``` ### JavaScript diff --git a/problems/1356.根据数字二进制下1的数目排序.md b/problems/1356.根据数字二进制下1的数目排序.md index 838c3a96..5ca73607 100644 --- a/problems/1356.根据数字二进制下1的数目排序.md +++ b/problems/1356.根据数字二进制下1的数目排序.md @@ -8,9 +8,9 @@ # 1356. 根据数字二进制下 1 的数目排序 -[力扣题目链接](https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/) +[力扣题目链接](https://leetcode.cn/problems/sort-integers-by-the-number-of-1-bits/) -题目链接:https://leetcode-cn.com/problems/sort-integers-by-the-number-of-1-bits/ +题目链接:https://leetcode.cn/problems/sort-integers-by-the-number-of-1-bits/ 给你一个整数数组 arr 。请你将数组中的元素按照其二进制表示中数字 1 的数目升序排序。 diff --git a/problems/1365.有多少小于当前数字的数字.md b/problems/1365.有多少小于当前数字的数字.md index 78fa84c0..3aaadf6d 100644 --- a/problems/1365.有多少小于当前数字的数字.md +++ b/problems/1365.有多少小于当前数字的数字.md @@ -8,7 +8,7 @@ # 1365.有多少小于当前数字的数字 -[力扣题目链接](https://leetcode-cn.com/problems/how-many-numbers-are-smaller-than-the-current-number/) +[力扣题目链接](https://leetcode.cn/problems/how-many-numbers-are-smaller-than-the-current-number/) 给你一个数组 nums,对于其中每个元素 nums[i],请你统计数组中比它小的所有数字的数目。 @@ -217,6 +217,46 @@ var smallerNumbersThanCurrent = function(nums) { }; ``` +TypeScript: + +> 暴力法: + +```typescript +function smallerNumbersThanCurrent(nums: number[]): number[] { + const length: number = nums.length; + const resArr: number[] = []; + for (let i = 0; i < length; i++) { + let count: number = 0; + for (let j = 0; j < length; j++) { + if (nums[j] < nums[i]) { + count++; + } + } + resArr[i] = count; + } + return resArr; +}; +``` + +> 排序+hash + +```typescript +function smallerNumbersThanCurrent(nums: number[]): number[] { + const length: number = nums.length; + const sortedArr: number[] = [...nums]; + sortedArr.sort((a, b) => a - b); + const hashMap: Map = new Map(); + for (let i = length - 1; i >= 0; i--) { + hashMap.set(sortedArr[i], i); + } + const resArr: number[] = []; + for (let i = 0; i < length; i++) { + resArr[i] = hashMap.get(nums[i]); + } + return resArr; +}; +``` + ----------------------- diff --git a/problems/1382.将二叉搜索树变平衡.md b/problems/1382.将二叉搜索树变平衡.md index 57231ec4..7d983fbb 100644 --- a/problems/1382.将二叉搜索树变平衡.md +++ b/problems/1382.将二叉搜索树变平衡.md @@ -7,7 +7,7 @@ # 1382.将二叉搜索树变平衡 -[力扣题目链接](https://leetcode-cn.com/problems/balance-a-binary-search-tree/) +[力扣题目链接](https://leetcode.cn/problems/balance-a-binary-search-tree/) 给你一棵二叉搜索树,请你返回一棵 平衡后 的二叉搜索树,新生成的树应该与原来的树有着相同的节点值。 @@ -123,6 +123,46 @@ class Solution: ``` Go: +```go +/** + * Definition for a binary tree node. + * type TreeNode struct { + * Val int + * Left *TreeNode + * Right *TreeNode + * } + */ +func balanceBST(root *TreeNode) *TreeNode { + // 二叉搜索树中序遍历得到有序数组 + nums := []int{} + // 中序递归遍历二叉树 + var travel func(node *TreeNode) + travel = func(node *TreeNode) { + if node == nil { + return + } + travel(node.Left) + nums = append(nums, node.Val) + travel(node.Right) + } + // 二分法保证左右子树高度差不超过一(题目要求返回的仍是二叉搜索树) + var buildTree func(nums []int, left, right int) *TreeNode + buildTree = func(nums []int, left, right int) *TreeNode { + if left > right { + return nil + } + mid := left + (right-left) >> 1 + root := &TreeNode{Val: nums[mid]} + root.Left = buildTree(nums, left, mid-1) + root.Right = buildTree(nums, mid+1, right) + return root + } + travel(root) + return buildTree(nums, 0, len(nums)-1) +} + +``` + JavaScript: ```javascript var balanceBST = function(root) { @@ -148,6 +188,30 @@ var balanceBST = function(root) { }; ``` +TypeScript: + +```typescript +function balanceBST(root: TreeNode | null): TreeNode | null { + const inorderArr: number[] = []; + inorderTraverse(root, inorderArr); + return buildTree(inorderArr, 0, inorderArr.length - 1); +}; +function inorderTraverse(node: TreeNode | null, arr: number[]): void { + if (node === null) return; + inorderTraverse(node.left, arr); + arr.push(node.val); + inorderTraverse(node.right, arr); +} +function buildTree(arr: number[], left: number, right: number): TreeNode | null { + if (left > right) return null; + const mid = (left + right) >> 1; + const resNode: TreeNode = new TreeNode(arr[mid]); + resNode.left = buildTree(arr, left, mid - 1); + resNode.right = buildTree(arr, mid + 1, right); + return resNode; +} +``` + ----------------------- diff --git a/problems/1791.找出星型图的中心节点.md b/problems/1791.找出星型图的中心节点.md new file mode 100644 index 00000000..57777fd7 --- /dev/null +++ b/problems/1791.找出星型图的中心节点.md @@ -0,0 +1,73 @@ +# 1791.找出星型图的中心节点 + +[题目链接](https://leetcode.cn/problems/find-center-of-star-graph/) + +本题思路就是统计各个节点的度(这里没有区别入度和出度),如果某个节点的度等于这个图边的数量。 那么这个节点一定是中心节点。 + +什么是度,可以理解为,链接节点的边的数量。 题目中度如图所示: + +![1791.找出星型图的中心节点](https://code-thinking-1253855093.file.myqcloud.com/pics/20220704113207.png) + +至于出度和入度,那就是在有向图里的概念了,本题是无向图。 + +本题代码如下: + +```c++ + +class Solution { +public: + int findCenter(vector>& edges) { + unordered_map du; + for (int i = 0; i < edges.size(); i++) { // 统计各个节点的度 + du[edges[i][1]]++; + du[edges[i][0]]++; + } + unordered_map::iterator iter; // 找出度等于边熟练的节点 + for (iter = du.begin(); iter != du.end(); iter++) { + if (iter->second == edges.size()) return iter->first; + } + return -1; + } +}; +``` + +其实可以只记录度不用最后统计,因为题目说了一定是星状图,所以 一旦有 节点的度 大于1,就返回该节点数值就行,只有中心节点的度会大于1。 + +代码如下: + +```c++ +class Solution { +public: + int findCenter(vector>& edges) { + vector du(edges.size() + 2); // edges.size() + 1 为节点数量,下标表示节点数,所以+2 + for (int i = 0; i < edges.size(); i++) { + du[edges[i][1]]++; + du[edges[i][0]]++; + if (du[edges[i][1]] > 1) return edges[i][1]; + if (du[edges[i][0]] > 1) return edges[i][0]; + + } + return -1; + } +}; +``` + +以上代码中没有使用 unordered_map,因为遍历的时候,开辟新空间会浪费时间,而采用 vector,这是 空间换时间的一种策略。 + +代码其实可以再精简: + +```c++ +class Solution { +public: + int findCenter(vector>& edges) { + vector du(edges.size() + 2); + for (int i = 0; i < edges.size(); i++) { + if (++du[edges[i][1]] > 1) return edges[i][1]; + if (++du[edges[i][0]] > 1) return edges[i][0]; + } + return -1; + } +}; +``` + + diff --git a/problems/1971.寻找图中是否存在路径.md b/problems/1971.寻找图中是否存在路径.md new file mode 100644 index 00000000..bf1e93cf --- /dev/null +++ b/problems/1971.寻找图中是否存在路径.md @@ -0,0 +1,123 @@ +# 1971. 寻找图中是否存在路径 + +[题目链接](https://leetcode.cn/problems/find-if-path-exists-in-graph/) + +有一个具有 n个顶点的 双向 图,其中每个顶点标记从 0 到 n - 1(包含 0 和 n - 1)。图中的边用一个二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示顶点 ui 和顶点 vi 之间的双向边。 每个顶点对由 最多一条 边连接,并且没有顶点存在与自身相连的边。 + +请你确定是否存在从顶点 start 开始,到顶点 end 结束的 有效路径 。 + +给你数组 edges 和整数 n、start和end,如果从 start 到 end 存在 有效路径 ,则返回 true,否则返回 false 。 + + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20220705101442.png) + + +提示: + +* 1 <= n <= 2 * 10^5 +* 0 <= edges.length <= 2 * 10^5 +* edges[i].length == 2 +* 0 <= ui, vi <= n - 1 +* ui != vi +* 0 <= start, end <= n - 1 +* 不存在双向边 +* 不存在指向顶点自身的边 + +## 思路 + +这道题目也是并查集基础题目。 + +首先要知道并查集可以解决什么问题呢? + +主要就是集合问题,两个节点在不在一个集合,也可以将两个节点添加到一个集合中。 + +这里整理出我的并查集模板如下: + +```CPP +int n = 1005; // 节点数量3 到 1000 +int father[1005]; + +// 并查集初始化 +void init() { + for (int i = 0; i < n; ++i) { + father[i] = i; + } +} +// 并查集里寻根的过程 +int find(int u) { + return u == father[u] ? u : father[u] = find(father[u]); +} +// 将v->u 这条边加入并查集 +void join(int u, int v) { + u = find(u); + v = find(v); + if (u == v) return ; + father[v] = u; +} +// 判断 u 和 v是否找到同一个根 +bool same(int u, int v) { + u = find(u); + v = find(v); + return u == v; +} +``` + +以上模板汇总,只要修改 n 和father数组的大小就可以了。 + +并查集主要有三个功能。 + +1. 寻找根节点,函数:find(int u),也就是判断这个节点的祖先节点是哪个 +2. 将两个节点接入到同一个集合,函数:join(int u, int v),将两个节点连在同一个根节点上 +3. 判断两个节点是否在同一个集合,函数:same(int u, int v),就是判断两个节点是不是同一个根节点 + +简单介绍并查集之后,我们再来看一下这道题目。 + +为什么说这道题目是并查集基础题目,因为 可以直接套用模板。 + +使用join(int u, int v)将每条边加入到并查集。 + +最后 same(int u, int v) 判断是否是同一个根 就可以里。 + +代码如下: + +```c++ +class Solution { + +private: + int n = 200005; // 节点数量 20000 + int father[200005]; + + // 并查集初始化 + void init() { + for (int i = 0; i < n; ++i) { + father[i] = i; + } + } + // 并查集里寻根的过程 + int find(int u) { + return u == father[u] ? u : father[u] = find(father[u]); + } + // 将v->u 这条边加入并查集 + void join(int u, int v) { + u = find(u); + v = find(v); + if (u == v) return ; + father[v] = u; + } + // 判断 u 和 v是否找到同一个根,本题用不上 + bool same(int u, int v) { + u = find(u); + v = find(v); + return u == v; + } + +public: + bool validPath(int n, vector>& edges, int source, int destination) { + init(); + for (int i = 0; i < edges.size(); i++) { + join(edges[i][0], edges[i][1]); + } + return same(source, destination); + } +}; +``` diff --git a/problems/qita/gitserver.md b/problems/qita/gitserver.md new file mode 100644 index 00000000..9ee06ae4 --- /dev/null +++ b/problems/qita/gitserver.md @@ -0,0 +1,312 @@ + +# 一文手把手教你搭建Git私服 + +## 为什么要搭建Git私服 + +很多同学都问文章,文档,资料怎么备份啊,自己电脑和公司电脑怎么随时同步资料啊等等,这里呢我写一个搭建自己的git私服的详细教程 + +为什么要搭建一个Git私服呢,而不是用Github免费的私有仓库,有以下几点: +* Github 私有仓库真的慢,文件一旦多了,或者有图片文件,git pull 的时候半天拉不下来 +* 自己的文档难免有自己个人信息,放在github心里也是担心的 +* 想建几个库就建几个,想几个人合作开发都可以,不香么? + +**网上可以搜到很多git搭建,但是说的模棱两可**,而且有的直接是在本地搭建git服务,既然是备份,搭建在本地哪有备份的意义,一定要有一个远端服务器, 而且自己的电脑和公司的电脑还是同步自己的文章,文档和资料等等。 + + +适合人群: 想通过git私服来备份自己的文章,Markdown,并做版本管理的同学 +最后,写好每篇 Chat 是对我的责任,也是对你的尊重。谢谢大家~ + +正文如下: + +----------------------------- + +## 如何找到可以外网访问服务器 + +有的同学问了,自己的电脑就不能作为服务器么? + +这里要说一下,安装家庭带宽,运营商默认是不会给我们独立分配公网IP的 + +一般情况下是一片区域公用一个公网IP池,所以外网是不能访问到在家里我们使用的电脑的 + +除非我们自己去做映射,这其实非常麻烦而且公网IP池 是不断变化的 + +辛辛苦苦做了映射,运营商给IP一换,我们的努力就白扯了 + +那我们如何才能找到一个外网可以访问的服务器呢,此时云计算拯救了我们。 + +推荐大家选一家云厂商(阿里云,腾讯云,百度云都可以)在上面上买一台云服务器 + +* [阿里云活动期间服务器购买](https://www.aliyun.com/minisite/goods?taskCode=shareNew2205&recordId=3641992&userCode=roof0wob) +* [腾讯云活动期间服务器购买](https://curl.qcloud.com/EiaMXllu) + +云厂商经常做活动,如果从来没有买过云服务器的账号更便宜,低配一年一百块左右的样子,强烈推荐一起买个三年。 + +买云服务器的时候推荐直接安装centos系统。 + +这里要说一下,有了自己的云服务器之后 不仅仅可以用来做git私服 + +**同时还可以做网站,做程序后台,跑程序,做测试**(这样我们自己的电脑就不会因为自己各种搭建环境下载各种包而搞的的烂糟糟),等等等。 + +有自己云服务器和一个公网IP真的是一件非常非常幸福的事情,能体验到自己的服务随时可以部署上去提供给所有人使用的喜悦。 + +外网可以访问的服务器解决了,接下来就要部署git服务了 + +本文将采用centos系统来部署git私服 + +## 服务器端安装Git + +切换至root账户 + +``` +su root +``` + +看一下服务器有没有安装git,如果出现下面信息就说明是有git的 +``` +[root@instance-5fcyjde7 ~]# git +usage: git [--version] [--help] [-c name=value] + [--exec-path[=]] [--html-path] [--man-path] [--info-path] + [-p|--paginate|--no-pager] [--no-replace-objects] [--bare] + [--git-dir=] [--work-tree=] [--namespace=] + [] + +The most commonly used git commands are: + add Add file contents to the index + bisect Find by binary search the change that introduced a bug + branch List, create, or delete branches + checkout Checkout a branch or paths to the working tree + clone Clone a repository into a new directory + commit Record changes to the repository + diff Show changes between commits, commit and working tree, etc + fetch Download objects and refs from another repository + grep Print lines matching a pattern + init Create an empty Git repository or reinitialize an existing one + log Show commit logs + merge Join two or more development histories together + mv Move or rename a file, a directory, or a symlink + pull Fetch from and merge with another repository or a local branch + push Update remote refs along with associated objects + rebase Forward-port local commits to the updated upstream head + reset Reset current HEAD to the specified state + rm Remove files from the working tree and from the index + show Show various types of objects + status Show the working tree status + tag Create, list, delete or verify a tag object signed with GPG + +'git help -a' and 'git help -g' lists available subcommands and some +concept guides. See 'git help ' or 'git help ' +to read about a specific subcommand or concept. +``` + +如果没有git,就安装一下,yum安装的版本默认是 `1.8.3.1` + +``` +yum install git +``` + +安装成功之后,看一下自己安装的版本 + +``` +git --version +``` + +## 服务器端设置Git账户 + +创建一个git的linux账户,这个账户只做git私服的操作,也是为了安全起见 + +如果不新创建一个linux账户,在自己的常用的linux账户下创建的话,哪天手抖 来一个`rm -rf *` 操作 数据可全没了 + +**这里linux git账户的密码设置的尽量复杂一些,我这里为了演示,就设置成为'gitpassword'** +``` +adduser git +passwd gitpassword +``` + +然后就要切换成git账户,进行后面的操作了 +``` +[root@instance-5fcyjde7 ~]# su - git +``` + +看一下自己所在的目录,是不是在git目录下面 + +``` +[git@instance-5fcyjde7 ~]$ pwd +/home/git +``` + +## 服务器端密钥管理 + +创建`.ssh` 目录,如果`.ssh` 已经存在了,可以忽略这一项 + +为啥用配置ssh公钥呢,同学们记不记得我急使用github上传上传代码的时候也要把自己的公钥配置上github上 + +这也是方面每次操作git仓库的时候不用再去输入密码 + +``` +cd ~/ +mkdir .ssh +``` + +进入.ssh 文件下,创建一个 `authorized_keys` 文件,这个文件就是后面就是要放我们客户端的公钥 + +``` +cd ~/.ssh +touch authorized_keys +``` + +别忘了`authorized_keys`给设置权限,很多同学发现自己不能免密登陆,都是因为忘记了给`authorized_keys` 设置权限 + +``` +chmod 700 /home/git/.ssh +chmod 600 /home/git/.ssh/authorized_keys +``` + +接下来我们要把客户端的公钥放在git服务器上,我们在回到客户端,创建一个公钥 + +在我们自己的电脑上,有公钥和私钥 两个文件分别是:`id_rsa` 和 `id_rsa.pub` + +如果是`windows`系统公钥私钥的目录在`C:\Users\用户名\.ssh` 下 + +如果是mac 或者 linux, 公钥和私钥的目录这里 `cd ~/.ssh/`, 如果发现自己的电脑上没有公钥私钥,那就自己创建一个 + +创建密钥的命令 + +``` +ssh-keygen -t rsa +``` + +创建密钥的过程中,一路点击回车就可以了。不需要填任何东西 + +把公钥拷贝到git服务器上,将我们刚刚生成的`id_rsa.pub`,拷贝到git服务器的`/home/git/.ssh/`目录 + +在git服务器上,将公钥添加到`authorized_keys` 文件中 + +``` +cd /home/git/.ssh/ +cat id_rsa.pub >> authorized_keys +``` + +如何看我们配置的密钥是否成功呢, 在客户点直接登录git服务器,看看是否是免密登陆 +``` +ssh git@git服务器ip +``` + +例如: + +``` +ssh git@127.0.0.1 +``` + +如果可以免密登录,那就说明服务器端密钥配置成功了 + +## 服务器端部署Git 仓库 + +我们在登陆到git 服务器端,切换为成 git账户 + +如果是root账户切换成git账户 +``` +su - git +``` + +如果是其他账户切换为git账户 +``` +sudo su - git +``` + +进入git目录下 +``` +cd ~/git +``` + +创建我们的第一个Git私服的仓库,我们叫它为world仓库 + +那么首先创建一个文件夹名为: world.git ,然后进入这个目录 + +有同学问,为什么文件夹名字后面要放`.git`, 其实不这样命名也是可以的 + +但是细心的同学可能注意到,我们平时在github上 `git clone` 其他人的仓库的时候,仓库名字后面,都是加上`.git`的 + +例如下面这个例子,其实就是github对仓库名称的一个命名规则,所以我们也遵守github的命名规则。 + +``` +git clone https://github.com/youngyangyang04/NoSQLAttack.git +``` + +所以我们的操作是 +``` +[git@localhost git]# mkdir world.git +[git@localhost git]# cd world.git +``` + +初始化我们的`world`仓库 + +``` +git init --bare + +``` + +**如果我们想创建多个仓库,就在这里创建多个文件夹并初始化就可以了,和world仓库的操作过程是一样一样的** + +现在我们服务端的git仓库就部署完了,接下来就看看客户端,如何使用这个仓库呢 + +## 客户端连接远程仓库 + +我们在自己的电脑上创建一个文件夹 也叫做`world`吧 + +其实这里命名是随意的,但是我们为了和git服务端的仓库名称保持同步。 这样更直观我们操作的是哪一个仓库。 + +``` +mkdir world +cd world +``` + +进入world文件,并初始化操作 + +``` +cd world +git init +``` + +在world目录上创建一个测试文件,并且将其添加到git版本管理中 + +``` +touch test +git add test +git commit -m "add test file" +``` + +将次仓库和远端仓库同步 + +``` +git remote add origin git@git服务器端的ip:world.git +git push -u origin master +``` + +此时这个test测试文件就已经提交到我们的git远端私服上了 + +## Git私服安全问题 + +这里有两点安全问题 + +### linux git的密码不要泄露出去 + +否则,别人可以通过 ssh git@git服务器IP 来登陆到你的git私服服务器上 + +当然了,这里同学们如果买的是云厂商的云服务器的话 + +如果有人恶意想通过 尝试不同密码链接的方式来链接你的服务器,重试三次以上 + +这个客户端的IP就会被封掉,同时邮件通知我们可以IP来自哪里 + +所以大可放心 密码只要我们不泄露出去,基本上不会有人同时不断尝试密码的方式来登上我们的git私服服务器 + +### 私钥文件`id_rsa` 不要给别人 + +如果有人得到了这个私钥,就可以免密码登陆我们的git私服上了,我相信大家也不至于把自己的私钥主动给别人吧 + +## 总结 + +这里就是整个git私服搭建的全过程,安全问题我也给大家列举了出来,接下来好好享受自己的Git私服吧 + +**enjoy!** + diff --git a/problems/qita/server.md b/problems/qita/server.md new file mode 100644 index 00000000..d2e76d36 --- /dev/null +++ b/problems/qita/server.md @@ -0,0 +1,129 @@ + +# 一台服务器有什么用! + +* [阿里云活动期间服务器购买](https://www.aliyun.com/minisite/goods?taskCode=shareNew2205&recordId=3641992&userCode=roof0wob) +* [腾讯云活动期间服务器购买](https://curl.qcloud.com/EiaMXllu) + +但在组织这场活动的时候,了解到大家都有一个共同的问题: **这个服务器究竟有啥用??** + +这真是一个好问题,而且我一句两句还说不清楚,所以就专门发文来讲一讲。 + +同时我还录制的一期视频,哈哈我的视频号,大家可以关注一波。 + + +一说到服务器,可能很多人都说搞分布式,做计算,搞爬虫,做程序后台服务,多人合作等等。 + +其实这些普通人都用不上,我来说一说大家能用上的吧。 + +## 搭建git私服 + +大家平时工作的时候一定有一个自己的工作文件夹,学生的话就是自己的课件,考试,准备面试的资料等等。 + +已经工作的录友,会有一个文件夹放着自己重要的文档,Markdown,图片,简历等等。 + +这么重要的文件夹,而且我们每天都要更新,也担心哪天电脑丢了,或者坏了,突然这些都不见了。 + +所以我们想备份嘛。 + +还有就是我们经常个人电脑和工作电脑要同步一些私人资料,而不是用微信传来传去。 + +这些都是git私服的使用场景,而且很好用。 + +大家也知道 github,gitee也可以搞私人仓库 用来备份,同步文件,但自己的文档可能放着很多重要的信息,包括自己的各种密码,密钥之类的,放到上面未必安全。你就不怕哪些重大bug把你的信息都泄漏了么[机智] + +更关键的是,github 和 gitee都限速的。毕竟人家的功能定位并不是网盘。 + +项目里有大文件(几百M以上),例如pdf,ppt等等 其上传和下载速度会让你窒息。 + +**后面我会发文专门来讲一讲,如何大家git私服!** + +## 搞一个文件存储 + +这个可以用来生成文件的下载链接,也可以把本地文件传到服务器上。 + +相当于自己做一个对象存储,其实云厂商也有对象存储的产品。 + +不过我们自己也可以做一个,不够很多很同学应该都不知道对象存储怎么用吧,其实我们用服务器可以自己做一个类似的公司。 + +我现在就用自己用go写的一个工具,部署在服务器上。 用来和服务器传文件,或者生成一些文件的临时下载链接。 + +这些都是直接命令行操作的, + +操作方式这样,我把命令包 包装成一个shell命令,想传那个文件,直接 uploadtomyserver,然后就返回可以下载的链接,这个文件也同时传到了我的服务器上。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211126165643.png) + +我也把我的项目代码放在了github上: + +https://github.com/youngyangyang04/fileHttpServer + +感兴趣的录友可以去学习一波,顺便给个star 哈哈 + + +## 网站 + +做网站,例如 大家知道用html 写几行代码,就可以生成一个网页,但怎么给别人展示呢? + +大家如果用自己的电脑做服务器,只能同一个路由器下的设备可以访问你的网站,可能这个设备出了这个屋子 都访问不了你的网站了。 + +因为你的IP不是公网IP。 + +如果有了一台云服务器,都是配公网IP,你的网站就可以让任何人访问了。 + +或者说 你提供的一个服务就可以让任何人使用。 + +例如第二个例子中,我们可以自己开发一个文件存储,这个服务,我只把把命令行给其他人,其他人都可以使用我的服务来生成链接,当然他们的文件也都传到了我的服务器上。 + +再说一个使用场景。 + +我之前在组织免费里服务器的活动的时候,阿里云给我一个excel,让面就是从我这里买服务器录友的名单,我直接把这个名单甩到群里,让大家自己检查,出现在名单里就可以找我返现,这样做是不是也可以。 + +这么做有几个很大的问题: +* 大家都要去下载excel,做对比,会有人改excel的内容然后就说是从你这里买的,我不可能挨个去比较excel有没有改动 +* excel有其他人的个人信息,这是不能暴漏的。 +* 如果每个人自己用excel查询,私信我返现,一个将近两千人找我返现,我微信根本处理不过来,这就变成体力活了。 + +那应该怎么做呢, + +我就简单写一个查询的页面,后端逻辑就是读一个execel表格,大家在查询页面输入自己的阿里云ID,如果在excel里,页面就会返回返现群的二维码,大家就可以自主扫码加群了。 + +这样,我最后就直接在返现群里 发等额红包就好了,是不是极大降低人力成本了 + +当然我是把 17个返现群的二维码都生成好了,按照一定的规则,展现给查询通过的录友。 + +就是这样一个非常普通的查询页面。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211126160200.png) + +查询通过之后,就会展现返现群二维码。 + +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211127160558.png) + +但要部署在服务器上,因为没有公网IP,别人用不了你的服务。 + + +## 学习linux + +学习linux其实在自己的电脑上搞一台虚拟机,或者安装双系统也可以学习,不过这很考验你的电脑性能如何了。 + +如果你有一个服务器,那就是独立的一台电脑,你怎么霍霍就怎么霍霍,而且一年都不用关机的,可以一直跑你的任务,和你本地电脑也完全隔离。 + +更方便的是,你目前系统假如是CentOS,想做一个实验需要在Ubuntu上,如果是云服务器,更换系统就是在 后台点一下,一键重装,云厂商基本都是支持所有系统一件安装的。 + +我们平时自己玩linux经常是配各种环境,然后这个linux就被自己玩坏了(一般都是毫无节制使用root权限导致的),总之就是环境配不起来了,基本就要重装了。 + +那云服务器重装系统可太方便了。 + +还有就是加入你好不容易配好的环境,如果以后把这个环境玩坏了,你先回退这之前配好的环境而不是重装系统在重新配一遍吧。 + +那么可以用云服务器的镜像保存功能,就是你配好环境的那一刻就可以打一个镜像包,以后如果环境坏了,直接回退到上次镜像包的状态,这是不是就很香了。 + + +## 总结 + +其实云服务器还有很多其他用处,不过我就说一说大家普遍能用的上的。 + + +* [阿里云活动期间服务器购买](https://www.aliyun.com/minisite/goods?taskCode=shareNew2205&recordId=3641992&userCode=roof0wob) +* [腾讯云活动期间服务器购买](https://curl.qcloud.com/EiaMXllu) + diff --git a/problems/其他/参与本项目.md b/problems/qita/参与本项目.md similarity index 100% rename from problems/其他/参与本项目.md rename to problems/qita/参与本项目.md diff --git a/problems/二叉树理论基础.md b/problems/二叉树理论基础.md index 9c151e32..9e10ac20 100644 --- a/problems/二叉树理论基础.md +++ b/problems/二叉树理论基础.md @@ -258,6 +258,13 @@ class TreeNode { } } ``` - +Scala: +```scala +class TreeNode(_value: Int = 0, _left: TreeNode = null, _right: TreeNode = null) { + var value: Int = _value + var left: TreeNode = _left + var right: TreeNode = _right +} +``` -----------------------
diff --git a/problems/二叉树的统一迭代法.md b/problems/二叉树的统一迭代法.md index f6edf586..9ca6ac39 100644 --- a/problems/二叉树的统一迭代法.md +++ b/problems/二叉树的统一迭代法.md @@ -591,6 +591,80 @@ function postorderTraversal(root: TreeNode | null): number[] { return res; }; ``` +Scala: +```scala +// 前序遍历 +object Solution { + import scala.collection.mutable + def preorderTraversal(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + val stack = mutable.Stack[TreeNode]() + if (root != null) stack.push(root) + while (!stack.isEmpty) { + var curNode = stack.top + if (curNode != null) { + stack.pop() + if (curNode.right != null) stack.push(curNode.right) + if (curNode.left != null) stack.push(curNode.left) + stack.push(curNode) + stack.push(null) + } else { + stack.pop() + res.append(stack.pop().value) + } + } + res.toList + } +} +// 中序遍历 +object Solution { + import scala.collection.mutable + def inorderTraversal(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + val stack = mutable.Stack[TreeNode]() + if (root != null) stack.push(root) + while (!stack.isEmpty) { + var curNode = stack.top + if (curNode != null) { + stack.pop() + if (curNode.right != null) stack.push(curNode.right) + stack.push(curNode) + stack.push(null) + if (curNode.left != null) stack.push(curNode.left) + } else { + // 等于空的时候好办,弹出这个元素 + stack.pop() + res.append(stack.pop().value) + } + } + res.toList + } +} + +// 后序遍历 +object Solution { + import scala.collection.mutable + def postorderTraversal(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + val stack = mutable.Stack[TreeNode]() + if (root != null) stack.push(root) + while (!stack.isEmpty) { + var curNode = stack.top + if (curNode != null) { + stack.pop() + stack.push(curNode) + stack.push(null) + if (curNode.right != null) stack.push(curNode.right) + if (curNode.left != null) stack.push(curNode.left) + } else { + stack.pop() + res.append(stack.pop().value) + } + } + res.toList + } +} +``` -----------------------
diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index 8164724b..dc8e812c 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -11,9 +11,9 @@ 看完本篇大家可以使用迭代法,再重新解决如下三道leetcode上的题目: -* 144.二叉树的前序遍历 -* 94.二叉树的中序遍历 -* 145.二叉树的后序遍历 +* [144.二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) +* [94.二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) +* [145.二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) 为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢? @@ -568,6 +568,71 @@ func inorderTraversal(_ root: TreeNode?) -> [Int] { return result } ``` +Scala: +```scala +// 前序遍历(迭代法) +object Solution { + import scala.collection.mutable + def preorderTraversal(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + if (root == null) return res.toList + // 声明一个栈,泛型为TreeNode + val stack = mutable.Stack[TreeNode]() + stack.push(root) // 先把根节点压入栈 + while (!stack.isEmpty) { + var curNode = stack.pop() + res.append(curNode.value) // 先把这个值压入栈 + // 如果当前节点的左右节点不为空,则入栈,先放右节点,再放左节点 + if (curNode.right != null) stack.push(curNode.right) + if (curNode.left != null) stack.push(curNode.left) + } + res.toList + } +} +// 中序遍历(迭代法) +object Solution { + import scala.collection.mutable + def inorderTraversal(root: TreeNode): List[Int] = { + val res = mutable.ArrayBuffer[Int]() + if (root == null) return res.toList + val stack = mutable.Stack[TreeNode]() + var curNode = root + // 将左节点都入栈,当遍历到最左(到空)的时候,再弹出栈顶元素,加入res + // 再把栈顶元素的右节点加进来,继续下一轮遍历 + while (curNode != null || !stack.isEmpty) { + if (curNode != null) { + stack.push(curNode) + curNode = curNode.left + } else { + curNode = stack.pop() + res.append(curNode.value) + curNode = curNode.right + } + } + res.toList + } +} + +// 后序遍历(迭代法) +object Solution { + import scala.collection.mutable + def postorderTraversal(root: TreeNode): List[Int] = { + val res = mutable.ListBuffer[Int]() + if (root == null) return res.toList + val stack = mutable.Stack[TreeNode]() + stack.push(root) + while (!stack.isEmpty) { + val curNode = stack.pop() + res.append(curNode.value) + // 这次左节点先入栈,右节点再入栈 + if(curNode.left != null) stack.push(curNode.left) + if(curNode.right != null) stack.push(curNode.right) + } + // 最后需要翻转List + res.reverse.toList + } +} +``` -----------------------
diff --git a/problems/二叉树的递归遍历.md b/problems/二叉树的递归遍历.md index 612f2394..1cce2a0d 100644 --- a/problems/二叉树的递归遍历.md +++ b/problems/二叉树的递归遍历.md @@ -99,9 +99,9 @@ void traversal(TreeNode* cur, vector& vec) { 此时大家可以做一做leetcode上三道题目,分别是: -* 144.二叉树的前序遍历 -* 145.二叉树的后序遍历 -* 94.二叉树的中序遍历 +* [144.二叉树的前序遍历](https://leetcode.cn/problems/binary-tree-preorder-traversal/) +* [145.二叉树的后序遍历](https://leetcode.cn/problems/binary-tree-postorder-traversal/) +* [94.二叉树的中序遍历](https://leetcode.cn/problems/binary-tree-inorder-traversal/) 可能有同学感觉前后中序遍历的递归太简单了,要打迭代法(非递归),别急,我们明天打迭代法,打个通透! @@ -470,5 +470,56 @@ func postorder(_ root: TreeNode?, res: inout [Int]) { res.append(root!.val) } ``` +Scala: 前序遍历:(144.二叉树的前序遍历) +```scala +object Solution { + import scala.collection.mutable.ListBuffer + def preorderTraversal(root: TreeNode): List[Int] = { + val res = ListBuffer[Int]() + def traversal(curNode: TreeNode): Unit = { + if(curNode == null) return + res.append(curNode.value) + traversal(curNode.left) + traversal(curNode.right) + } + traversal(root) + res.toList + } +} +``` +中序遍历:(94. 二叉树的中序遍历) +```scala +object Solution { + import scala.collection.mutable.ListBuffer + def inorderTraversal(root: TreeNode): List[Int] = { + val res = ListBuffer[Int]() + def traversal(curNode: TreeNode): Unit = { + if(curNode == null) return + traversal(curNode.left) + res.append(curNode.value) + traversal(curNode.right) + } + traversal(root) + res.toList + } +} +``` +后序遍历:(145. 二叉树的后序遍历) +```scala +object Solution { + import scala.collection.mutable.ListBuffer + def postorderTraversal(root: TreeNode): List[Int] = { + val res = ListBuffer[Int]() + def traversal(curNode: TreeNode): Unit = { + if (curNode == null) return + traversal(curNode.left) + traversal(curNode.right) + res.append(curNode.value) + } + traversal(root) + res.toList + } +} +``` -----------------------
diff --git a/problems/前序/ACM模式如何构建二叉树.md b/problems/前序/ACM模式如何构建二叉树.md index 815eb17d..f2aafed6 100644 --- a/problems/前序/ACM模式如何构建二叉树.md +++ b/problems/前序/ACM模式如何构建二叉树.md @@ -266,7 +266,61 @@ public class Solution { ## Python -```Python +```Python3 +class TreeNode: + def __init__(self, val = 0, left = None, right = None): + self.val = val + self.left = left + self.right = right + + +# 根据数组构建二叉树 + +def construct_binary_tree(nums: []) -> TreeNode: + if not nums: + return None + # 用于存放构建好的节点 + root = TreeNode(-1) + Tree = [] + # 将数组元素全部转化为树节点 + for i in range(len(nums)): + if nums[i]!= -1: + node = TreeNode(nums[i]) + else: + node = None + Tree.append(node) + if i == 0: + root = node + for i in range(len(Tree)): + node = Tree[i] + if node and (2 * i + 2) < len(Tree): + node.left = Tree[i * 2 + 1] + node.right = Tree[i * 2 + 2] + return root + + + +# 算法:中序遍历二叉树 + +class Solution: + def __init__(self): + self.T = [] + def inorder(self, root: TreeNode) -> []: + if not root: + return + self.inorder(root.left) + self.T.append(root.val) + self.inorder(root.right) + return self.T + + + +# 验证创建二叉树的有效性,二叉排序树的中序遍历应为顺序排列 + +test_tree = [3, 1, 5, -1, 2, 4 ,6] +root = construct_binary_tree(test_tree) +A = Solution() +print(A.inorder(root)) ``` diff --git a/problems/剑指Offer05.替换空格.md b/problems/剑指Offer05.替换空格.md index 037bd427..e1ccc458 100644 --- a/problems/剑指Offer05.替换空格.md +++ b/problems/剑指Offer05.替换空格.md @@ -7,7 +7,7 @@ # 题目:剑指Offer 05.替换空格 -[力扣题目链接](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/) +[力扣题目链接](https://leetcode.cn/problems/ti-huan-kong-ge-lcof/) 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 @@ -413,8 +413,129 @@ func replaceSpace(_ s: String) -> String { } ``` +Scala: + +方式一: 双指针 +```scala +object Solution { + def replaceSpace(s: String): String = { + var count = 0 + s.foreach(c => if (c == ' ') count += 1) // 统计空格的数量 + val sOldSize = s.length // 旧数组字符串长度 + val sNewSize = s.length + count * 2 // 新数组字符串长度 + val res = new Array[Char](sNewSize) // 新数组 + var index = sNewSize - 1 // 新数组索引 + // 逆序遍历 + for (i <- (0 until sOldSize).reverse) { + if (s(i) == ' ') { + res(index) = '0' + index -= 1 + res(index) = '2' + index -= 1 + res(index) = '%' + } else { + res(index) = s(i) + } + index -= 1 + } + res.mkString + } +} +``` +方式二: 使用一个集合,遇到空格就添加%20 +```scala +object Solution { + import scala.collection.mutable.ListBuffer + def replaceSpace(s: String): String = { + val res: ListBuffer[Char] = ListBuffer[Char]() + for (i <- s.indices) { + if (s(i) == ' ') { + res += '%' + res += '2' + res += '0' + }else{ + res += s(i) + } + } + res.mkString + } +} +``` +方式三: 使用map +```scala +object Solution { + def replaceSpace(s: String): String = { + s.map(c => if(c == ' ') "%20" else c).mkString + } + } +``` +PHP: +```php +function replaceSpace($s){ + $sLen = strlen($s); + $moreLen = $this->spaceLen($s) * 2; + + $head = $sLen - 1; + $tail = $sLen + $moreLen - 1; + + $s = $s . str_repeat(' ', $moreLen); + while ($head != $tail) { + if ($s[$head] == ' ') { + $s[$tail--] = '0'; + $s[$tail--] = '2'; + $s[$tail] = '%'; + } else { + $s[$tail] = $s[$head]; + } + $head--; + $tail--; + } + return $s; +} +// 统计空格个数 +function spaceLen($s){ + $count = 0; + for ($i = 0; $i < strlen($s); $i++) { + if ($s[$i] == ' ') { + $count++; + } + } + return $count; +} +``` + +Rust + +```Rust +impl Solution { + pub fn replace_space(s: String) -> String { + let mut len: usize = s.len(); + let mut s = s.chars().collect::>(); + let mut count = 0; + for i in &s { + if i.is_ascii_whitespace() { + count += 1; + } + } + let mut new_len = len + count * 2; + s.resize(new_len, ' '); + while len < new_len { + len -= 1; + new_len -= 1; + if s[len].is_ascii_whitespace() { + s[new_len] = '0'; + s[new_len - 1] = '2'; + s[new_len - 2] = '%'; + new_len -= 2; + } + else { s[new_len] = s[len] } + } + s.iter().collect::() + } +} +``` ----------------------- diff --git a/problems/剑指Offer58-II.左旋转字符串.md b/problems/剑指Offer58-II.左旋转字符串.md index fec83e1d..de4a9030 100644 --- a/problems/剑指Offer58-II.左旋转字符串.md +++ b/problems/剑指Offer58-II.左旋转字符串.md @@ -9,7 +9,7 @@ # 题目:剑指Offer58-II.左旋转字符串 -[力扣题目链接](https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) +[力扣题目链接](https://leetcode.cn/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/) 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。 @@ -291,7 +291,80 @@ func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) { ``` +### PHP +```php +function reverseLeftWords($s, $n) { + $this->reverse($s,0,$n-1); //反转区间为前n的子串 + $this->reverse($s,$n,strlen($s)-1); //反转区间为n到末尾的子串 + $this->reverse($s,0,strlen($s)-1); //反转整个字符串 + return $s; +} + +// 按指定进行翻转 【array、string都可】 +function reverse(&$s, $start, $end) { + for ($i = $start, $j = $end; $i < $j; $i++, $j--) { + $tmp = $s[$i]; + $s[$i] = $s[$j]; + $s[$j] = $tmp; + } +} +``` + + +Scala: + +```scala +object Solution { + def reverseLeftWords(s: String, n: Int): String = { + var str = s.toCharArray // 转换为Array + // abcdefg => ba cdefg + reverseString(str, 0, n - 1) + // ba cdefg => ba gfedc + reverseString(str, n, str.length - 1) + // ba gfedc => cdefgab + reverseString(str, 0, str.length - 1) + // 最终返回,return关键字可以省略 + new String(str) + } + // 翻转字符串 + def reverseString(s: Array[Char], start: Int, end: Int): Unit = { + var (left, right) = (start, end) + while (left < right) { + var tmp = s(left) + s(left) = s(right) + s(right) = tmp + left += 1 + right -= 1 + } + } +} +``` + +Rust: + +```Rust +impl Solution { + pub fn reverse(s: &mut Vec, mut begin: usize, mut end: usize){ + while begin < end { + let temp = s[begin]; + s[begin] = s[end]; + s[end] = temp; + begin += 1; + end -= 1; + } + } + pub fn reverse_left_words(s: String, n: i32) -> String { + let len = s.len(); + let mut s = s.chars().collect::>(); + let n = n as usize; + Self::reverse(&mut s, 0, n - 1); + Self::reverse(&mut s, n, len - 1); + Self::reverse(&mut s, 0, len - 1); + s.iter().collect::() + } +} +``` diff --git a/problems/动态规划总结篇.md b/problems/动态规划总结篇.md index cc973b23..847baa9a 100644 --- a/problems/动态规划总结篇.md +++ b/problems/动态规划总结篇.md @@ -116,7 +116,7 @@ 能把本篇中列举的题目都研究通透的话,你的动规水平就已经非常高了。 对付面试已经足够! -![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211121223754.png) +![](https://kstar-1253855093.cos.ap-nanjing.myqcloud.com/baguwenpdf/_%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE_%E9%9D%92.png) 这个图是 [代码随想录知识星球](https://programmercarl.com/other/kstar.html) 成员:[青](https://wx.zsxq.com/dweb2/index/footprint/185251215558842),所画,总结的非常好,分享给大家。 diff --git a/problems/回溯算法去重问题的另一种写法.md b/problems/回溯算法去重问题的另一种写法.md index f48097e1..c7bf24bc 100644 --- a/problems/回溯算法去重问题的另一种写法.md +++ b/problems/回溯算法去重问题的另一种写法.md @@ -365,6 +365,84 @@ class Solution: return res ``` +JavaScript: + +**90.子集II** + +```javascript +function subsetsWithDup(nums) { + nums.sort((a, b) => a - b); + const resArr = []; + backTraking(nums, 0, []); + return resArr; + function backTraking(nums, startIndex, route) { + resArr.push([...route]); + const helperSet = new Set(); + for (let i = startIndex, length = nums.length; i < length; i++) { + if (helperSet.has(nums[i])) continue; + helperSet.add(nums[i]); + route.push(nums[i]); + backTraking(nums, i + 1, route); + route.pop(); + } + } +}; +``` + +**40. 组合总和 II** + +```javascript +function combinationSum2(candidates, target) { + candidates.sort((a, b) => a - b); + const resArr = []; + backTracking(candidates, target, 0, 0, []); + return resArr; + function backTracking( candidates, target, curSum, startIndex, route ) { + if (curSum > target) return; + if (curSum === target) { + resArr.push([...route]); + return; + } + const helperSet = new Set(); + for (let i = startIndex, length = candidates.length; i < length; i++) { + let tempVal = candidates[i]; + if (helperSet.has(tempVal)) continue; + helperSet.add(tempVal); + route.push(tempVal); + backTracking(candidates, target, curSum + tempVal, i + 1, route); + route.pop(); + } + } +}; +``` + +**47. 全排列 II** + +```javascript +function permuteUnique(nums) { + const resArr = []; + const usedArr = []; + backTracking(nums, []); + return resArr; + function backTracking(nums, route) { + if (nums.length === route.length) { + resArr.push([...route]); + return; + } + const usedSet = new Set(); + for (let i = 0, length = nums.length; i < length; i++) { + if (usedArr[i] === true || usedSet.has(nums[i])) continue; + usedSet.add(nums[i]); + route.push(nums[i]); + usedArr[i] = true; + backTracking(nums, route); + usedArr[i] = false; + route.pop(); + } + } +}; +``` + TypeScript: **90.子集II** @@ -376,7 +454,7 @@ function subsetsWithDup(nums: number[]): number[][] { backTraking(nums, 0, []); return resArr; function backTraking(nums: number[], startIndex: number, route: number[]): void { - resArr.push(route.slice()); + resArr.push([...route]); const helperSet: Set = new Set(); for (let i = startIndex, length = nums.length; i < length; i++) { if (helperSet.has(nums[i])) continue; @@ -403,7 +481,7 @@ function combinationSum2(candidates: number[], target: number): number[][] { ) { if (curSum > target) return; if (curSum === target) { - resArr.push(route.slice()); + resArr.push([...route]); return; } const helperSet: Set = new Set(); @@ -430,7 +508,7 @@ function permuteUnique(nums: number[]): number[][] { return resArr; function backTracking(nums: number[], route: number[]): void { if (nums.length === route.length) { - resArr.push(route.slice()); + resArr.push([...route]); return; } const usedSet: Set = new Set(); diff --git a/problems/数组总结篇.md b/problems/数组总结篇.md index ef7a4876..6d7603fd 100644 --- a/problems/数组总结篇.md +++ b/problems/数组总结篇.md @@ -43,19 +43,19 @@ **那么二维数组在内存的空间地址是连续的么?** -我们来举一个例子,例如: `int[][] rating = new int[3][4];` , 这个二维数据在内存空间可不是一个 `3*4` 的连续地址空间 +我们来举一个Java的例子,例如: `int[][] rating = new int[3][4];` , 这个二维数组在内存空间可不是一个 `3*4` 的连续地址空间 看了下图,就应该明白了: -所以**二维数据在内存中不是 `3*4` 的连续地址空间,而是四条连续的地址空间组成!** +所以**Java的二维数组在内存中不是 `3*4` 的连续地址空间,而是四条连续的地址空间组成!** # 数组的经典题目 在面试中,数组是必考的基础数据结构。 -其实数据的题目在思想上一般比较简单的,但是如果想高效,并不容易。 +其实数组的题目在思想上一般比较简单的,但是如果想高效,并不容易。 我们之前一共讲解了四道经典数组题目,每一道题目都代表一个类型,一种思想。 @@ -115,7 +115,7 @@ 在这道题目中,我们再一次介绍到了**循环不变量原则**,其实这也是写程序中的重要原则。 -相信大家又遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,踩了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。 +相信大家有遇到过这种情况: 感觉题目的边界调节超多,一波接着一波的判断,找边界,拆了东墙补西墙,好不容易运行通过了,代码写的十分冗余,毫无章法,其实**真正解决题目的代码都是简洁的,或者有原则性的**,大家可以在这道题目中体会到这一点。 # 总结 diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index fe940b4c..c2e93eb4 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -356,9 +356,13 @@ func test_2_wei_bag_problem1(weight, value []int, bagweight int) int { // 递推公式 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]) - } + for 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]) + } + } } return dp[len(weight)-1][bagweight] } @@ -380,48 +384,159 @@ func main() { ### javascript ```js -function testweightbagproblem (wight, value, size) { - const len = wight.length, - dp = array.from({length: len + 1}).map( - () => array(size + 1).fill(0) - ); - - for(let i = 1; i <= len; i++) { - for(let j = 0; j <= size; j++) { - if(wight[i - 1] <= j) { - dp[i][j] = math.max( - dp[i - 1][j], - value[i - 1] + dp[i - 1][j - wight[i - 1]] - ) - } else { - dp[i][j] = dp[i - 1][j]; - } +function testWeightBagProblem (weight, value, size) { + // 定义 dp 数组 + const len = weight.length, + dp = Array(len).fill().map(() => Array(size + 1).fill(0)); + + // 初始化 + for(let j = weight[0]; j <= size; j++) { + dp[0][j] = value[0]; } - } -// console.table(dp); - - return dp[len][size]; -} - -function testWeightBagProblem2 (wight, value, size) { - const len = wight.length, - dp = Array(size + 1).fill(0); - for(let i = 1; i <= len; i++) { - for(let j = size; j >= wight[i - 1]; j--) { - dp[j] = Math.max(dp[j], value[i - 1] + dp[j - wight[i - 1]]); + // weight 数组的长度len 就是物品个数 + for(let i = 1; i < len; i++) { // 遍历物品 + for(let j = 0; j <= size; j++) { // 遍历背包容量 + if(j < weight[i]) dp[i][j] = dp[i - 1][j]; + else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); + } } - } - return dp[size]; -} + console.table(dp) + + return dp[len - 1][size]; +} function test () { - console.log(testWeightBagProblem([1, 3, 4, 5], [15, 20, 30, 55], 6)); + console.log(testWeightBagProblem([1, 3, 4, 5], [15, 20, 30, 55], 6)); } test(); ``` + +### C +```c +#include +#include +#include + +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define ARR_SIZE(a) (sizeof((a)) / sizeof((a)[0])) +#define BAG_WEIGHT 4 + +void backPack(int* weights, int weightSize, int* costs, int costSize, int bagWeight) { + // 开辟dp数组 + int dp[weightSize][bagWeight + 1]; + memset(dp, 0, sizeof(int) * weightSize * (bagWeight + 1)); + + int i, j; + // 当背包容量大于物品0的重量时,将物品0放入到背包中 + for(j = weights[0]; j <= bagWeight; ++j) { + dp[0][j] = costs[0]; + } + + // 先遍历物品,再遍历重量 + for(j = 1; j <= bagWeight; ++j) { + for(i = 1; i < weightSize; ++i) { + // 如果当前背包容量小于物品重量 + if(j < weights[i]) + // 背包物品的价值等于背包不放置当前物品时的价值 + dp[i][j] = dp[i-1][j]; + // 若背包当前重量可以放置物品 + else + // 背包的价值等于放置该物品或不放置该物品的最大值 + dp[i][j] = MAX(dp[i - 1][j], dp[i - 1][j - weights[i]] + costs[i]); + } + } + + printf("%d\n", dp[weightSize - 1][bagWeight]); +} + +int main(int argc, char* argv[]) { + int weights[] = {1, 3, 4}; + int costs[] = {15, 20, 30}; + backPack(weights, ARR_SIZE(weights), costs, ARR_SIZE(costs), BAG_WEIGHT); + return 0; +} +``` + + +### TypeScript + +```typescript +function testWeightBagProblem( + weight: number[], + value: number[], + size: number +): number { + /** + * dp[i][j]: 前i个物品,背包容量为j,能获得的最大价值 + * dp[0][*]: u=weight[0],u之前为0,u之后(含u)为value[0] + * dp[*][0]: 0 + * ... + * dp[i][j]: max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i]); + */ + const goodsNum: number = weight.length; + const dp: number[][] = new Array(goodsNum) + .fill(0) + .map((_) => new Array(size + 1).fill(0)); + for (let i = weight[0]; i <= size; i++) { + dp[0][i] = value[0]; + } + for (let i = 1; i < goodsNum; i++) { + for (let j = 1; j <= size; j++) { + if (j < weight[i]) { + dp[i][j] = dp[i - 1][j]; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); + } + } + } + return dp[goodsNum - 1][size]; +} +// test +const weight = [1, 3, 4]; +const value = [15, 20, 30]; +const size = 4; +console.log(testWeightBagProblem(weight, value, size)); +``` + +### Scala + +```scala +object Solution { + // 01背包 + def test_2_wei_bag_problem1(): Unit = { + var weight = Array[Int](1, 3, 4) + var value = Array[Int](15, 20, 30) + var baseweight = 4 + + // 二维数组 + var dp = Array.ofDim[Int](weight.length, baseweight + 1) + + // 初始化 + for (j <- weight(0) to baseweight) { + dp(0)(j) = value(0) + } + + // 遍历 + for (i <- 1 until weight.length; j <- 1 to baseweight) { + if (j - weight(i) >= 0) dp(i)(j) = dp(i - 1)(j - weight(i)) + value(i) + dp(i)(j) = math.max(dp(i)(j), dp(i - 1)(j)) + } + + // 打印数组 + dp.foreach(x => println("[" + x.mkString(",") + "]")) + + dp(weight.length - 1)(baseweight) // 最终返回 + } + + def main(args: Array[String]): Unit = { + test_2_wei_bag_problem1() + } +} +``` + -----------------------
diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index dabdfb2d..81e61be4 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -137,6 +137,8 @@ dp[1] = dp[1 - weight[0]] + value[0] = 15 因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。 +倒序遍历的原因是,本质上还是一个对二维数组的遍历,并且右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。 + (这里如果读不懂,就在回想一下dp[j]的定义,或者就把两个for循环顺序颠倒一下试试!) **所以一维dp数组的背包在遍历顺序上和二维其实是有很大差异的!**,这一点大家一定要注意。 @@ -315,7 +317,91 @@ function test () { test(); ``` +### C +```c +#include +#include +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define ARR_SIZE(arr) ((sizeof((arr))) / sizeof((arr)[0])) +#define BAG_WEIGHT 4 + +void test_back_pack(int* weights, int weightSize, int* values, int valueSize, int bagWeight) { + int dp[bagWeight + 1]; + memset(dp, 0, sizeof(int) * (bagWeight + 1)); + + int i, j; + // 先遍历物品 + for(i = 0; i < weightSize; ++i) { + // 后遍历重量。从后向前遍历 + for(j = bagWeight; j >= weights[i]; --j) { + dp[j] = MAX(dp[j], dp[j - weights[i]] + values[i]); + } + } + + // 打印最优结果 + printf("%d\n", dp[bagWeight]); +} + +int main(int argc, char** argv) { + int weights[] = {1, 3, 4}; + int values[] = {15, 20, 30}; + test_back_pack(weights, ARR_SIZE(weights), values, ARR_SIZE(values), BAG_WEIGHT); + return 0; +} +``` + +### TypeScript + +```typescript +function testWeightBagProblem( + weight: number[], + value: number[], + size: number +): number { + const goodsNum: number = weight.length; + const dp: number[] = new Array(size + 1).fill(0); + for (let i = 0; i < goodsNum; i++) { + for (let j = size; j >= weight[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); + } + } + return dp[size]; +} +const weight = [1, 3, 4]; +const value = [15, 20, 30]; +const size = 4; +console.log(testWeightBagProblem(weight, value, size)); + +``` + +### Scala + +```scala +object Solution { + // 滚动数组 + def test_1_wei_bag_problem(): Unit = { + var weight = Array[Int](1, 3, 4) + var value = Array[Int](15, 20, 30) + var baseweight = 4 + + // dp数组 + var dp = new Array[Int](baseweight + 1) + + // 遍历 + for (i <- 0 until weight.length; j <- baseweight to weight(i) by -1) { + dp(j) = math.max(dp(j), dp(j - weight(i)) + value(i)) + } + + // 打印数组 + println("[" + dp.mkString(",") + "]") + } + + def main(args: Array[String]): Unit = { + test_1_wei_bag_problem() + } +} +``` -----------------------
diff --git a/problems/背包问题理论基础多重背包.md b/problems/背包问题理论基础多重背包.md index a988db2c..712380f4 100644 --- a/problems/背包问题理论基础多重背包.md +++ b/problems/背包问题理论基础多重背包.md @@ -334,6 +334,64 @@ func Test_multiplePack(t *testing.T) { PASS ``` +TypeScript: + +> 版本一(改变数据源): + +```typescript +function testMultiPack() { + const bagSize: number = 10; + const weightArr: number[] = [1, 3, 4], + valueArr: number[] = [15, 20, 30], + amountArr: number[] = [2, 3, 2]; + for (let i = 0, length = amountArr.length; i < length; i++) { + while (amountArr[i] > 1) { + weightArr.push(weightArr[i]); + valueArr.push(valueArr[i]); + amountArr[i]--; + } + } + const goodsNum: number = weightArr.length; + const dp: number[] = new Array(bagSize + 1).fill(0); + // 遍历物品 + for (let i = 0; i < goodsNum; i++) { + // 遍历背包容量 + for (let j = bagSize; j >= weightArr[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - weightArr[i]] + valueArr[i]); + } + } + console.log(dp); +} +testMultiPack(); +``` + +> 版本二(改变遍历方式): + +```typescript +function testMultiPack() { + const bagSize: number = 10; + const weightArr: number[] = [1, 3, 4], + valueArr: number[] = [15, 20, 30], + amountArr: number[] = [2, 3, 2]; + const goodsNum: number = weightArr.length; + const dp: number[] = new Array(bagSize + 1).fill(0); + // 遍历物品 + for (let i = 0; i < goodsNum; i++) { + // 遍历物品个数 + for (let j = 0; j < amountArr[i]; j++) { + // 遍历背包容量 + for (let k = bagSize; k >= weightArr[i]; k--) { + dp[k] = Math.max(dp[k], dp[k - weightArr[i]] + valueArr[i]); + } + } + } + console.log(dp); +} +testMultiPack(); +``` + + + -----------------------
diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index 3ec399f1..54e772e0 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -340,6 +340,27 @@ function test_completePack2() { } ``` +TypeScript: + +```typescript +// 先遍历物品,再遍历背包容量 +function test_CompletePack(): void { + const weight: number[] = [1, 3, 4]; + const value: number[] = [15, 20, 30]; + const bagSize: number = 4; + const dp: number[] = new Array(bagSize + 1).fill(0); + for (let i = 0; i < weight.length; i++) { + for (let j = weight[i]; j <= bagSize; j++) { + dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]); + } + } + console.log(dp); +} +test_CompletePack(); +``` + + + -----------------------
diff --git a/problems/链表理论基础.md b/problems/链表理论基础.md index 095282f5..1a29c32a 100644 --- a/problems/链表理论基础.md +++ b/problems/链表理论基础.md @@ -24,7 +24,7 @@ ## 双链表 -单链表中的节点只能指向节点的下一个节点。 +单链表中的指针域只能指向节点的下一个节点。 双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。 @@ -56,7 +56,7 @@ ![链表3](https://img-blog.csdnimg.cn/20200806194613920.png) -这个链表起始节点为2, 终止节点为7, 各个节点分布在内存个不同地址空间上,通过指针串联在一起。 +这个链表起始节点为2, 终止节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在一起。 # 链表的定义 @@ -210,6 +210,13 @@ type ListNode struct { } ``` +Scala: +```scala +class ListNode(_x: Int = 0, _next: ListNode = null) { + var next: ListNode = _next + var x: Int = _x +} +``` ----------------------- diff --git a/problems/面试题 02.07. 解法更新.md b/problems/面试题 02.07. 解法更新.md deleted file mode 100644 index 6115d02e..00000000 --- a/problems/面试题 02.07. 解法更新.md +++ /dev/null @@ -1,41 +0,0 @@ -# 双指针,不计算链表长度 -设置指向headA和headB的指针pa、pb,分别遍历两个链表,每次循环同时更新pa和pb。 -* 当链表A遍历完之后,即pa为空时,将pa指向headB; -* 当链表B遍历完之后,即pa为空时,将pb指向headA; -* 当pa与pb相等时,即指向同一个节点,该节点即为相交起始节点。 -* 若链表不相交,则pa、pb同时为空时退出循环,即如果链表不相交,pa与pb在遍历过全部节点后同时指向结尾空节点,此时退出循环,返回空。 -# 证明思路 -设链表A不相交部分长度为a,链表B不相交部分长度为b,两个链表相交部分长度为c。
-在pa指向链表A时,即pa为空之前,pa经过链表A不相交部分和相交部分,走过的长度为a+c;
-pa指向链表B后,在移动相交节点之前经过链表B不相交部分,走过的长度为b,总合为a+c+b。
-同理,pb走过长度的总合为b+c+a。二者相等,即pa与pb可同时到达相交起始节点。
-该方法可避免计算具体链表长度。 -```cpp -class Solution { -public: - ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { - //链表为空时,返回空指针 - if(headA == nullptr || headB == nullptr) return nullptr; - ListNode* pa = headA; - ListNode* pb = headB; - //pa与pb在遍历过全部节点后,同时指向结尾空节点时退出循环 - while(pa != nullptr || pb != nullptr){ - //pa为空时,将pa指向headB - if(pa == nullptr){ - pa = headB; - } - //pa为空时,将pb指向headA - if(pb == nullptr){ - pb = headA; - } - //pa与pb相等时,返回相交起始节点 - if(pa == pb){ - return pa; - } - pa = pa->next; - pb = pb->next; - } - return nullptr; - } -}; -``` diff --git a/problems/面试题02.07.链表相交.md b/problems/面试题02.07.链表相交.md index 2e7226de..abf7a2f8 100644 --- a/problems/面试题02.07.链表相交.md +++ b/problems/面试题02.07.链表相交.md @@ -7,27 +7,27 @@ # 面试题 02.07. 链表相交 -[力扣题目链接](https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/) +[力扣题目链接](https://leetcode.cn/problems/intersection-of-two-linked-lists-lcci/) 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。 图示两个链表在节点 c1 开始相交: -![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221657.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221657.png) 题目数据 保证 整个链式结构中不存在环。 -注意,函数返回结果后,链表必须 保持其原始结构 。 +注意,函数返回结果后,链表必须 保持其原始结构 。 -示例 1: +示例 1: -![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221723.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221723.png) 示例 2: -![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221749.png) +![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221749.png) -示例 3: +示例 3: ![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221812.png)![](https://code-thinking-1253855093.file.myqcloud.com/pics/20211219221812.png) @@ -94,13 +94,13 @@ public: }; ``` -* 时间复杂度:$O(n + m)$ -* 空间复杂度:$O(1)$ +* 时间复杂度:O(n + m) +* 空间复杂度:O(1) ## 其他语言版本 -### Java +### Java ```Java public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { @@ -144,11 +144,11 @@ public class Solution { } return null; } - + } ``` -### Python +### Python ```python class Solution: @@ -160,17 +160,19 @@ class Solution: 那么,只要其中一个链表走完了,就去走另一条链表的路。如果有交点,他们最终一定会在同一个 位置相遇 """ + if headA is None or headB is None: + return None cur_a, cur_b = headA, headB # 用两个指针代替a和b - + while cur_a != cur_b: cur_a = cur_a.next if cur_a else headB # 如果a走完了,那么就切换到b走 cur_b = cur_b.next if cur_b else headA # 同理,b走完了就切换到a - + return cur_a ``` -### Go +### Go ```go func getIntersectionNode(headA, headB *ListNode) *ListNode { @@ -208,7 +210,30 @@ func getIntersectionNode(headA, headB *ListNode) *ListNode { } ``` -### javaScript +双指针 + +```go +func getIntersectionNode(headA, headB *ListNode) *ListNode { + l1,l2 := headA, headB + for l1 != l2 { + if l1 != nil { + l1 = l1.Next + } else { + l1 = headB + } + + if l2 != nil { + l2 = l2.Next + } else { + l2 = headA + } + } + + return l1 +} +``` + +### javaScript ```js var getListLen = function(head) { @@ -218,9 +243,9 @@ var getListLen = function(head) { cur = cur.next; } return len; -} +} var getIntersectionNode = function(headA, headB) { - let curA = headA,curB = headB, + let curA = headA,curB = headB, lenA = getListLen(headA), lenB = getListLen(headB); if(lenA < lenB) { @@ -317,7 +342,55 @@ ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { } ``` - +Scala: +```scala +object Solution { + def getIntersectionNode(headA: ListNode, headB: ListNode): ListNode = { + var lenA = 0 // headA链表的长度 + var lenB = 0 // headB链表的长度 + var tmp = headA // 临时变量 + // 统计headA的长度 + while (tmp != null) { + lenA += 1; + tmp = tmp.next + } + // 统计headB的长度 + tmp = headB // 临时变量赋值给headB + while (tmp != null) { + lenB += 1 + tmp = tmp.next + } + // 因为传递过来的参数是不可变量,所以需要重新定义 + var listA = headA + var listB = headB + // 两个链表的长度差 + // 如果gap>0,lenA>lenB,headA(listA)链表往前移动gap步 + // 如果gap<0,lenA 0) { + // 因为不可以i-=1,所以可以使用for + for (i <- 0 until gap) { + listA = listA.next // 链表headA(listA) 移动 + } + } else { + gap = math.abs(gap) // 此刻gap为负值,取绝对值 + for (i <- 0 until gap) { + listB = listB.next + } + } + // 现在两个链表同时往前走,如果相等则返回 + while (listA != null && listB != null) { + if (listA == listB) { + return listA + } + listA = listA.next + listB = listB.next + } + // 如果链表没有相交则返回null,return可以省略 + null + } +} +``` -----------------------