mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-06 23:28:29 +08:00
Update
This commit is contained in:
@ -1,28 +1,47 @@
|
||||
|
||||
## 题目地址
|
||||
|
||||
https://leetcode-cn.com/problems/valid-parentheses/
|
||||
https://leetcode-cn.com/problems/valid-parentheses/
|
||||
|
||||
## 思路
|
||||
|
||||
### 题外话
|
||||
括号匹配是使用栈解决的经典问题。
|
||||
|
||||
题意其实就像我们在写代码的过程中,要求括号的顺序是一样的,有左括号,响应的位置必须要有右括号,如果还记得编译原理的话,编译器在 词法分析的过程中处理 括号,花括号等这个符号的逻辑,也是使用了栈这种数据结构。
|
||||
|
||||
所以栈在计算机领域中应用是非常广泛的。 有的同学可以经常会想学的这些数据结构有什么用,也开发不了什么软件,大多数同学说的软件应该都是可视化的软件例如APP之类的,那都是非常上层的应用了,底层很多功能的实现都是基础的数据结构和算法。 这里我就不过多展开了,我们先来看题。
|
||||
|
||||
### 进入正题
|
||||
|
||||
由于栈结构的特殊性,非常适合做对称匹配类的题目。首先我们要弄清楚,字符串里的括号不匹配有几种情况。
|
||||
|
||||
一些同学,在面试中看到这种题目上来就开始写代码,建议要写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。 会给面试官留下不好的印象。
|
||||
|
||||
我们先来分析一下 这里有三种不匹配的情况,
|
||||
|
||||
第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
|
||||
第二种情况,括号没有多余,但是 括号的类型没有匹配上。
|
||||
第三种情况,字符串里右方向的括号多余了,所以不匹配。
|
||||
1. 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
|
||||

|
||||
2. 第二种情况,括号没有多余,但是 括号的类型没有匹配上。
|
||||

|
||||
3. 第三种情况,字符串里右方向的括号多余了,所以不匹配。
|
||||

|
||||
|
||||
我们的代码只要覆盖了这三种不匹配的情况,基本就不会出问题,可以看出 动手之前分析好题目的重要性。
|
||||
|
||||
动画如下:
|
||||
|
||||
<video src='../video/20.有效括号.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
|
||||
|
||||
|
||||
第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false
|
||||
|
||||
第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false
|
||||
|
||||
第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false
|
||||
|
||||
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈也是空的,就说明全都匹配了。
|
||||
|
||||
接下来我们来看一下代码。
|
||||
|
||||
## C++代码
|
||||
|
@ -3,13 +3,7 @@ https://leetcode-cn.com/problems/binary-tree-inorder-traversal/
|
||||
|
||||
## 思路
|
||||
|
||||
|
||||
|
||||
|
||||
094.二叉树的中序遍历
|
||||
144.二叉树的前序遍历
|
||||
145.二叉树的后序遍历
|
||||
建议一起做一下
|
||||
详细题解请看这篇:[一文学通二叉树前中后序递归法与迭代法](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)
|
||||
|
||||
## C++代码
|
||||
|
||||
|
@ -3,7 +3,19 @@ https://leetcode-cn.com/problems/binary-tree-level-order-traversal/
|
||||
|
||||
## 思路
|
||||
|
||||
使用队列实现广度优先遍历
|
||||
我们之前讲过了,二叉树的深度优先遍历:[一文学通二叉树前中后序递归法与迭代法](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)里面有前中后序遍历的方式,前中后序分辨可以使用递归和迭代的方法来实现,接下来我们再来介绍二叉树的另一种遍历方式,也就是层序遍历。
|
||||
|
||||
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
|
||||
|
||||
需要借用一个辅助数据结构队列来实现,**队列先进后出,符合一层一层遍历的逻辑,而是用栈先进先出适合模拟深度优先遍历也就是递归的逻辑。**
|
||||
|
||||
使用队列实现广度优先遍历,动画如下:
|
||||
|
||||
<video src='../video/102二叉树的层序遍历.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
|
||||
|
||||
这样就实现了层序从左到右遍历二叉树。
|
||||
|
||||
代码如下:这份代码也可以作为二叉树层序遍历的模板。
|
||||
|
||||
## C++代码
|
||||
|
||||
|
@ -3,25 +3,27 @@ https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
|
||||
|
||||
## 思路
|
||||
|
||||
这篇文章,彻底讲清楚应该如何写递归,并给出了前中后序三种不同的迭代法,最后分析为什么迭代法代码风格不能统一,最后给出统一的前中后序迭代法的代码,帮大家彻底吃透二叉树的深度优先遍历。
|
||||
这篇文章,**彻底讲清楚应该如何写递归,并给出了前中后序三种不同的迭代法,然后分析迭代法的代码风格为什么没有统一,最后给出统一的前中后序迭代法的代码,帮大家彻底吃透二叉树的深度优先遍历。**
|
||||
|
||||
这里想帮大家一下,明确一下二叉树的遍历规则
|
||||
|
||||
* 二叉树深度优先遍历
|
||||
* 前序遍历: 144.二叉树的前序遍历
|
||||
* 后序遍历: 145.二叉树的后序遍历
|
||||
* 中序遍历: 094.二叉树的中序遍历
|
||||
* 前序遍历: [0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)
|
||||
* 后序遍历: [0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md)
|
||||
* 中序遍历: [0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md)
|
||||
* 二叉树广度优先遍历
|
||||
* 层序遍历:0145.二叉树的后序遍历
|
||||
* 层序遍历:[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
|
||||
|
||||
这几道题目建议大家都做一下,本题解先只写二叉树深度优先遍历,二叉树广度优先遍历请看[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
|
||||
这几道题目建议大家都做一下,本题解先只写二叉树深度优先遍历,二叉树广度优先遍历请看题解[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md)
|
||||
|
||||
这里想帮大家一下,明确一下二叉树的遍历规则:
|
||||
|
||||

|
||||
|
||||
以上述中,前中后序遍历顺序如下
|
||||
前序遍历(中左右):5 4 1 2 6 7 8
|
||||
中序遍历(左中右):1 4 2 5 7 6 8
|
||||
后序遍历(左右中):1 2 4 7 8 6 5
|
||||
以上述中,前中后序遍历顺序如下:
|
||||
|
||||
* 前序遍历(中左右):5 4 1 2 6 7 8
|
||||
* 中序遍历(左中右):1 4 2 5 7 6 8
|
||||
* 后序遍历(左右中):1 2 4 7 8 6 5
|
||||
|
||||
### 递归法
|
||||
|
||||
@ -31,15 +33,11 @@ https://leetcode-cn.com/problems/binary-tree-preorder-traversal/
|
||||
|
||||
|
||||
1. **确定递归函数的参数和返回值:**
|
||||
|
||||
确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
|
||||
|
||||
|
||||
2. **确定终止条件:**
|
||||
|
||||
写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
|
||||
|
||||
|
||||
3. **确定单层递归的逻辑:**
|
||||
确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
|
||||
|
||||
@ -67,11 +65,9 @@ traversal(cur->left, vec); // 左
|
||||
traversal(cur->right, vec); // 右
|
||||
```
|
||||
|
||||
单层递归的逻辑就是按照中左右的顺序来处理的
|
||||
单层递归的逻辑就是按照中左右的顺序来处理的,这样二叉树的前序遍历,基本就写完了,在看一下完整代码:
|
||||
|
||||
这样二叉树的前序遍历,基本就写完了,在看一下完整代码:
|
||||
|
||||
前序遍历
|
||||
前序遍历:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
@ -90,7 +86,7 @@ public:
|
||||
};
|
||||
```
|
||||
|
||||
中序遍历
|
||||
中序遍历:
|
||||
|
||||
```
|
||||
void traversal(TreeNode* cur, vector<int>& vec) {
|
||||
@ -101,7 +97,7 @@ public:
|
||||
}
|
||||
```
|
||||
|
||||
后序遍历
|
||||
后序遍历:
|
||||
|
||||
```
|
||||
void traversal(TreeNode* cur, vector<int>& vec) {
|
||||
@ -119,6 +115,7 @@ public:
|
||||
那么接下来我先带大家看一看其中的根本原因,其实是可以针对三种遍历方式,使用迭代法可以写出统一风格的代码。
|
||||
|
||||
前序遍历(迭代法)不难写出如下代码:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -141,7 +138,7 @@ public:
|
||||
|
||||
这时会发现貌似使用迭代法写出先序遍历并不难,确实不难,但难却难在,我们再用迭代法写中序遍历的时候,发现套路又不一样了,目前的这个逻辑无法直接应用到中序遍历上。
|
||||
|
||||
#### 为什么迭代法不容易写出统一风格的代码
|
||||
#### 前后中遍历迭代法不统一的写法
|
||||
|
||||
为了解释清楚,我说明一下 刚刚在迭代的过程中,其实我们有两个操作,**一个是处理:将元素放进result数组中,一个是访问:遍历节点。**
|
||||
|
||||
@ -206,15 +203,15 @@ public:
|
||||
|
||||
```
|
||||
|
||||
此时我们实现了前后中遍历的三种迭代法,是不是发现迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。
|
||||
此时我们实现了前后中遍历的三种迭代法,**是不是发现迭代法实现的先中后序,其实风格也不是那么统一,除了先序和后序,有关联,中序完全就是另一个风格了,一会用栈遍历,一会又用指针来遍历。**
|
||||
|
||||
重头戏来了,接下来介绍一下统一写法。
|
||||
**重头戏来了,接下来介绍一下统一写法。**
|
||||
|
||||
#### 迭代法统一写法
|
||||
#### 前后中遍历迭代法统一的写法
|
||||
|
||||
我们以中序遍历为例,之前说使用栈的话,**无法同时解决处理过程和访问过程不一致的情况**,那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记,标记就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。
|
||||
|
||||
代码如下:
|
||||
中序遍历代码如下:
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -248,12 +245,66 @@ public:
|
||||
|
||||
<video src='../video/中序遍历迭代(统一写法).mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
|
||||
|
||||
前序遍历代码如下:
|
||||
|
||||
大家在看一下具体代码实现
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
vector<int> preorderTraversal(TreeNode* root) {
|
||||
vector<int> result;
|
||||
stack<TreeNode*> st;
|
||||
if (root != NULL) st.push(root);
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top();
|
||||
if (node != NULL) {
|
||||
st.pop();
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
st.push(node); // 中
|
||||
st.push(NULL);
|
||||
} else {
|
||||
st.pop();
|
||||
node = st.top();
|
||||
st.pop();
|
||||
result.push_back(node->val);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
接下来给大家介绍一种统一的写法。
|
||||
后续遍历代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
vector<int> postorderTraversal(TreeNode* root) {
|
||||
vector<int> result;
|
||||
stack<TreeNode*> st;
|
||||
if (root != NULL) st.push(root);
|
||||
while (!st.empty()) {
|
||||
TreeNode* node = st.top();
|
||||
if (node != NULL) {
|
||||
st.pop();
|
||||
st.push(node); // 中
|
||||
st.push(NULL);
|
||||
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
|
||||
} else {
|
||||
st.pop();
|
||||
node = st.top();
|
||||
st.pop();
|
||||
result.push_back(node->val);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
我们再来看一下代码。
|
||||
## C++代码
|
||||
|
||||
### 递归
|
||||
@ -296,7 +347,7 @@ public:
|
||||
```
|
||||
|
||||
### 栈 通用模板
|
||||
详细代码注释看 [0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md)
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
@ -308,9 +359,9 @@ public:
|
||||
TreeNode* node = st.top();
|
||||
if (node != NULL) {
|
||||
st.pop();
|
||||
if (node->right) st.push(node->right);
|
||||
if (node->left) st.push(node->left);
|
||||
st.push(node);
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
st.push(node); // 中
|
||||
st.push(NULL);
|
||||
} else {
|
||||
st.pop();
|
||||
|
@ -3,11 +3,7 @@ https://leetcode-cn.com/problems/binary-tree-postorder-traversal/
|
||||
|
||||
## 思路
|
||||
|
||||
094.二叉树的中序遍历
|
||||
144.二叉树的前序遍历
|
||||
145.二叉树的后序遍历
|
||||
|
||||
建议一起做一下
|
||||
详细题解请看这篇:[一文学通二叉树前中后序递归法与迭代法](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md)
|
||||
|
||||
## C++代码
|
||||
|
||||
@ -71,11 +67,11 @@ public:
|
||||
TreeNode* node = st.top();
|
||||
if (node != NULL) {
|
||||
st.pop();
|
||||
st.push(node); // 中
|
||||
st.push(node); // 中
|
||||
st.push(NULL);
|
||||
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
if (node->right) st.push(node->right); // 右
|
||||
if (node->left) st.push(node->left); // 左
|
||||
|
||||
} else {
|
||||
st.pop();
|
||||
|
@ -4,11 +4,11 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/
|
||||
|
||||
## 思路
|
||||
|
||||
这道题目,主要要学会使用一种哈希数据结构,unordered_set,这个数据结构可以解决很多类似的问题
|
||||
这道题目,主要要学会使用一种哈希数据结构,unordered_set,这个数据结构可以解决很多类似的问题。
|
||||
|
||||
注意题目特意说明:输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序
|
||||
注意题目特意说明:**输出结果中的每个元素一定是唯一的,也就是说输出的结果的去重的, 同时可以不考虑输出结果的顺序**
|
||||
|
||||
这道题用暴力的解法时间复杂度是O(n^2),这种解法面试官一定不会满意,那我们看看使用哈希法进一步优化
|
||||
这道题用暴力的解法时间复杂度是O(n^2),这种解法面试官一定不会满意,那我们看看使用哈希法进一步优化。
|
||||
|
||||
那么可以发现,貌似用数组做哈希表可以解决这道题目,把nums1的元素,映射到哈希数组的下表上,然后在遍历nums2的时候,判断是否出现过就可以了。
|
||||
|
||||
@ -22,7 +22,10 @@ https://leetcode-cn.com/problems/intersection-of-two-arrays/
|
||||
* std::multiset
|
||||
* std::unordered_set
|
||||
|
||||
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,我们并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set
|
||||
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,我们并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
|
||||
|
||||
思路如图所示:
|
||||

|
||||
|
||||
## 代码
|
||||
```
|
||||
|
@ -4,7 +4,15 @@ https://leetcode-cn.com/problems/design-linked-list/
|
||||
|
||||
## 思路
|
||||
|
||||
这道题目设计链表的五个接口
|
||||
如果链表的基础知识还不太懂,可以看这篇文章:[关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ)
|
||||
|
||||
删除链表节点:
|
||||

|
||||
|
||||
添加链表节点:
|
||||

|
||||
|
||||
这道题目设计链表的五个接口:
|
||||
* 获取链表第index个节点的数值
|
||||
* 在链表的最前面插入一个节点
|
||||
* 在链表的最后面插入一个节点
|
||||
@ -13,6 +21,14 @@ https://leetcode-cn.com/problems/design-linked-list/
|
||||
|
||||
可以说这五个接口,已经覆盖了链表的常见操作,是练习链表操作非常好的一道题目
|
||||
|
||||
**链表操作的两种方式:**
|
||||
|
||||
1. 直接使用原来的链表来进行操作。
|
||||
2. 设置一个虚拟头结点在进行操作。
|
||||
|
||||
下面我采用的设置一个虚拟头结点(这样更方便一些,大家看代码就会感受出来)。
|
||||
|
||||
|
||||
## 代码
|
||||
```
|
||||
class MyLinkedList {
|
||||
|
Reference in New Issue
Block a user