diff --git a/README.md b/README.md index 2c462a52..f6567cc4 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ 7. [英语到底重不重要!](https://mp.weixin.qq.com/s/1PRZiyF_-TVA-ipwDNjdKw) 8. [计算机专业要不要读研!](https://mp.weixin.qq.com/s/c9v1L3IjqiXtkNH7sOMAdg) 9. [秋招和提前批都越来越提前了....](https://mp.weixin.qq.com/s/SNFiRDx8CKyjhTPlys6ywQ) +10. [你的简历里「专业技能」写的够专业么?](https://mp.weixin.qq.com/s/bp6y-e5FVN28H9qc8J9zrg) ## 数组 @@ -395,7 +396,8 @@ ## 单调栈 -1. [每日温度](./problems/0739.每日温度.md) +1. [单调栈:每日温度](./problems/0739.每日温度.md) +2. [单调栈:下一个更大元素I](./problems/0496.下一个更大元素I.md) ## 图论 diff --git a/problems/0040.组合总和II.md b/problems/0040.组合总和II.md index 021bba94..70e012da 100644 --- a/problems/0040.组合总和II.md +++ b/problems/0040.组合总和II.md @@ -41,6 +41,9 @@ candidates 中的每个数字在每个组合中只能使用一次。 ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 这道题目和[39.组合总和](https://mp.weixin.qq.com/s/FLg8G6EjVcxBjwCbzpACPw)如下区别: 1. 本题candidates 中的每个数字在每个组合中只能使用一次。 diff --git a/problems/0045.跳跃游戏II.md b/problems/0045.跳跃游戏II.md index 4128da4c..a161d944 100644 --- a/problems/0045.跳跃游戏II.md +++ b/problems/0045.跳跃游戏II.md @@ -30,7 +30,7 @@ ## 思路 -本题相对于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。 +本题相对于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)还是难了不少。 但思路是相似的,还是要看最大覆盖范围。 @@ -132,7 +132,7 @@ public: ## 总结 -相信大家可以发现,这道题目相当于[贪心算法:跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。 +相信大家可以发现,这道题目相当于[55.跳跃游戏](https://mp.weixin.qq.com/s/606_N9j8ACKCODoCbV1lSA)难了不止一点。 但代码又十分简单,贪心就是这么巧妙。 @@ -228,11 +228,6 @@ var jump = function(nums) { }; ``` -/* -dp[i]表示从起点到当前位置的最小跳跃次数 -dp[i]=min(dp[j]+1,dp[i]) 表示从j位置用一步跳跃到当前位置,这个j位置可能有很多个,却最小一个就可以 -*/ -``` diff --git a/problems/0046.全排列.md b/problems/0046.全排列.md index 30c3374b..3ee7271e 100644 --- a/problems/0046.全排列.md +++ b/problems/0046.全排列.md @@ -27,7 +27,10 @@ ## 思路 -此时我们已经学习了[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + +此时我们已经学习了[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、 [131.分割回文串](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),接下来看一看排列问题。 相信这个排列问题就算是让你用for循环暴力把结果搜索出来,这个暴力也不是很好写。 @@ -81,7 +84,7 @@ if (path.size() == nums.size()) { * 单层搜索的逻辑 -这里和[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。 +这里和[77.组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)、[131.切割问题](https://mp.weixin.qq.com/s/Pb1epUTbU8fHIht-g_MS5Q)和[78.子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA)最大的不同就是for循环里不用startIndex了。 因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。 diff --git a/problems/0047.全排列II.md b/problems/0047.全排列II.md index b4fb3470..079ca834 100644 --- a/problems/0047.全排列II.md +++ b/problems/0047.全排列II.md @@ -29,6 +29,8 @@ * -10 <= nums[i] <= 10 ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + 这道题目和[回溯算法:排列问题!](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)的区别在与**给定一个可包含重复数字的序列**,要返回**所有不重复的全排列**。 diff --git a/problems/0051.N皇后.md b/problems/0051.N皇后.md index fd2c7d0f..a8919ec3 100644 --- a/problems/0051.N皇后.md +++ b/problems/0051.N皇后.md @@ -41,6 +41,9 @@ n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并 ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 都知道n皇后问题是回溯算法解决的经典问题,但是用回溯解决多了组合、切割、子集、排列问题之后,遇到这种二位矩阵还会有点不知所措。 首先来看一下皇后们的约束条件: diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 2f56af52..b13fdd8d 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -80,15 +80,10 @@ public: } }; ``` + python代码: ```python -# Definition for a binary tree node. -# class TreeNode: -# def __init__(self, val=0, left=None, right=None): -# self.val = val -# self.left = left -# self.right = right class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: if not root: @@ -110,7 +105,94 @@ class Solution: return out_list ``` +java: +```Java +// 102.二叉树的层序遍历 +class Solution { + public List> resList = new ArrayList>(); + + public List> levelOrder(TreeNode root) { + //checkFun01(root,0); + checkFun02(root); + + return resList; + } + + //DFS--递归方式 + public void checkFun01(TreeNode node, Integer deep) { + if (node == null) return; + deep++; + + if (resList.size() < deep) { + //当层级增加时,list的Item也增加,利用list的索引值进行层级界定 + List item = new ArrayList(); + resList.add(item); + } + resList.get(deep - 1).add(node.val); + + checkFun01(node.left, deep); + checkFun01(node.right, deep); + } + + //BFS--迭代方式--借助队列 + public void checkFun02(TreeNode node) { + if (node == null) return; + Queue que = new LinkedList(); + que.offer(node); + + while (!que.isEmpty()) { + List itemList = new ArrayList(); + int len = que.size(); + + while (len > 0) { + TreeNode tmpNode = que.poll(); + itemList.add(tmpNode.val); + + if (tmpNode.left != null) que.offer(tmpNode.left); + if (tmpNode.right != null) que.offer(tmpNode.right); + len--; + } + + resList.add(itemList); + } + + } +} +``` + +go: + +```go +/** +102. 二叉树的层序遍历 + */ +func levelOrder(root *TreeNode) [][]int { + res:=[][]int{} + if root==nil{//防止为空 + return res + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []int + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i List[List[int]]: if not root: @@ -218,7 +294,88 @@ class Solution: # 内存消耗:15.2 MB, 在所有 Python3 提交中击败了63.76%的用户 ``` +Java: +```java +// 107. 二叉树的层序遍历 II +public class N0107 { + + /** + * 解法:队列,迭代。 + * 层序遍历,再翻转数组即可。 + */ + public List> solution1(TreeNode root) { + List> list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + List levelList = new ArrayList<>(); + + int levelSize = que.size(); + for (int i = 0; i < levelSize; i++) { + TreeNode peek = que.peekFirst(); + levelList.add(que.pollFirst().val); + + if (peek.left != null) { + que.offerLast(peek.left); + } + if (peek.right != null) { + que.offerLast(peek.right); + } + } + list.add(levelList); + } + + List> result = new ArrayList<>(); + for (int i = list.size() - 1; i >= 0; i-- ) { + result.add(list.get(i)); + } + + return result; + } +} +``` + +go: + +```GO +/** +107. 二叉树的层序遍历 II + */ +func levelOrderBottom(root *TreeNode) [][]int { + queue:=list.New() + res:=[][]int{} + if root==nil{ + return res + } + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + tmp:=[]int{} + for i:=0;i List[int]: if not root: @@ -322,7 +473,87 @@ class Solution: ``` +Java: +```java +// 199.二叉树的右视图 +public class N0199 { + /** + * 解法:队列,迭代。 + * 每次返回每层的最后一个字段即可。 + * + * 小优化:每层右孩子先入队。代码略。 + */ + public List rightSideView(TreeNode root) { + List list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + int levelSize = que.size(); + + for (int i = 0; i < levelSize; i++) { + TreeNode poll = que.pollFirst(); + + if (poll.left != null) { + que.addLast(poll.left); + } + if (poll.right != null) { + que.addLast(poll.right); + } + + if (i == levelSize - 1) { + list.add(poll.val); + } + } + } + + return list; + } +} +``` + +go: + +```GO + +/** +199. 二叉树的右视图 + */ +func rightSideView(root *TreeNode) []int { + queue:=list.New() + res:=[][]int{} + var finaRes []int + if root==nil{ + return finaRes + } + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len() + tmp:=[]int{} + for i:=0;i List[float]: if not root: @@ -426,7 +651,93 @@ class Solution: # 内存消耗:17 MB, 在所有 Python3 提交中击败了89.68%的用户 ``` +java: +```java + +// 637. 二叉树的层平均值 +public class N0637 { + + /** + * 解法:队列,迭代。 + * 每次返回每层的最后一个字段即可。 + */ + public List averageOfLevels(TreeNode root) { + List list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + TreeNode peek = que.peekFirst(); + + int levelSize = que.size(); + double levelSum = 0.0; + for (int i = 0; i < levelSize; i++) { + TreeNode poll = que.pollFirst(); + + levelSum += poll.val; + + if (poll.left != null) { + que.addLast(poll.left); + } + if (poll.right != null) { + que.addLast(poll.right); + } + } + list.add(levelSum / levelSize); + } + return list; + } +} +``` + +go: + +```GO +/** +637. 二叉树的层平均值 + */ +func averageOfLevels(root *TreeNode) []float64 { + res:=[][]int{} + var finRes []float64 + if root==nil{//防止为空 + return finRes + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []int + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i List[List[int]]: @@ -562,7 +866,97 @@ class Solution: # 内存消耗:16.5 MB, 在所有 Python3 提交中击败了89.19%的用户 ``` +java: +```java + +// 429. N 叉树的层序遍历 +public class N0429 { + /** + * 解法1:队列,迭代。 + */ + public List> levelOrder(Node root) { + List> list = new ArrayList<>(); + Deque que = new LinkedList<>(); + + if (root == null) { + return list; + } + + que.offerLast(root); + while (!que.isEmpty()) { + int levelSize = que.size(); + List levelList = new ArrayList<>(); + + for (int i = 0; i < levelSize; i++) { + Node poll = que.pollFirst(); + + levelList.add(poll.val); + + List children = poll.children; + if (children == null || children.size() == 0) { + continue; + } + for (Node child : children) { + if (child != null) { + que.offerLast(child); + } + } + } + list.add(levelList); + } + + return list; + } + + class Node { + public int val; + public List children; + + public Node() {} + + public Node(int _val) { + val = _val; + } + + public Node(int _val, List _children) { + val = _val; + children = _children; + } + } +} +``` + + +go: + +```GO +/** +429. N 叉树的层序遍历 + */ + +func levelOrder(root *Node) [][]int { + queue:=list.New() + res:=[][]int{}//结果集 + if root==nil{ + return res + } + queue.PushBack(root) + for queue.Len()>0{ + length:=queue.Len()//记录当前层的数量 + var tmp []int + for T:=0;T0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i max { + max = val + } + } + return max +} +``` + javascript代码: ```javascript @@ -733,6 +1175,7 @@ public: }; ``` + python代码: ```python @@ -769,6 +1212,47 @@ class Solution: return root ``` +go: + +```GO +/** +116. 填充每个节点的下一个右侧节点指针 +117. 填充每个节点的下一个右侧节点指针 II + */ + +func connect(root *Node) *Node { + res:=[][]*Node{} + if root==nil{//防止为空 + return root + } + queue:=list.New() + queue.PushBack(root) + var tmpArr []*Node + for queue.Len()>0 { + length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) + for i:=0;i> resList = new ArrayList>(); - - public List> levelOrder(TreeNode root) { - //checkFun01(root,0); - checkFun02(root); - - return resList; - } - - //DFS--递归方式 - public void checkFun01(TreeNode node, Integer deep) { - if (node == null) return; - deep++; - - if (resList.size() < deep) { - //当层级增加时,list的Item也增加,利用list的索引值进行层级界定 - List item = new ArrayList(); - resList.add(item); - } - resList.get(deep - 1).add(node.val); - - checkFun01(node.left, deep); - checkFun01(node.right, deep); - } - - //BFS--迭代方式--借助队列 - public void checkFun02(TreeNode node) { - if (node == null) return; - Queue que = new LinkedList(); - que.offer(node); - - while (!que.isEmpty()) { - List itemList = new ArrayList(); - int len = que.size(); - - while (len > 0) { - TreeNode tmpNode = que.poll(); - itemList.add(tmpNode.val); - - if (tmpNode.left != null) que.offer(tmpNode.left); - if (tmpNode.right != null) que.offer(tmpNode.right); - len--; - } - - resList.add(itemList); - } - - } -} - - -// 107. 二叉树的层序遍历 II -public class N0107 { - - /** - * 解法:队列,迭代。 - * 层序遍历,再翻转数组即可。 - */ - public List> solution1(TreeNode root) { - List> list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - List levelList = new ArrayList<>(); - - int levelSize = que.size(); - for (int i = 0; i < levelSize; i++) { - TreeNode peek = que.peekFirst(); - levelList.add(que.pollFirst().val); - - if (peek.left != null) { - que.offerLast(peek.left); - } - if (peek.right != null) { - que.offerLast(peek.right); - } - } - list.add(levelList); - } - - List> result = new ArrayList<>(); - for (int i = list.size() - 1; i >= 0; i-- ) { - result.add(list.get(i)); - } - - return result; - } -} - -// 199.二叉树的右视图 -public class N0199 { - /** - * 解法:队列,迭代。 - * 每次返回每层的最后一个字段即可。 - * - * 小优化:每层右孩子先入队。代码略。 - */ - public List rightSideView(TreeNode root) { - List list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - int levelSize = que.size(); - - for (int i = 0; i < levelSize; i++) { - TreeNode poll = que.pollFirst(); - - if (poll.left != null) { - que.addLast(poll.left); - } - if (poll.right != null) { - que.addLast(poll.right); - } - - if (i == levelSize - 1) { - list.add(poll.val); - } - } - } - - return list; - } -} - -// 637. 二叉树的层平均值 -public class N0637 { - - /** - * 解法:队列,迭代。 - * 每次返回每层的最后一个字段即可。 - */ - public List averageOfLevels(TreeNode root) { - List list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - TreeNode peek = que.peekFirst(); - - int levelSize = que.size(); - double levelSum = 0.0; - for (int i = 0; i < levelSize; i++) { - TreeNode poll = que.pollFirst(); - - levelSum += poll.val; - - if (poll.left != null) { - que.addLast(poll.left); - } - if (poll.right != null) { - que.addLast(poll.right); - } - } - list.add(levelSum / levelSize); - } - return list; - } -} - -// 429. N 叉树的层序遍历 -public class N0429 { - /** - * 解法1:队列,迭代。 - */ - public List> levelOrder(Node root) { - List> list = new ArrayList<>(); - Deque que = new LinkedList<>(); - - if (root == null) { - return list; - } - - que.offerLast(root); - while (!que.isEmpty()) { - int levelSize = que.size(); - List levelList = new ArrayList<>(); - - for (int i = 0; i < levelSize; i++) { - Node poll = que.pollFirst(); - - levelList.add(poll.val); - - List children = poll.children; - if (children == null || children.size() == 0) { - continue; - } - for (Node child : children) { - if (child != null) { - que.offerLast(child); - } - } - } - list.add(levelList); - } - - return list; - } - - class Node { - public int val; - public List children; - - public Node() {} - - public Node(int _val) { - val = _val; - } - - public Node(int _val, List _children) { - val = _val; - children = _children; - } - } -} -``` - - -Python: - - -Go: -```Go -func levelOrder(root *TreeNode) [][]int { - result:=make([][]int,0) - if root==nil{ - return result - } - - queue:=make([]*TreeNode,0) - queue=append(queue,root) - - for len(queue)>0{ - list:=make([]int,0) - l:=len(queue) - - for i:=0;i 二叉树的层序遍历(GO语言完全版) - -```go -/** -102. 二叉树的层序遍历 - */ -func levelOrder(root *TreeNode) [][]int { - res:=[][]int{} - if root==nil{//防止为空 - return res - } - queue:=list.New() - queue.PushBack(root) - var tmpArr []int - for queue.Len()>0 { - length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) - for i:=0;i0{ - length:=queue.Len() - tmp:=[]int{} - for i:=0;i0{ - length:=queue.Len() - tmp:=[]int{} - for i:=0;i0 { - length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) - for i:=0;i0{ - length:=queue.Len()//记录当前层的数量 - var tmp []int - for T:=0;T0 { - length:=queue.Len()//保存当前层的长度,然后处理当前层(十分重要,防止添加下层元素影响判断层中元素的个数) - for i:=0;i max { - max = val - } - } - return max -} +```GO /** 116. 填充每个节点的下一个右侧节点指针 117. 填充每个节点的下一个右侧节点指针 II @@ -1391,26 +1377,31 @@ func connect(root *Node) *Node { return root } ``` -Javascript: -```javascript -var levelOrder = function (root) { - let ans = []; - if (!root) return ans; - let queue = [root]; - while (queue.length) { - let size = queue.length; - let temp = []; - while (size--) { - let n = queue.shift(); - temp.push(n.val); - if (n.left) queue.push(n.left); - if (n.right) queue.push(n.right); - } - ans.push(temp); - } - return ans; -}; -``` + +## 总结 + +二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时是不是又发现队列的应用了)。 + +虽然不能一口气打十个,打八个也还行。 + +* 102.二叉树的层序遍历 +* 107.二叉树的层次遍历II +* 199.二叉树的右视图 +* 637.二叉树的层平均值 +* 429.N叉树的前序遍历 +* 515.在每个树行中找最大值 +* 116.填充每个节点的下一个右侧节点指针 +* 117.填充每个节点的下一个右侧节点指针II + +如果非要打十个,还得找叶师傅! + +![我要打十个](https://tva1.sinaimg.cn/large/008eGmZEly1gnadnltbpjg309603w4qp.gif) + + + + +# 其他语言版本 + > 二叉树的层序遍历(Javascript语言完全版) (迭代 + 递归) diff --git a/problems/0111.二叉树的最小深度.md b/problems/0111.二叉树的最小深度.md index 48795722..a36faeff 100644 --- a/problems/0111.二叉树的最小深度.md +++ b/problems/0111.二叉树的最小深度.md @@ -29,7 +29,7 @@ ## 思路 -看完了这篇[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。 +看完了这篇[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),再来看看如何求最小深度。 直觉上好像和求最大深度差不多,其实还是差不少的。 @@ -154,7 +154,7 @@ public: ## 迭代法 -相对于[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 +相对于[104.二叉树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg),本题还可以使用层序遍历的方式来解决,思路是一样的。 如果对层序遍历还不清楚的话,可以看这篇:[二叉树:层序遍历登场!](https://mp.weixin.qq.com/s/Gb3BjakIKGNpup2jYtTzog) diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md index b6a6242e..59892ef9 100644 --- a/problems/0139.单词拆分.md +++ b/problems/0139.单词拆分.md @@ -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]; +} +``` + ----------------------- diff --git a/problems/0150.逆波兰表达式求值.md b/problems/0150.逆波兰表达式求值.md index c8b0da08..aceb91b4 100644 --- a/problems/0150.逆波兰表达式求值.md +++ b/problems/0150.逆波兰表达式求值.md @@ -26,27 +26,26 @@ https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/   示例 1: -输入: ["2", "1", "+", "3", " * "] -输出: 9 -解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 +* 输入: ["2", "1", "+", "3", " * "] +* 输出: 9 +* 解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9 示例 2: -输入: ["4", "13", "5", "/", "+"] -输出: 6 -解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 +* 输入: ["4", "13", "5", "/", "+"] +* 输出: 6 +* 解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6 示例 3: -输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"] -输出: 22 -解释: -该算式转化为常见的中缀算术表达式为: - ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 -= ((10 * (6 / (12 * -11))) + 17) + 5 -= ((10 * (6 / -132)) + 17) + 5 -= ((10 * 0) + 17) + 5 -= (0 + 17) + 5 -= 17 + 5 -= 22 +* 输入: ["10", "6", "9", "3", "+", "-11", " * ", "/", " * ", "17", "+", "5", "+"] +* 输出: 22 +* 解释:该算式转化为常见的中缀算术表达式为: + ((10 * (6 / ((9 + 3) * -11))) + 17) + 5 += ((10 * (6 / (12 * -11))) + 17) + 5 += ((10 * (6 / -132)) + 17) + 5 += ((10 * 0) + 17) + 5 += (0 + 17) + 5 += 17 + 5 += 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++代码如下: @@ -126,7 +125,7 @@ public: -## 其他语言版本 +# 其他语言版本 java: @@ -153,23 +152,20 @@ public class EvalRPN { } return stack.pop(); } - - private boolean isOpe(String s) { return s.length() == 1 && s.charAt(0) <'0' || s.charAt(0) >'9'; } - private int stoi(String s) { return Integer.valueOf(s); } - public static void main(String[] args) { new EvalRPN().evalRPN(new String[] {"10","6","9","3","+","-11","*","/","*","17","+","5","+"}); } } ``` + Go: ```Go func evalRPN(tokens []string) int { diff --git a/problems/0216.组合总和III.md b/problems/0216.组合总和III.md index 67e67ad0..15220464 100644 --- a/problems/0216.组合总和III.md +++ b/problems/0216.组合总和III.md @@ -34,7 +34,7 @@ 本题就是在[1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合。 -相对于[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),无非就是多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。 +相对于[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),无非就是多了一个限制,本题是要找到和为n的k个数的组合,而整个集合已经是固定的了[1,...,9]。 想到这一点了,做过[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)之后,本题是简单一些了。 @@ -53,7 +53,7 @@ * **确定递归函数参数** -和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。 +和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)一样,依然需要一维数组path来存放符合条件的结果,二维数组result来存放结果集。 这里我依然定义path 和 result为全局变量。 @@ -103,7 +103,7 @@ if (path.size() == k) { * **单层搜索过程** -本题和[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9],所以for循环固定i<=9 +本题和[77. 组合](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)区别之一就是集合固定的就是9个数[1,...,9],所以for循环固定i<=9 如图: ![216.组合总和III](https://img-blog.csdnimg.cn/20201123195717975.png) @@ -227,6 +227,44 @@ public: Java: + +模板方法 +```java +class Solution { + List> result = new ArrayList<>(); + LinkedList path = new LinkedList<>(); + + public List> combinationSum3(int k, int n) { + backTracking(n, k, 1, 0); + return result; + } + + private void backTracking(int targetSum, int k, int startIndex, int sum) { + // 减枝 + if (sum > targetSum) { + return; + } + + if (path.size() == k) { + if (sum == targetSum) result.add(new ArrayList<>(path)); + return; + } + + // 减枝 9 - (k - path.size()) + 1 + for (int i = startIndex; i <= 9 - (k - path.size()) + 1; i++) { + path.add(i); + sum += i; + backTracking(targetSum, k, i + 1, sum); + //回溯 + path.removeLast(); + //回溯 + sum -= i; + } + } +} +``` + +其他方法 ```java class Solution { List> res = new ArrayList<>(); diff --git a/problems/0239.滑动窗口最大值.md b/problems/0239.滑动窗口最大值.md index ad54a940..bb89a1ac 100644 --- a/problems/0239.滑动窗口最大值.md +++ b/problems/0239.滑动窗口最大值.md @@ -22,13 +22,13 @@ https://leetcode-cn.com/problems/sliding-window-maximum/ 你能在线性时间复杂度内解决此题吗?   - + 提示: -1 <= nums.length <= 10^5 --10^4 <= nums[i] <= 10^4 -1 <= k <= nums.length +* 1 <= nums.length <= 10^5 +* -10^4 <= nums[i] <= 10^4 +* 1 <= k <= nums.length @@ -84,7 +84,7 @@ public: 动画如下: -![239.滑动窗口最大值](https://code-thinking.cdn.bcebos.com/gifs/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.gif) +![239.滑动窗口最大值](https://code-thinking.cdn.bcebos.com/gifs/239.滑动窗口最大值.gif) 对于窗口里的元素{2, 3, 5, 1 ,4},单调队列里只维护{5, 4} 就够了,保持单调队列里单调递减,此时队列出口元素就是窗口里最大元素。 @@ -100,11 +100,11 @@ public: 为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下: -![239.滑动窗口最大值-2](https://code-thinking.cdn.bcebos.com/gifs/239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC-2.gif) +![239.滑动窗口最大值-2](https://code-thinking.cdn.bcebos.com/gifs/239.滑动窗口最大值-2.gif) 那么我们用什么数据结构来实现这个单调队列呢? -使用deque最为合适,在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/VZRjOccyE09aE-MgLbCMjQ)中,我们就提到了常用的queue在没有指定容器的情况下,deque就是默认底层容器。 +使用deque最为合适,在文章[栈与队列:来看看栈和队列不为人知的一面](https://mp.weixin.qq.com/s/HCXfQ_Bhpi63YaX0ZRSnAQ)中,我们就提到了常用的queue在没有指定容器的情况下,deque就是默认底层容器。 基于刚刚说过的单调队列pop和push的规则,代码不难实现,如下: @@ -201,9 +201,7 @@ public: - - -## 其他语言版本 +# 其他语言版本 Java: @@ -337,36 +335,6 @@ class Solution: Go: -```go -func maxSlidingWindow(nums []int, k int) []int { - var queue []int - var rtn []int - - for f := 0; f < len(nums); f++ { - //维持队列递减, 将 k 插入合适的位置, queue中 <=k 的 元素都不可能是窗口中的最大值, 直接弹出 - for len(queue) > 0 && nums[f] > nums[queue[len(queue)-1]] { - queue = queue[:len(queue)-1] - } - // 等大的后来者也应入队 - if len(queue) == 0 || nums[f] <= nums[queue[len(queue)-1]] { - queue = append(queue, f) - } - - if f >= k - 1 { - rtn = append(rtn, nums[queue[0]]) - //弹出离开窗口的队首 - if f - k + 1 == queue[0] { - queue = queue[1:] - } - } - } - - return rtn - -} - -``` - ```go // 封装单调队列的方式解题 type MyQueue struct { diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index cf537088..302ae789 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -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]; +} +``` + ----------------------- diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 97059e4a..3c7e34d8 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -33,6 +33,9 @@ ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 这道题目还是很难的,之前我们用回溯法解决了如下问题:[组合问题](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ),[分割问题](https://mp.weixin.qq.com/s/v--VmA8tp9vs4bXCqHhBuA),[子集问题](https://mp.weixin.qq.com/s/NNRzX-vJ_pjK4qxohd_LtA),[排列问题](https://mp.weixin.qq.com/s/SCOjeMX1t41wcvJq49GhMw)。 直觉上来看 这道题和回溯法没有什么关系,更像是图论中的深度优先搜索。 diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index d0394706..2ee009b3 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -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]; +}; +``` + ----------------------- diff --git a/problems/0406.根据身高重建队列.md b/problems/0406.根据身高重建队列.md index 946c41ce..2b447da4 100644 --- a/problems/0406.根据身高重建队列.md +++ b/problems/0406.根据身高重建队列.md @@ -43,9 +43,9 @@ 本题有两个维度,h和k,看到这种题目一定要想如何确定一个维度,然后在按照另一个维度重新排列。 -其实如果大家认真做了[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),就会发现和此题有点点的像。 +其实如果大家认真做了[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ),就会发现和此题有点点的像。 -在[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。 +在[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)我就强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。 **如果两个维度一起考虑一定会顾此失彼**。 @@ -161,11 +161,11 @@ public: ## 总结 -关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。 +关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)。 **其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼**。 -这道题目可以说比[贪心算法:分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。 +这道题目可以说比[135. 分发糖果](https://mp.weixin.qq.com/s/8MwlgFfvaNYmjGwjuMlETQ)难不少,其贪心的策略也是比较巧妙。 最后我给出了两个版本的代码,可以明显看是使用C++中的list(底层链表实现)比vector(数组)效率高得多。 diff --git a/problems/0435.无重叠区间.md b/problems/0435.无重叠区间.md index 23ee9f94..37bb0b5d 100644 --- a/problems/0435.无重叠区间.md +++ b/problems/0435.无重叠区间.md @@ -122,9 +122,9 @@ public: ## 补充 -本题其实和[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 +本题其实和[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)非常像,弓箭的数量就相当于是非交叉区间的数量,只要把弓箭那道题目代码里射爆气球的判断条件加个等号(认为[0,1][1,2]不是相邻区间),然后用总区间数减去弓箭数量 就是要移除的区间数量了。 -把[贪心算法:用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,就可以AC本题。 +把[452.用最少数量的箭引爆气球](https://mp.weixin.qq.com/s/HxVAJ6INMfNKiGwI88-RFw)代码稍做修改,就可以AC本题。 ```C++ class Solution { diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index fd925f76..6b73c080 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -244,6 +244,35 @@ func max(a,b int) int { } ``` +Javascript: +```javascript +const findMaxForm = (strs, m, n) => { + const dp = Array.from(Array(m+1), () => Array(n+1).fill(0)); + let numOfZeros, numOfOnes; + + for(let str of strs) { + numOfZeros = 0; + numOfOnes = 0; + + for(let c of str) { + if (c === '0') { + numOfZeros++; + } else { + numOfOnes++; + } + } + + for(let i = m; i >= numOfZeros; i--) { + for(let j = n; j >= numOfOnes; j--) { + dp[i][j] = Math.max(dp[i][j], dp[i - numOfZeros][j - numOfOnes] + 1); + } + } + } + + return dp[m][n]; +}; +``` + ----------------------- diff --git a/problems/0491.递增子序列.md b/problems/0491.递增子序列.md index 5538a2c9..103b8b10 100644 --- a/problems/0491.递增子序列.md +++ b/problems/0491.递增子序列.md @@ -28,6 +28,9 @@ ## 思路 +**如果对回溯算法基础还不了解的话,我还特意录制了一期视频:[带你学透回溯算法(理论篇)](https://www.bilibili.com/video/BV1cy4y167mM/)** 可以结合题解和视频一起看,希望对大家理解回溯算法有所帮助。 + + 这个递增子序列比较像是取有序的子集。而且本题也要求不能有相同的递增子序列。 这又是子集,又是去重,是不是不由自主的想起了刚刚讲过的[回溯算法:求子集问题(二)](https://mp.weixin.qq.com/s/WJ4JNDRJgsW3eUN72Hh3uQ)。 @@ -254,6 +257,34 @@ class Solution: 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;i0&&nums[i] { + + const sum = nums.reduce((a, b) => a+b); + + if(target > sum) { + return 0; + } + + if((target + sum) % 2) { + return 0; + } + + const halfSum = (target + sum) / 2; + nums.sort((a, b) => a - b); + + let dp = new Array(halfSum+1).fill(0); + dp[0] = 1; + + for(let i = 0; i < nums.length; i++) { + for(let j = halfSum; j >= nums[i]; j--) { + dp[j] += dp[j - nums[i]]; + } + } + + return dp[halfSum]; +}; +``` + ----------------------- diff --git a/problems/0496.下一个更大元素I.md b/problems/0496.下一个更大元素I.md new file mode 100644 index 00000000..0404e434 --- /dev/null +++ b/problems/0496.下一个更大元素I.md @@ -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 umap; // key:下表元素,value:下表 +for (int i = 0; i < nums1.size(); i++) { + umap[nums1[i]] = i; +} +``` + +使用单调栈,首先要想单调栈是从大到小还是从小到大。 + +本题和739. 每日温度是一样的。 + +栈头到栈底的顺序,要从小到大,也就是保持栈里的元素为递增顺序。只要保持递增,才能找到右边第一个比自己大的元素。 + +可能这里有一些同学不理解,那么可以自己尝试一下用递减栈,能不能求出来。其实递减栈就是求右边第一个比自己小的元素了。 + + +接下来就要分析如下三种情况,一定要分析清楚。 + +1. 情况一:当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况 + +此时满足递增栈(栈头到栈底的顺序),所以直接入栈。 + +2. 情况二:当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况 + +如果相等的话,依然直接入栈,因为我们要求的是右边第一个比自己大的元素,而不是大于等于! + +3. 情况三:当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况 + +此时如果入栈就不满足递增栈了,这也是找到右边第一个比自己大的元素的时候。 + +判断栈顶元素是否在nums1里出现过,(注意栈里的元素是nums2的元素),如果出现过,开始记录结果。 + +记录结果这块逻辑有一点小绕,要清楚,此时栈顶元素在nums2中右面第一个大的元素是nums2[i]即当前遍历元素。 + +代码如下: + +```C++ +while (!st.empty() && nums2[i] > nums2[st.top()]) { + if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 + int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 + result[index] = nums2[i]; + } + st.pop(); +} +st.push(i); +``` + +以上分析完毕,C++代码如下: + + +```C++ +// 版本一 +class Solution { +public: + vector nextGreaterElement(vector& nums1, vector& nums2) { + stack st; + vector result(nums1.size(), -1); + if (nums1.size() == 0) return result; + + unordered_map umap; // key:下表元素,value:下表 + for (int i = 0; i < nums1.size(); i++) { + umap[nums1[i]] = i; + } + st.push(0); + for (int i = 1; i < nums2.size(); i++) { + if (nums2[i] < nums2[st.top()]) { // 情况一 + st.push(i); + } else if (nums2[i] == nums2[st.top()]) { // 情况二 + st.push(i); + } else { // 情况三 + while (!st.empty() && nums2[i] > nums2[st.top()]) { + if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 + int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 + result[index] = nums2[i]; + } + st.pop(); + } + st.push(i); + } + } + return result; + } +}; +``` + +针对版本一,进行代码精简后,代码如下: + + +```C++ +// 版本二 +class Solution { +public: + vector nextGreaterElement(vector& nums1, vector& nums2) { + stack st; + vector result(nums1.size(), -1); + if (nums1.size() == 0) return result; + + unordered_map umap; // key:下表元素,value:下表 + for (int i = 0; i < nums1.size(); i++) { + umap[nums1[i]] = i; + } + st.push(0); + for (int i = 1; i < nums2.size(); i++) { + while (!st.empty() && nums2[i] > nums2[st.top()]) { + if (umap.count(nums2[st.top()]) > 0) { // 看map里是否存在这个元素 + int index = umap[nums2[st.top()]]; // 根据map找到nums2[st.top()] 在 nums1中的下表 + result[index] = nums2[i]; + } + st.pop(); + } + st.push(i); + } + return result; + } +}; +``` + +精简的代码是直接把情况一二三都合并到了一起,其实这种代码精简是精简,但思路不是很清晰。 + +建议大家把情况一二三想清楚了,先写出版本一的代码,然后在其基础上在做精简! + +## 其他语言版本 + +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 +``` + diff --git a/problems/0518.零钱兑换II.md b/problems/0518.零钱兑换II.md index 08043ea3..3f907da9 100644 --- a/problems/0518.零钱兑换II.md +++ b/problems/0518.零钱兑换II.md @@ -243,6 +243,22 @@ func change(amount int, coins []int) int { } ``` +Javascript: +```javascript +const change = (amount, coins) => { + let dp = Array(amount + 1).fill(0); + dp[0] = 1; + + for(let i =0; i < coins.length; i++) { + for(let j = coins[i]; j <= amount; j++) { + dp[j] += dp[j - coins[i]]; + } + } + + return dp[amount]; +} +``` + ----------------------- diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md index f325df64..19b58bd3 100644 --- a/problems/0617.合并二叉树.md +++ b/problems/0617.合并二叉树.md @@ -319,7 +319,7 @@ Python: # self.val = val # self.left = left # self.right = right -//递归法*前序遍历 +# 递归法*前序遍历 class Solution: def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: if not root1: return root2 // 如果t1为空,合并之后就应该是t2 @@ -328,6 +328,32 @@ class Solution: root1.left = self.mergeTrees(root1.left , root2.left) //左 root1.right = self.mergeTrees(root1.right , root2.right) //右 return root1 //root1修改了结构和数值 + +# 迭代法-覆盖原来的树 +class Solution: + def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode: + if not root1: return root2 + if not root2: return root1 + # 迭代,将树2覆盖到树1 + queue1 = [root1] + queue2 = [root2] + root = root1 + while queue1 and queue2: + root1 = queue1.pop(0) + root2 = queue2.pop(0) + root1.val += root2.val + if not root1.left: # 如果树1左儿子不存在,则覆盖后树1的左儿子为树2的左儿子 + root1.left = root2.left + elif root1.left and root2.left: + queue1.append(root1.left) + queue2.append(root2.left) + + if not root1.right: # 同理,处理右儿子 + root1.right = root2.right + elif root1.right and root2.right: + queue1.append(root1.right) + queue2.append(root2.right) + return root ``` Go: diff --git a/problems/0739.每日温度.md b/problems/0739.每日温度.md index e72fd6a4..8ad79fe3 100644 --- a/problems/0739.每日温度.md +++ b/problems/0739.每日温度.md @@ -211,7 +211,24 @@ Java: } ``` 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: > 暴力法 diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index 85bc7e42..2a99c97f 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -99,10 +99,17 @@ leetcode上没有纯01背包的问题,都是01背包应用方面的题目, 状态转移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推导出来,那么i为0的时候就一定要初始化。 -dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 +dp[0][j],即:i为0,存放编号0的物品的时候,各个容量的背包所能存放的最大价值。 -代码如下: -``` +那么很明显当 j < weight[0]的时候,dp[0][j] 应该是 0,因为背包容量比编号0的物品重量还小。 + +当j >= weight[0]是,dp[0][j] 应该是value[0],因为背包容量放足够放编号0物品。 + +代码初始化如下: +``` +for (int j = 0 ; j < weight[0]; j++) { // 当然这一步,如果把dp数组预先初始化为0了,这一步就可以省略,但很多同学应该没有想清楚这一点。 + dp[0][j] = 0; +} // 正序遍历 for (int j = weight[0]; j <= bagWeight; j++) { dp[0][j] = value[0]; @@ -116,14 +123,11 @@ for (int j = weight[0]; j <= bagWeight; j++) { dp[0][j] 和 dp[i][0] 都已经初始化了,那么其他下标应该初始化多少呢? +其实从递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出dp[i][j] 是又左上方数值推导出来了,那么 其他下标初始为什么数值都可以,因为都会被覆盖。 -dp[i][j]在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,因为0就是最小的了,不会影响取最大价值的结果。 +初始-1,初始-2,初始100,都可以! -如果题目给的价值有负数,那么非0下标就要初始化为负无穷了。例如:一个物品的价值是-2,但对应的位置依然初始化为0,那么取最大值的时候,就会取0而不是-2了,所以要初始化为负无穷。 - -而背包问题的物品价值都是正整数,所以初始化为0,就可以了。 - -**这样才能让dp数组在递归公式的过程中取最大的价值,而不是被初始值覆盖了**。 +但只不过一开始就统一把dp数组统一初始为0,更方便一些。 如图: @@ -159,7 +163,7 @@ for (int j = weight[0]; j <= bagWeight; j++) { // weight数组的大小 就是物品个数 for(int i = 1; i < weight.size(); i++) { // 遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 - if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化 + if (j < weight[i]) dp[i][j] = dp[i - 1][j]; else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); } diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index a5a708cf..1ad09c4b 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -176,9 +176,48 @@ int main() { ## 其他语言版本 - 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: ```python3