Merge branch 'youngyangyang04:master' into master

This commit is contained in:
xsduan98
2021-07-12 09:29:05 +08:00
committed by GitHub
26 changed files with 1118 additions and 652 deletions

View File

@ -131,6 +131,7 @@
7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw) 7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw)
8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg) 8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg)
9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ) 9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ)
10. [你的简历里「专业技能」写的够专业么?](https://mp.weixin.qq.com/s/bp6y-e5FVN28H9qc8J9zrg)
## 数组 ## 数组
@ -395,7 +396,8 @@
## 单调栈 ## 单调栈
1. [每日温度](./problems/0739.每日温度.md) 1. [单调栈:每日温度](./problems/0739.每日温度.md)
2. [单调栈下一个更大元素I](./problems/0496.下一个更大元素I.md)
## 图论 ## 图论

View File

@ -41,6 +41,9 @@ candidates 中的每个数字在每个组合中只能使用一次。
## 思路 ## 思路
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别: 这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别:
1. 本题candidates 中的每个数字在每个组合中只能使用一次。 1. 本题candidates 中的每个数字在每个组合中只能使用一次。

View File

@ -30,7 +30,7 @@
## 思路 ## 思路
本题相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。 本题相对于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。
但思路是相似的,还是要看最大覆盖范围。 但思路是相似的,还是要看最大覆盖范围。
@ -132,7 +132,7 @@ public:
## 总结 ## 总结
相信大家可以发现,这道题目相当于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。 相信大家可以发现,这道题目相当于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。
但代码又十分简单,贪心就是这么巧妙。 但代码又十分简单,贪心就是这么巧妙。
@ -228,11 +228,6 @@ var jump = function(nums) {
}; };
``` ```
/*
dp[i]表示从起点到当前位置的最小跳跃次数
dp[i]=min(dp[j]+1,dp[i]) 表示从j位置用一步跳跃到当前位置这个j位置可能有很多个却最小一个就可以
*/
```

View File

@ -27,7 +27,10 @@
## 思路 ## 思路
此时我们已经学习了[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题 **如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助
此时我们已经学习了[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、 [131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。
相信这个排列问题就算是让你用for循环暴力把结果搜索出来这个暴力也不是很好写。 相信这个排列问题就算是让你用for循环暴力把结果搜索出来这个暴力也不是很好写。
@ -81,7 +84,7 @@ if (path.size() == nums.size()) {
* 单层搜索的逻辑 * 单层搜索的逻辑
这里和[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。 这里和[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[131.切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。
因为排列问题每次都要从头开始搜索例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。 因为排列问题每次都要从头开始搜索例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

View File

@ -29,6 +29,8 @@
* -10 <= nums[i] <= 10 * -10 <= nums[i] <= 10
## 思路 ## 思路
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。

View File

@ -41,6 +41,9 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并
## 思路 ## 思路
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
都知道n皇后问题是回溯算法解决的经典问题但是用回溯解决多了组合、切割、子集、排列问题之后遇到这种二位矩阵还会有点不知所措。 都知道n皇后问题是回溯算法解决的经典问题但是用回溯解决多了组合、切割、子集、排列问题之后遇到这种二位矩阵还会有点不知所措。
首先来看一下皇后们的约束条件: 首先来看一下皇后们的约束条件:

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,7 @@
## 思路 ## 思路
看完了这篇[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。 看完了这篇[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。
直觉上好像和求最大深度差不多,其实还是差不少的。 直觉上好像和求最大深度差不多,其实还是差不少的。
@ -154,7 +154,7 @@ public:
## 迭代法 ## 迭代法
相对于[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。
如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) 如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog)

View File

@ -291,6 +291,27 @@ func wordBreak(s string,wordDict []string) bool {
} }
``` ```
Javascript
```javascript
const wordBreak = (s, wordDict) => {
let dp = Array(s.length + 1).fill(false);
dp[0] = true;
for(let i = 0; i <= s.length; i++){
for(let j = 0; j < wordDict.length; j++) {
if(i >= wordDict[j].length) {
if(s.slice(i - wordDict[j].length, i) === wordDict[j] && dp[i - wordDict[j].length]) {
dp[i] = true
}
}
}
}
return dp[s.length];
}
```
----------------------- -----------------------

View File

@ -26,27 +26,26 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
   
示例 1 示例 1
输入: ["2", "1", "+", "3", " * "] * 输入: ["2", "1", "+", "3", " * "]
输出: 9 * 输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 * 解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2 示例 2
输入: ["4", "13", "5", "/", "+"] * 输入: ["4", "13", "5", "/", "+"]
输出: 6 * 输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 * 解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3 示例 3
输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"] * 输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"]
输出: 22 * 输出: 22
解释: * 解释:该算式转化为常见的中缀算术表达式为:
该算式转化为常见的中缀算术表达式为: ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
((10 * (6 / ((9 + 3) * -11))) + 17) + 5 = ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5 = ((10 * (6 / -132)) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5 = ((10 * 0) + 17) + 5
= ((10 * 0) + 17) + 5 = (0 + 17) + 5
= (0 + 17) + 5 = 17 + 5
= 17 + 5 = 22
= 22
   
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。 逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
@ -63,20 +62,20 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/
# 思路 # 思路
在上一篇文章中[栈与队列:匹配问题都是栈的强](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)提到了 递归就是用栈来实现的。 在上一篇文章中[1047.删除字符串中的所有相邻重复](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)提到了 递归就是用栈来实现的。
所以**栈与递归之间在某种程度上是可以转换的!**这一点我们在后续讲解二叉树的时候,会更详细的讲解到。 所以**栈与递归之间在某种程度上是可以转换的!** 这一点我们在后续讲解二叉树的时候,会更详细的讲解到。
那么来看一下本题,**其实逆波兰表达式相当于是二叉树中的后序遍历**。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。 那么来看一下本题,**其实逆波兰表达式相当于是二叉树中的后序遍历**。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。
但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。 但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后续遍历的方式把二叉树序列化了,就可以了。
在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[栈与队列:匹配问题都是栈的强](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)中的对对碰游戏是不是就非常像了。** 在进一步看,本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么**这岂不就是一个相邻字符串消除的过程,和[1047.删除字符串中的所有相邻重复](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)中的对对碰游戏是不是就非常像了。**
如动画所示: 如动画所示:
![150.逆波兰表达式求值](https://code-thinking.cdn.bcebos.com/gifs/150.%E9%80%86%E6%B3%A2%E5%85%B0%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B1%82%E5%80%BC.gif) ![150.逆波兰表达式求值](https://code-thinking.cdn.bcebos.com/gifs/150.逆波兰表达式求值.gif)
相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/eynAEbUbZoAWrk0ZlEugqg)是差不错的,只不过本题不要相邻元素做消除了,而是做运算! 相信看完动画大家应该知道,这和[1047. 删除字符串中的所有相邻重复项](https://mp.weixin.qq.com/s/1-x6r1wGA9mqIHW5LrMvBg)是差不错的,只不过本题不要相邻元素做消除了,而是做运算!
C++代码如下: C++代码如下:
@ -126,7 +125,7 @@ public:
## 其他语言版本 # 其他语言版本
java: java:
@ -153,23 +152,20 @@ public class EvalRPN {
} }
return stack.pop(); return stack.pop();
} }
private boolean isOpe(String s) { private boolean isOpe(String s) {
return s.length() == 1 && s.charAt(0) <'0' || s.charAt(0) >'9'; return s.length() == 1 && s.charAt(0) <'0' || s.charAt(0) >'9';
} }
private int stoi(String s) { private int stoi(String s) {
return Integer.valueOf(s); return Integer.valueOf(s);
} }
public static void main(String[] args) { public static void main(String[] args) {
new EvalRPN().evalRPN(new String[] {"10","6","9","3","+","-11","*","/","*","17","+","5","+"}); new EvalRPN().evalRPN(new String[] {"10","6","9","3","+","-11","*","/","*","17","+","5","+"});
} }
} }
``` ```
Go: Go:
```Go ```Go
func evalRPN(tokens []string) int { func evalRPN(tokens []string) int {

View File

@ -34,7 +34,7 @@
本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。 本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。
相对于[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)无非就是多了一个限制本题是要找到和为n的k个数的组合而整个集合已经是固定的了[1,...,9]。 相对于[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)无非就是多了一个限制本题是要找到和为n的k个数的组合而整个集合已经是固定的了[1,...,9]。
想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。 想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。
@ -53,7 +53,7 @@
* **确定递归函数参数** * **确定递归函数参数**
和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样依然需要一维数组path来存放符合条件的结果二维数组result来存放结果集。 和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样依然需要一维数组path来存放符合条件的结果二维数组result来存放结果集。
这里我依然定义path 和 result为全局变量。 这里我依然定义path 和 result为全局变量。
@ -103,7 +103,7 @@ if (path.size() == k) {
* **单层搜索过程** * **单层搜索过程**
本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9 本题和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9]所以for循环固定i<=9
如图: 如图:
![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) ![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png)
@ -227,6 +227,44 @@ public:
Java Java
模板方法
```java
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backTracking(n, k, 1, 0);
return result;
}
private void backTracking(int targetSum, int k, int startIndex, int sum) {
// 减枝
if (sum > targetSum) {
return;
}
if (path.size() == k) {
if (sum == targetSum) result.add(new ArrayList<>(path));
return;
}
// 减枝 9 - (k - path.size()) + 1
for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) {
path.add(i);
sum += i;
backTracking(targetSum, k, i + 1, sum);
//回溯
path.removeLast();
//回溯
sum -= i;
}
}
}
```
其他方法
```java ```java
class Solution { class Solution {
List<List<Integer>> res = new ArrayList<>(); List<List<Integer>> res = new ArrayList<>();

View File

@ -22,13 +22,13 @@ https://leetcode-cn.com/problems/sliding-window-maximum/
你能在线性时间复杂度内解决此题吗? 你能在线性时间复杂度内解决此题吗?
   
<img src='https://code-thinking.cdn.bcebos.com/pics/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.png' width=600> </img></div> <img src='https://code-thinking.cdn.bcebos.com/pics/239.滑动窗口最大值.png' width=600> </img></div>
提示: 提示:
1 <= nums.length <= 10^5 * 1 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4 * -10^4 <= nums[i] <= 10^4
1 <= k <= nums.length * 1 <= k <= nums.length
@ -84,7 +84,7 @@ public:
动画如下: 动画如下:
![239.滑动窗口最大值](https://code-thinking.cdn.bcebos.com/gifs/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.gif) ![239.滑动窗口最大值](https://code-thinking.cdn.bcebos.com/gifs/239.滑动窗口最大值.gif)
对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。 对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。
@ -100,11 +100,11 @@ public:
为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3动画如下 为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3动画如下
![239.滑动窗口最大值-2](https://code-thinking.cdn.bcebos.com/gifs/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC-2.gif) ![239.滑动窗口最大值-2](https://code-thinking.cdn.bcebos.com/gifs/239.滑动窗口最大值-2.gif)
那么我们用什么数据结构来实现这个单调队列呢? 那么我们用什么数据结构来实现这个单调队列呢?
使用deque最为合适在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)中我们就提到了常用的queue在没有指定容器的情况下deque就是默认底层容器。 使用deque最为合适在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/HCXfQ_Bhpi63YaX0ZRSnAQ)中我们就提到了常用的queue在没有指定容器的情况下deque就是默认底层容器。
基于刚刚说过的单调队列pop和push的规则代码不难实现如下 基于刚刚说过的单调队列pop和push的规则代码不难实现如下
@ -201,9 +201,7 @@ public:
# 其他语言版本
## 其他语言版本
Java Java
@ -337,36 +335,6 @@ class Solution:
Go Go
```go
func maxSlidingWindow(nums []int, k int) []int {
var queue []int
var rtn []int
for f := 0; f < len(nums); f++ {
//维持队列递减, 将 k 插入合适的位置, queue中 <=k 的 元素都不可能是窗口中的最大值, 直接弹出
for len(queue) > 0 && nums[f] > nums[queue[len(queue)-1]] {
queue = queue[:len(queue)-1]
}
// 等大的后来者也应入队
if len(queue) == 0 || nums[f] <= nums[queue[len(queue)-1]] {
queue = append(queue, f)
}
if f >= k - 1 {
rtn = append(rtn, nums[queue[0]])
//弹出离开窗口的队首
if f - k + 1 == queue[0] {
queue = queue[1:]
}
}
}
return rtn
}
```
```go ```go
// 封装单调队列的方式解题 // 封装单调队列的方式解题
type MyQueue struct { type MyQueue struct {

View File

@ -304,6 +304,26 @@ func min(a, b int) int {
``` ```
Javascript
```javascript
const coinChange = (coins, amount) => {
if(!amount) {
return 0;
}
let dp = Array(amount + 1).fill(Infinity);
dp[0] = 0;
for(let i =0; i < coins.length; i++) {
for(let j = coins[i]; j <= amount; j++) {
dp[j] = Math.min(dp[j - coins[i]] + 1, dp[j]);
}
}
return dp[amount] === Infinity ? -1 : dp[amount];
}
```
----------------------- -----------------------

View File

@ -33,6 +33,9 @@
## 思路 ## 思路
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[分割问题](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)[排列问题](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)。 这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)[分割问题](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA)[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)[排列问题](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)。
直觉上来看 这道题和回溯法没有什么关系,更像是图论中的深度优先搜索。 直觉上来看 这道题和回溯法没有什么关系,更像是图论中的深度优先搜索。

View File

@ -201,6 +201,25 @@ func combinationSum4(nums []int, target int) int {
} }
``` ```
Javascript
```javascript
const combinationSum4 = (nums, target) => {
let dp = Array(target + 1).fill(0);
dp[0] = 1;
for(let i = 0; i <= target; i++) {
for(let j = 0; j < nums.length; j++) {
if (i >= nums[j]) {
dp[i] += dp[i - nums[j]];
}
}
}
return dp[target];
};
```
----------------------- -----------------------

View File

@ -43,9 +43,9 @@
本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后在按照另一个维度重新排列 本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后在按照另一个维度重新排列
其实如果大家认真做了[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)就会发现和此题有点点的像 其实如果大家认真做了[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)就会发现和此题有点点的像
[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次遇到两个维度权衡的时候一定要先确定一个维度再确定另一个维度 [135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次遇到两个维度权衡的时候一定要先确定一个维度再确定另一个维度
**如果两个维度一起考虑一定会顾此失彼** **如果两个维度一起考虑一定会顾此失彼**
@ -161,11 +161,11 @@ public:
## 总结 ## 总结
关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。 关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。
**其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼**。 **其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼**。
这道题目可以说比[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。 这道题目可以说比[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。
最后我给出了两个版本的代码可以明显看是使用C++中的list底层链表实现比vector数组效率高得多。 最后我给出了两个版本的代码可以明显看是使用C++中的list底层链表实现比vector数组效率高得多。

View File

@ -122,9 +122,9 @@ public:
## 补充 ## 补充
本题其实和[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[01][12]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 本题其实和[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[01][12]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。
把[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改就可以AC本题。 把[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改就可以AC本题。
```C++ ```C++
class Solution { class Solution {

View File

@ -244,6 +244,35 @@ func max(a,b int) int {
} }
``` ```
Javascript
```javascript
const findMaxForm = (strs, m, n) => {
const dp = Array.from(Array(m+1), () => Array(n+1).fill(0));
let numOfZeros, numOfOnes;
for(let str of strs) {
numOfZeros = 0;
numOfOnes = 0;
for(let c of str) {
if (c === '0') {
numOfZeros++;
} else {
numOfOnes++;
}
}
for(let i = m; i >= numOfZeros; i--) {
for(let j = n; j >= numOfOnes; j--) {
dp[i][j] = Math.max(dp[i][j], dp[i - numOfZeros][j - numOfOnes] + 1);
}
}
}
return dp[m][n];
};
```
----------------------- -----------------------

View File

@ -28,6 +28,9 @@
## 思路 ## 思路
**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。
这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。 这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。
这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)。 这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)。
@ -254,6 +257,34 @@ class Solution:
Go Go
```golang
func findSubsequences(nums []int) [][]int {
var subRes []int
var res [][]int
backTring(0,nums,subRes,&res)
return res
}
func backTring(startIndex int,nums,subRes []int,res *[][]int){
if len(subRes)>1{
tmp:=make([]int,len(subRes))
copy(tmp,subRes)
*res=append(*res,tmp)
}
history:=[201]int{}//记录本层元素使用记录
for i:=startIndex;i<len(nums);i++{
//分两种情况判断:一,当前取的元素小于子集的最后一个元素,则继续寻找下一个适合的元素
// 或者二,当前取的元素在本层已经出现过了,所以跳过该元素,继续寻找
if len(subRes)>0&&nums[i]<subRes[len(subRes)-1]||history[nums[i] + 100]==1{
continue
}
history[nums[i] + 100]=1//表示本层该元素使用过了
subRes=append(subRes,nums[i])
backTring(i+1,nums,subRes,res)
subRes=subRes[:len(subRes)-1]
}
}
```
Javascript: Javascript:
```Javascript ```Javascript

View File

@ -306,6 +306,36 @@ func findTargetSumWays(nums []int, target int) int {
} }
``` ```
Javascript
```javascript
const findTargetSumWays = (nums, target) => {
const sum = nums.reduce((a, b) => a+b);
if(target > sum) {
return 0;
}
if((target + sum) % 2) {
return 0;
}
const halfSum = (target + sum) / 2;
nums.sort((a, b) => a - b);
let dp = new Array(halfSum+1).fill(0);
dp[0] = 1;
for(let i = 0; i < nums.length; i++) {
for(let j = halfSum; j >= nums[i]; j--) {
dp[j] += dp[j - nums[i]];
}
}
return dp[halfSum];
};
```
----------------------- -----------------------

View File

@ -0,0 +1,210 @@
# 496.下一个更大元素 I
题目链接https://leetcode-cn.com/problems/next-greater-element-i/
给你两个 没有重复元素 的数组 nums1 和 nums2 其中nums1  nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x  nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在对应位置输出 -1 。
示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出-1 。
 
提示:
* 1 <= nums1.length <= nums2.length <= 1000
* 0 <= nums1[i], nums2[i] <= 10^4
* nums1和nums2中所有整数 互不相同
* nums1 中的所有整数同样出现在 nums2 中
# 思路
做本题之前,建议先做一下[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q)
在[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q)中是求每个元素下一个比当前元素大的元素的位置。
本题则是说nums1 是 nums2的子集找nums1中的元素在nums2中下一个比当前元素大的元素。
看上去和[739. 每日温度](https://mp.weixin.qq.com/s/YeQ7eE0-hZpxJfJJziq25Q) 就如出一辙了。
几乎是一样的,但是这么绕了一下,其实还上升了一点难度。
需要对单调栈使用的更熟练一些,才能顺利的把本题写出来。
从题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素那么就要定义一个和nums1一样大小的数组result来存放结果。
一些同学可能看到两个数组都已经懵了不知道要定一个一个多大的result数组来存放结果了。
**这么定义这个result数组初始化应该为多少呢**
题目说如果不存在对应位置就输出 -1 所以result数组如果某位置没有被赋值那么就应该是是-1所以就初始化为-1。
在遍历nums2的过程中我们要判断nums2[i]是否在nums1中出现过因为最后是要根据nums1元素的下标来更新result数组。
**注意题目中说是两个没有重复元素 的数组 nums1 和 nums2**
没有重复元素我们就可以用map来做映射了。根据数值快速找到下标还可以判断nums2[i]是否在nums1中出现过。
C++中当我们要使用集合来解决哈希问题的时候优先使用unordered_set因为它的查询和增删效率是最优的。我在[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/RSUANESA_tkhKhYe3ZR8Jg)中也做了详细的解释。
那么预处理代码如下:
```C++
unordered_map<int, int> umap; // key:下表元素value下表
for (int i = 0; i < nums1.size(); i++) {
umap[nums1[i]] = i;
}
```
使用单调栈,首先要想单调栈是从大到小还是从小到大。
本题和739. 每日温度是一样的。
栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。
可能这里有一些同学不理解,那么可以自己尝试一下用递减栈,能不能求出来。其实递减栈就是求右边第一个比自己小的元素了。
接下来就要分析如下三种情况,一定要分析清楚。
1. 情况一当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
此时满足递增栈(栈头到栈底的顺序),所以直接入栈。
2. 情况二当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
如果相等的话,依然直接入栈,因为我们要求的是右边第一个比自己大的元素,而不是大于等于!
3. 情况三当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况
此时如果入栈就不满足递增栈了,这也是找到右边第一个比自己大的元素的时候。
判断栈顶元素是否在nums1里出现过注意栈里的元素是nums2的元素如果出现过开始记录结果。
记录结果这块逻辑有一点小绕要清楚此时栈顶元素在nums2中右面第一个大的元素是nums2[i]即当前遍历元素。
代码如下:
```C++
while (!st.empty() && nums2[i] > nums2[st.top()]) {
if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表
result[index] = nums2[i];
}
st.pop();
}
st.push(i);
```
以上分析完毕C++代码如下:
```C++
// 版本一
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> st;
vector<int> result(nums1.size(), -1);
if (nums1.size() == 0) return result;
unordered_map<int, int> umap; // key:下表元素value下表
for (int i = 0; i < nums1.size(); i++) {
umap[nums1[i]] = i;
}
st.push(0);
for (int i = 1; i < nums2.size(); i++) {
if (nums2[i] < nums2[st.top()]) { // 情况一
st.push(i);
} else if (nums2[i] == nums2[st.top()]) { // 情况二
st.push(i);
} else { // 情况三
while (!st.empty() && nums2[i] > nums2[st.top()]) {
if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表
result[index] = nums2[i];
}
st.pop();
}
st.push(i);
}
}
return result;
}
};
```
针对版本一,进行代码精简后,代码如下:
```C++
// 版本二
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
stack<int> st;
vector<int> result(nums1.size(), -1);
if (nums1.size() == 0) return result;
unordered_map<int, int> umap; // key:下表元素value下表
for (int i = 0; i < nums1.size(); i++) {
umap[nums1[i]] = i;
}
st.push(0);
for (int i = 1; i < nums2.size(); i++) {
while (!st.empty() && nums2[i] > nums2[st.top()]) {
if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素
int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表
result[index] = nums2[i];
}
st.pop();
}
st.push(i);
}
return result;
}
};
```
精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。
建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简!
## 其他语言版本
Python
```python3
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
result = [-1]*len(nums1)
stack = [0]
for i in range(1,len(nums2)):
# 情况一情况二
if nums2[i]<=nums2[stack[-1]]:
stack.append(i)
# 情况三
else:
while len(stack)!=0 and nums2[i]>nums2[stack[-1]]:
if nums2[stack[-1]] in nums1:
index = nums1.index(nums2[stack[-1]])
result[index]=nums2[i]
stack.pop()
stack.append(i)
return result
```

View File

@ -243,6 +243,22 @@ func change(amount int, coins []int) int {
} }
``` ```
Javascript
```javascript
const change = (amount, coins) => {
let dp = Array(amount + 1).fill(0);
dp[0] = 1;
for(let i =0; i < coins.length; i++) {
for(let j = coins[i]; j <= amount; j++) {
dp[j] += dp[j - coins[i]];
}
}
return dp[amount];
}
```
----------------------- -----------------------

View File

@ -319,7 +319,7 @@ Python
# self.val = val # self.val = val
# self.left = left # self.left = left
# self.right = right # self.right = right
//递归法*前序遍历 # 递归法*前序遍历
class Solution: class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if not root1: return root2 // 如果t1为空合并之后就应该是t2 if not root1: return root2 // 如果t1为空合并之后就应该是t2
@ -328,6 +328,32 @@ class Solution:
root1.left = self.mergeTrees(root1.left , root2.left) //左 root1.left = self.mergeTrees(root1.left , root2.left) //左
root1.right = self.mergeTrees(root1.right , root2.right) //右 root1.right = self.mergeTrees(root1.right , root2.right) //右
return root1 //root1修改了结构和数值 return root1 //root1修改了结构和数值
# 迭代法-覆盖原来的树
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
if not root1: return root2
if not root2: return root1
# 迭代,将树2覆盖到树1
queue1 = [root1]
queue2 = [root2]
root = root1
while queue1 and queue2:
root1 = queue1.pop(0)
root2 = queue2.pop(0)
root1.val += root2.val
if not root1.left: # 如果树1左儿子不存在则覆盖后树1的左儿子为树2的左儿子
root1.left = root2.left
elif root1.left and root2.left:
queue1.append(root1.left)
queue2.append(root2.left)
if not root1.right: # 同理,处理右儿子
root1.right = root2.right
elif root1.right and root2.right:
queue1.append(root1.right)
queue2.append(root2.right)
return root
``` ```
Go Go

View File

@ -211,7 +211,24 @@ Java
} }
``` ```
Python Python
``` Python3
class Solution:
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
answer = [0]*len(temperatures)
stack = [0]
for i in range(1,len(temperatures)):
# 情况一和情况二
if temperatures[i]<=temperatures[stack[-1]]:
stack.append(i)
# 情况三
else:
while len(stack) != 0 and temperatures[i]>temperatures[stack[-1]]:
answer[stack[-1]]=i-stack[-1]
stack.pop()
stack.append(i)
return answer
```
Go Go
> 暴力法 > 暴力法

View File

@ -99,10 +99,17 @@ leetcode上没有纯01背包的问题都是01背包应用方面的题目
状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来那么i为0的时候就一定要初始化。 状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来那么i为0的时候就一定要初始化。
dp[0][j]i为0存放编号0的物品的时候各个容量的背包所能存放的最大价值。 dp[0][j]i为0存放编号0的物品的时候各个容量的背包所能存放的最大价值。
代码如下: 那么很明显当 j < weight[0]的时候dp[0][j] 应该是 0因为背包容量比编号0的物品重量还小
```
当j >= weight[0]是dp[0][j] 应该是value[0]因为背包容量放足够放编号0物品。
代码初始化如下:
```
for (int j = 0 ; j < weight[0]; j++) { // 当然这一步如果把dp数组预先初始化为0了这一步就可以省略但很多同学应该没有想清楚这一点。
dp[0][j] = 0;
}
// 正序遍历 // 正序遍历
for (int j = weight[0]; j <= bagWeight; j++) { for (int j = weight[0]; j <= bagWeight; j++) {
dp[0][j] = value[0]; dp[0][j] = value[0];
@ -116,14 +123,11 @@ for (int j = weight[0]; j <= bagWeight; j++) {
dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢? dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢?
其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是又左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。
dp[i][j]在推导的时候一定是取价值最大的数如果题目给的价值都是正整数那么非0下标都初始化为0就可以了因为0就是最小的了不会影响取最大价值的结果。 初始-1初始-2初始100都可以
如果题目给的价值有负数那么非0下标就要初始化为负无穷了。例如一个物品的价值是-2但对应的位置依然初始为0那么取最大值的时候就会取0而不是-2了所以要初始化为负无穷 但只不过一开始就统一把dp数组统一初始为0更方便一些
而背包问题的物品价值都是正整数所以初始化为0就可以了。
**这样才能让dp数组在递归公式的过程中取最大的价值而不是被初始值覆盖了**
如图: 如图:
@ -159,7 +163,7 @@ for (int j = weight[0]; j <= bagWeight; j++) {
// weight数组的大小 就是物品个数 // weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品 for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化 if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
} }

View File

@ -176,9 +176,48 @@ int main() {
## 其他语言版本 ## 其他语言版本
Java Java
```java
//先遍历物品,再遍历背包
private static void testCompletePack(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 0; i < weight.length; i++){
for (int j = 1; j <= bagWeight; j++){
if (j - weight[i] >= 0){
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
}
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
//先遍历背包,再遍历物品
private static void testCompletePackAnotherWay(){
int[] weight = {1, 3, 4};
int[] value = {15, 20, 30};
int bagWeight = 4;
int[] dp = new int[bagWeight + 1];
for (int i = 1; i <= bagWeight; i++){
for (int j = 0; j < weight.length; j++){
if (i - weight[j] >= 0){
dp[i] = Math.max(dp[i], dp[i - weight[j]] + value[j]);
}
}
}
for (int maxValue : dp){
System.out.println(maxValue + " ");
}
}
```
Python Python
```python3 ```python3