diff --git a/README.md b/README.md index 4e2993d8..620942dc 100644 --- a/README.md +++ b/README.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 6969c2e2..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,6 @@ 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 @@ -317,6 +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 1764d244..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还是一个数一个数的减下去的,所以在哪里减的都是一样的。 + +所以这种去重 是可以不加的。 仅仅是 把去重的逻辑提前了而已。 + + # 思考题 @@ -345,6 +411,76 @@ var threeSum = function(nums) { 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) { + // 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; + } + + 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; + } + nums.sort((a, b) => a - b); + // n = 3,此时求3sum之和 + return nSumTarget(nums, 3, 0, 0); +}; +``` + TypeScript: ```typescript @@ -484,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函数 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 6cbd40c2..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) { @@ -522,6 +522,51 @@ 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 { diff --git a/problems/0019.删除链表的倒数第N个节点.md b/problems/0019.删除链表的倒数第N个节点.md index c36900bc..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 个结点,并且返回链表的头结点。 @@ -289,6 +289,30 @@ func removeNthFromEnd(_ head: ListNode?, _ n: Int) -> ListNode? { return dummyHead.next } ``` + + +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 { diff --git a/problems/0020.有效的括号.md b/problems/0020.有效的括号.md index a0df0d07..c3ff9d53 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/) 给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 @@ -400,6 +400,37 @@ bool isValid(char * s){ return !stackTop; } ``` + + +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 { @@ -422,5 +453,6 @@ object Solution { } } ``` + -----------------------
diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md index 2289c229..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: diff --git a/problems/0027.移除元素.md b/problems/0027.移除元素.md index f7142bad..fd24e8f8 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 的元素,并返回移除后数组的新长度。 @@ -219,6 +219,7 @@ func removeElement(nums []int, val int) int { res++ } } + nums=nums[:res] return res } ``` @@ -339,7 +340,6 @@ int removeElement(int* nums, int numsSize, int val){ } ``` - Kotlin: ```kotlin fun removeElement(nums: IntArray, `val`: Int): Int { @@ -351,7 +351,6 @@ fun removeElement(nums: IntArray, `val`: Int): Int { } ``` - Scala: ```scala object Solution { @@ -368,5 +367,20 @@ object Solution { } ``` +C#: +```csharp +public class Solution { + public int RemoveElement(int[] nums, int val) { + int slow = 0; + for (int fast = 0; fast < nums.Length; fast++) { + if (val != nums[fast]) { + nums[slow++] = nums[fast]; + } + } + return slow; + } +} +``` + -----------------------
diff --git a/problems/0028.实现strStr.md b/problems/0028.实现strStr.md index 1cdd5292..1887e91b 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() 函数。 @@ -1241,5 +1241,49 @@ function getNext(&$next, $s){ } } ``` + +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 { @@ -529,5 +586,49 @@ object Solution { } ``` + +### 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 5cf44ded..77def4f8 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/) 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 @@ -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 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 e10a827f..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() # 回溯 ``` @@ -502,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 34ac64e6..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 的组合。 @@ -693,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 4e3ab24a..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/) 给定一个非负整数数组,你最初位于数组的第一个位置。 @@ -279,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 73cac244..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 ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 @@ -333,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..0d79fdf9 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: 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 e444a221..a8f36fdf 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] ## 思路 @@ -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 diff --git a/problems/0059.螺旋矩阵II.md b/problems/0059.螺旋矩阵II.md index 22229302..bf0a279e 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,6 +24,8 @@ ## 思路 +为了利于录友们理解,我特意录制了视频,[拿下螺旋矩阵,《代码随想录》第五题!](https://www.bilibili.com/video/BV1SL4y1N7mV),结合本篇文章一起看,效果更佳。 + 这道题目可以说在面试中出现频率较高的题目,**本题并不涉及到什么算法,就是模拟过程,但却十分考察对代码的掌控能力。** 要如何画出这个螺旋排列的正方形矩阵呢? @@ -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为奇数的话,需要单独给矩阵最中间的位置赋值 diff --git a/problems/0062.不同路径.md b/problems/0062.不同路径.md index f59b7be8..aa7c8ab8 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” )。 @@ -374,6 +374,30 @@ function uniquePaths(m: number, n: number): number { }; ``` +### 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 diff --git a/problems/0063.不同路径II.md b/problems/0063.不同路径II.md index 59c60156..e3857db6 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” )。 @@ -384,6 +384,42 @@ function uniquePathsWithObstacles(obstacleGrid: number[][]): number { }; ``` +### 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 diff --git a/problems/0070.爬楼梯.md b/problems/0070.爬楼梯.md index 34d41441..c92c581c 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 阶你才能到达楼顶。 diff --git a/problems/0070.爬楼梯完全背包版本.md b/problems/0070.爬楼梯完全背包版本.md index 0f482bb7..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 阶你才能到达楼顶。 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 9e0398ab..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 个数的组合。 @@ -535,6 +535,56 @@ func backtrack(n,k,start int,track []int){ } ``` +### 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; @@ -673,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 25561b50..9d98c62d 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 为节点组成的二叉搜索树有多少种? 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 1eb43589..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/) 给定一个二叉树,检查它是否是镜像对称的。 @@ -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 3aa17699..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/) 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 @@ -379,7 +379,7 @@ pub fn level_order(root: Option>>) -> Vec> { # 107.二叉树的层次遍历 II -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-level-order-traversal-ii/) 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历) @@ -660,7 +660,7 @@ pub fn level_order(root: Option>>) -> Vec> { # 199.二叉树的右视图 -[力扣题目链接](https://leetcode-cn.com/problems/binary-tree-right-side-view/) +[力扣题目链接](https://leetcode.cn/problems/binary-tree-right-side-view/) 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。 @@ -907,7 +907,7 @@ object Solution { # 637.二叉树的层平均值 -[力扣题目链接](https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/average-of-levels-in-binary-tree/) 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。 @@ -1163,7 +1163,7 @@ object Solution { # 429.N叉树的层序遍历 -[力扣题目链接](https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/) +[力扣题目链接](https://leetcode.cn/problems/n-ary-tree-level-order-traversal/) 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。 @@ -1434,7 +1434,7 @@ object Solution { # 515.在每个树行中找最大值 -[力扣题目链接](https://leetcode-cn.com/problems/find-largest-value-in-each-tree-row/) +[力扣题目链接](https://leetcode.cn/problems/find-largest-value-in-each-tree-row/) 您需要在二叉树的每一行中找到最大的值。 @@ -1668,7 +1668,7 @@ object Solution { # 116.填充每个节点的下一个右侧节点指针 -[力扣题目链接](https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/) +[力扣题目链接](https://leetcode.cn/problems/populating-next-right-pointers-in-each-node/) 给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: @@ -1956,7 +1956,7 @@ object Solution { ``` # 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/) 思路: @@ -2236,7 +2236,7 @@ object Solution { ``` # 104.二叉树的最大深度 -[力扣题目链接](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/maximum-depth-of-binary-tree/) 给定一个二叉树,找出其最大深度。 @@ -2477,7 +2477,7 @@ object Solution { # 111.二叉树的最小深度 -[力扣题目链接](https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/) +[力扣题目链接](https://leetcode.cn/problems/minimum-depth-of-binary-tree/) 相对于 104.二叉树的最大深度 ,本题还也可以使用层序遍历的方式来解决,思路是一样的。 @@ -2629,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 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); } } } @@ -495,7 +494,7 @@ class solution: ## go - +### 104.二叉树的最大深度 ```go /** * definition for a binary tree node. @@ -548,6 +547,8 @@ func maxdepth(root *treenode) int { ## javascript +### 104.二叉树的最大深度 + ```javascript var maxdepth = function(root) { if (root === null) return 0; @@ -595,6 +596,8 @@ var maxDepth = function(root) { }; ``` +### 559.n叉树的最大深度 + N叉树的最大深度 递归写法 ```js var maxDepth = function(root) { @@ -627,9 +630,9 @@ var maxDepth = function(root) { }; ``` -## TypeScript: +## TypeScript -> 二叉树的最大深度: +### 104.二叉树的最大深度 ```typescript // 后续遍历(自下而上) @@ -672,7 +675,7 @@ function maxDepth(root: TreeNode | null): number { }; ``` -> N叉树的最大深度 +### 559.n叉树的最大深度 ```typescript // 后续遍历(自下而上) @@ -702,6 +705,8 @@ function maxDepth(root: TreeNode | null): number { ## C +### 104.二叉树的最大深度 + 二叉树最大深度递归 ```c int maxDepth(struct TreeNode* root){ @@ -758,7 +763,8 @@ int maxDepth(struct TreeNode* root){ ## Swift ->二叉树最大深度 +### 104.二叉树的最大深度 + ```swift // 递归 - 后序 func maxDepth1(_ root: TreeNode?) -> Int { @@ -797,7 +803,8 @@ func maxDepth(_ root: TreeNode?) -> Int { } ``` ->N叉树最大深度 +### 559.n叉树的最大深度 + ```swift // 递归 func maxDepth(_ root: Node?) -> Int { @@ -833,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 b7598365..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/) 给定一个二叉树,判断它是否是高度平衡的二叉树。 @@ -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 b1331659..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,6 +488,46 @@ 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 { @@ -550,6 +590,7 @@ impl Solution { } min_depth } + ``` ----------------------- diff --git a/problems/0112.路径总和.md b/problems/0112.路径总和.md index d3eec16b..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,7 +373,7 @@ class solution { ``` -0113.路径总和-ii +### 0113.路径总和-ii ```java class solution { @@ -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 **递归** @@ -1126,7 +1126,90 @@ int** pathSum(struct TreeNode* root, int targetSum, int* returnSize, int** retur } ``` +## 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 a2498bb6..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 天的价格。 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 12b21fde..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 天的价格。 diff --git a/problems/0123.买卖股票的最佳时机III.md b/problems/0123.买卖股票的最佳时机III.md index 67c99497..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 天的价格。 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 7a702898..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(); } @@ -676,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 a88f677d..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] 升。 @@ -471,5 +471,73 @@ int canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize){ } ``` +### 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 3456a04c..55c2efc7 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 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。 diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md index 5b4e92b9..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 是否可以被空格拆分为一个或多个在字典中出现的单词。 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 f8e62d45..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; } 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 5452c304..1a90265a 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/) 根据 逆波兰表示法,求表达式的值。 @@ -325,6 +325,33 @@ func evalRPN(_ tokens: [String]) -> Int { return stack.last! } ``` + + +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 { @@ -351,6 +378,7 @@ object Solution { // 最后返回栈顶,不需要加return关键字 stack.pop() } + } ``` ----------------------- diff --git a/problems/0151.翻转字符串里的单词.md b/problems/0151.翻转字符串里的单词.md index 0e25fc4d..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说明不是第一个单词,需要在单词前添加空格。 @@ -817,7 +817,105 @@ object Solution { ``` +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 27eb38c3..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 天的价格。 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 a828b9a9..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/) 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。 diff --git a/problems/0202.快乐数.md b/problems/0202.快乐数.md index be8686f7..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,53 +368,6 @@ 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; - - } - } - - return bHappy; -} -``` Scala: ```scala @@ -417,6 +400,7 @@ object Solution { } sum } +``` C#: diff --git a/problems/0203.移除链表元素.md b/problems/0203.移除链表元素.md index fe78ddab..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) @@ -395,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; }; ``` @@ -485,17 +487,19 @@ 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 } } ``` @@ -530,5 +534,39 @@ object Solution { } } ``` +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 25b16907..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 @@ -496,8 +533,26 @@ struct ListNode* reverseList(struct ListNode* head){ return reverse(NULL, head); } ``` -Scala: + + +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 { @@ -529,6 +584,7 @@ object Solution { cur.next = pre reverse(cur, tmp) // 此时cur成为前一个节点,tmp是当前节点 } + } ``` ----------------------- diff --git a/problems/0209.长度最小的子数组.md b/problems/0209.长度最小的子数组.md index fbef7692..c75df661 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,8 +124,8 @@ public: }; ``` -时间复杂度:O(n) -空间复杂度:O(1) +* 时间复杂度:O(n) +* 空间复杂度:O(1) **一些录友会疑惑为什么时间复杂度是O(n)**。 @@ -116,8 +133,8 @@ public: ## 相关题目推荐 -* [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/) @@ -400,6 +417,38 @@ 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: 滑动窗口: @@ -448,6 +497,27 @@ object Solution { } } ``` - +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 9e698d01..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/) 你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。 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 3c134870..40415d8d 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/) 使用队列实现栈的下列操作: @@ -816,7 +816,6 @@ class MyStack { } ``` Scala: - 使用两个队列模拟栈: ```scala import scala.collection.mutable @@ -897,6 +896,86 @@ class MyStack() { def empty(): Boolean = { queue.isEmpty } + } +``` + + +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 af5b8043..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/) 翻转一棵二叉树。 @@ -470,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; }; ``` @@ -818,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 d9ba8e26..4662e2f2 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/) 使用栈实现队列的下列操作: @@ -495,6 +495,53 @@ void myQueueFree(MyQueue* obj) { obj->stackOutTop = 0; } ``` + + +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() { @@ -533,6 +580,7 @@ class MyQueue() { def empty(): Boolean = { stackIn.isEmpty && stackOut.isEmpty } + } ``` ----------------------- diff --git a/problems/0234.回文链表.md b/problems/0234.回文链表.md index db910d4e..eeee6fa5 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/) 请判断一个链表是否为回文链表。 @@ -273,7 +273,7 @@ class Solution: return pre ``` -## Go +### Go ```go @@ -319,6 +319,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 eb32fdd2..7ee1fdb1 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 个数字。滑动窗口每次只向右移动一位。 @@ -654,8 +654,7 @@ object Solution { // 最终返回res,return关键字可以省略 res } - -} + } class MyQueue { var queue = ArrayBuffer[Int]() @@ -678,5 +677,84 @@ class MyQueue { 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"; + } +} +``` + -----------------------
diff --git a/problems/0242.有效的字母异位词.md b/problems/0242.有效的字母异位词.md index 8fd9c604..0437330b 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)。 暴力的方法这里就不做介绍了,直接看一下有没有更优的方式。 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 5b15639c..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。你需要让组成和的完全平方数的个数最少。 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 ffa66c02..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 ,找到其中最长严格递增子序列的长度。 diff --git a/problems/0309.最佳买卖股票时机含冷冻期.md b/problems/0309.最佳买卖股票时机含冷冻期.md index f037fe85..229fc636 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 天的股票价格 。 diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index fc0490c8..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。 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 6f50723d..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/) 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。 diff --git a/problems/0343.整数拆分.md b/problems/0343.整数拆分.md index 279f1d71..dd03937f 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,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。 diff --git a/problems/0344.反转字符串.md b/problems/0344.反转字符串.md index e1f27bd7..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){ @@ -266,6 +282,38 @@ 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 { diff --git a/problems/0347.前K个高频元素.md b/problems/0347.前K个高频元素.md index 20932b28..0c978b2a 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(); diff --git a/problems/0349.两个数组的交集.md b/problems/0349.两个数组的交集.md index f7dab3d7..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(); } } ``` @@ -356,6 +384,8 @@ object Solution { } } +``` + C#: ```csharp 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 1d808a3a..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/) 难度:中等 diff --git a/problems/0383.赎金信.md b/problems/0383.赎金信.md index 75dafb72..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。 @@ -425,6 +425,7 @@ object Solution { true } } +``` C#: 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 4ef68d3b..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/) 计算给定二叉树的所有左叶子之和。 @@ -516,6 +516,44 @@ int sumOfLeftLeaves(struct TreeNode* root){ } ``` +## 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 641086a9..879820ea 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 的人。 diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index eb6601e1..5419fc1f 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/) 题目难易:中等 @@ -417,6 +417,26 @@ var canPartition = function(nums) { ``` +TypeScript: + +```ts +function canPartition(nums: number[]): boolean { + const sum: number = nums.reduce((a: number, b: number): number => a + b); + if (sum % 2 === 1) return false; + const target: number = sum / 2; + // dp[j]表示容量(总数和)为j的背包所能装下的数(下标[0, i]之间任意取)的总和(<= 容量)的最大值 + const dp: number[] = new Array(target + 1).fill(0); + const n: number = nums.length; + for (let i: number = 0; i < n; i++) { + for (let j: number = target; j >= nums[i]; j--) { + dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]); + } + } + return dp[target] === target; +}; +``` + + C: 二维dp: ```c @@ -575,6 +595,5 @@ function canPartition(nums: number[]): boolean { - -----------------------
diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index 66aa1244..f1e259ae 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/) 给定一个区间的集合,找到需要移除区间的最小数量,使剩余区间互不重叠。 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 d4bbe961..5b75e64e 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/) 在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。 @@ -136,17 +136,28 @@ 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]; + //如果下一个气球的左边界大于最小右边界 + 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; diff --git a/problems/0454.四数相加II.md b/problems/0454.四数相加II.md index bfdee26e..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 @@ -354,6 +359,7 @@ object Solution { res } } +``` C#: ```csharp diff --git a/problems/0455.分发饼干.md b/problems/0455.分发饼干.md index 17db4a85..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/) 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 @@ -296,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..4f45f4d7 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。 @@ -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 d38ce03f..a5018baf 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 。 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 8ce1f6f1..cc8a08e3 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++) { @@ -351,22 +352,26 @@ const findTargetSumWays = (nums, target) => { }; ``` -TypeScript: -```typescript +TypeScript: + +```ts function findTargetSumWays(nums: number[], target: number): number { - const sum: number = nums.reduce((pre, cur) => pre + cur); - if (Math.abs(target) > sum) return 0; - if ((target + sum) % 2 === 1) return 0; - const bagSize: number = (target + sum) / 2; - const dp: number[] = new Array(bagSize + 1).fill(0); - dp[0] = 1; - for (let i = 0; i < nums.length; i++) { - for (let j = bagSize; j >= nums[i]; j--) { + // 把数组分成两个组合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[bagSize]; + return dp[left]; }; ``` diff --git a/problems/0496.下一个更大元素I.md b/problems/0496.下一个更大元素I.md index 02339677..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 的子集。 @@ -332,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 1d17784d..785d0125 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,6 +245,23 @@ 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 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 63120b14..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 。 @@ -235,6 +235,35 @@ const longestPalindromeSubseq = (s) => { }; ``` +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 b6593438..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/) 难度:中等 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 99d6ebe0..7ef6463e 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,10 +53,10 @@ 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; } @@ -389,5 +389,36 @@ object Solution { } } ``` + +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..f1b3be11 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(夜魇) 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 b1ed80c3..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/) 给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。 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 50e39ade..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)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。 @@ -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 50f01226..98b55505 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,7 +36,7 @@ ## 思路 -为了易于大家理解,我还录制了视频,可以看这里:[手把手带你撕出正确的二分法](https://www.bilibili.com/video/BV1fA4y1o715) +为了易于大家理解,我还录制了视频,可以看这里:[B站:手把手带你撕出正确的二分法](https://www.bilibili.com/video/BV1fA4y1o715) **这道题目的前提是数组为有序数组**,同时题目还强调**数组中无重复元素**,因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的,这些都是使用二分法的前提条件,当大家看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了。 @@ -613,6 +613,36 @@ 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:** diff --git a/problems/0707.设计链表.md b/problems/0707.设计链表.md index dcdb53f4..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); diff --git a/problems/0714.买卖股票的最佳时机含手续费.md b/problems/0714.买卖股票的最佳时机含手续费.md index b27631c6..7600c52c 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 代表了交易股票的手续费用。 diff --git a/problems/0714.买卖股票的最佳时机含手续费(动态规划).md b/problems/0714.买卖股票的最佳时机含手续费(动态规划).md index 5625604b..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 代表了交易股票的手续费用。 diff --git a/problems/0718.最长重复子数组.md b/problems/0718.最长重复子数组.md index 0b7b5199..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数组吧。 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 4e4079a7..d2f041f5 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 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。 diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index 987ce27e..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 来代替。 @@ -372,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 5931fc8a..0006f7ac 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 开始)。 @@ -288,6 +288,24 @@ function minCostClimbingStairs(cost: number[]): number { }; ``` +### 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 diff --git a/problems/0763.划分字母区间.md b/problems/0763.划分字母区间.md index 2210cffa..eb21e42f 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 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 aa09e1c6..f5785a91 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 美元。 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..58fe99b7 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 次或多次。 @@ -209,6 +209,31 @@ var isLongPressedName = function(name, typed) { }; ``` +### TypeScript + +```typescript +function isLongPressedName(name: string, typed: string): boolean { + const nameLength: number = name.length, + typeLength: number = typed.length; + let i: number = 0, + j: number = 0; + while (i < nameLength && j < typeLength) { + if (name[i] !== typed[j]) return false; + i++; + j++; + if (i === nameLength || name[i] !== name[i - 1]) { + // 跳过typed中的连续相同字符 + while (j < typeLength && typed[j] === typed[j - 1]) { + j++; + } + } + } + return i === nameLength && j === typeLength; +}; +``` + + + -----------------------
diff --git a/problems/0941.有效的山脉数组.md b/problems/0941.有效的山脉数组.md index 4b7a978c..310dd35a 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,6 +157,26 @@ 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; +}; +``` + diff --git a/problems/0968.监控二叉树.md b/problems/0968.监控二叉树.md index 9a510a1b..0aa04a02 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/) 给定一个二叉树,我们在树的节点上安装摄像头。 diff --git a/problems/0977.有序数组的平方.md b/problems/0977.有序数组的平方.md index 20bdf7b0..a9ee232a 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,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。 @@ -106,6 +106,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 { @@ -362,6 +363,8 @@ class Solution { ``` Kotlin: + +双指针法 ```kotlin class Solution { // 双指针法 @@ -383,6 +386,32 @@ class Solution { } } ``` +骚操作(暴力思路) +```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: @@ -420,6 +449,24 @@ object Solution { } ``` - +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 075b5ef1..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 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。 diff --git a/problems/1005.K次取反后最大化的数组和.md b/problems/1005.K次取反后最大化的数组和.md index 202534da..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。) @@ -289,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 279ed816..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 中的整数。 @@ -183,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 a92a3911..9691c75b 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,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 @@ -374,6 +374,34 @@ func removeDuplicates(_ s: String) -> String { return String(stack) } ``` + + +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 { @@ -396,5 +424,6 @@ object Solution { } } ``` + -----------------------
diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md index 3d256c3d..46777642 100644 --- a/problems/1049.最后一块石头的重量II.md +++ b/problems/1049.最后一块石头的重量II.md @@ -7,7 +7,7 @@ ## 1049. 最后一块石头的重量 II -[力扣题目链接](https://leetcode-cn.com/problems/last-stone-weight-ii/) +[力扣题目链接](https://leetcode.cn/problems/last-stone-weight-ii/) 题目难度:中等 @@ -277,26 +277,60 @@ var lastStoneWeightII = function (stones) { }; ``` -TypeScript: +C版本 +```c +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -```typescript +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((pre, cur) => pre + cur); - const bagSize: number = Math.floor(sum / 2); - const weightArr: number[] = stones; - const valueArr: number[] = stones; - 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]); + 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[bagSize] * 2; + return sum - dp[target] - dp[target]; }; ``` + + + -----------------------
diff --git a/problems/1143.最长公共子序列.md b/problems/1143.最长公共子序列.md index d58330ec..4f4cb32a 100644 --- a/problems/1143.最长公共子序列.md +++ b/problems/1143.最长公共子序列.md @@ -6,7 +6,7 @@ ## 1143.最长公共子序列 -[力扣题目链接](https://leetcode-cn.com/problems/longest-common-subsequence/) +[力扣题目链接](https://leetcode.cn/problems/longest-common-subsequence/) 给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。 @@ -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: 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..08d4fee7 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' 字符的数量是相同的。 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/server.md b/problems/qita/server.md index 16995d70..d2e76d36 100644 --- a/problems/qita/server.md +++ b/problems/qita/server.md @@ -1,6 +1,9 @@ # 一台服务器有什么用! +* [阿里云活动期间服务器购买](https://www.aliyun.com/minisite/goods?taskCode=shareNew2205&recordId=3641992&userCode=roof0wob) +* [腾讯云活动期间服务器购买](https://curl.qcloud.com/EiaMXllu) + 但在组织这场活动的时候,了解到大家都有一个共同的问题: **这个服务器究竟有啥用??** 这真是一个好问题,而且我一句两句还说不清楚,所以就专门发文来讲一讲。 @@ -105,7 +108,7 @@ https://github.com/youngyangyang04/fileHttpServer 如果你有一个服务器,那就是独立的一台电脑,你怎么霍霍就怎么霍霍,而且一年都不用关机的,可以一直跑你的任务,和你本地电脑也完全隔离。 -更方便的是,你目前系统假如是centos,想做一个实验需要在unbantu上,如果是云服务器,更换系统就是在 后台点一下,一键重装,云厂商基本都是支持所有系统一件安装的。 +更方便的是,你目前系统假如是CentOS,想做一个实验需要在Ubuntu上,如果是云服务器,更换系统就是在 后台点一下,一键重装,云厂商基本都是支持所有系统一件安装的。 我们平时自己玩linux经常是配各种环境,然后这个linux就被自己玩坏了(一般都是毫无节制使用root权限导致的),总之就是环境配不起来了,基本就要重装了。 diff --git a/problems/二叉树的迭代遍历.md b/problems/二叉树的迭代遍历.md index fac30f99..dc8e812c 100644 --- a/problems/二叉树的迭代遍历.md +++ b/problems/二叉树的迭代遍历.md @@ -11,9 +11,9 @@ 看完本篇大家可以使用迭代法,再重新解决如下三道leetcode上的题目: -* [144.二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) -* [94.二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) -* [145.二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) +* [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/) 为什么可以用迭代法(非递归的方式)来实现二叉树的前后中序遍历呢? diff --git a/problems/二叉树的递归遍历.md b/problems/二叉树的递归遍历.md index 29c0cfda..1cce2a0d 100644 --- a/problems/二叉树的递归遍历.md +++ b/problems/二叉树的递归遍历.md @@ -99,9 +99,9 @@ void traversal(TreeNode* cur, vector& vec) { 此时大家可以做一做leetcode上三道题目,分别是: -* [144.二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) -* [145.二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/) -* [94.二叉树的中序遍历](https://leetcode-cn.com/problems/binary-tree-inorder-traversal/) +* [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/) 可能有同学感觉前后中序遍历的递归太简单了,要打迭代法(非递归),别急,我们明天打迭代法,打个通透! diff --git a/problems/剑指Offer05.替换空格.md b/problems/剑指Offer05.替换空格.md index eecd7f0c..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"。 @@ -467,6 +467,73 @@ 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 7c39ed69..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"。 @@ -341,7 +341,30 @@ object Solution { } ``` +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..cbfe046a 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** + +```javaescript +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/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index a40a92ab..e24824b9 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] } diff --git a/problems/面试题02.07.链表相交.md b/problems/面试题02.07.链表相交.md index 0a38cc33..6ea9a037 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: @@ -162,15 +162,15 @@ class Solution: """ 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 +208,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 +241,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) {