栈与队列
4.括号匹配()[] {}
括号匹配这种具有对称性的问题适合先进后出的栈数据结构。有两种思路
1,左括号入栈遇到右括号逐一比较栈顶元素是否匹配(三种对应情况),匹配则pop,不匹配则false
※2,遇到左括号,入栈相应的右括号,遇到右括号则只需要判断是否与栈顶相同(一种情况),匹配则pop,不匹配则false
5.删除字符串中所有相邻重复项
abbaca -> ca
类似括号匹配,用栈来压入字符,最后需要将栈内元素转为字符输出,注意栈先进后出特性,因此需要反转!
值得注意的地方有:
1.c++语法或逻辑若判断第一个为正确,则不进行第二个判断,因此需要先判断是否为空才能访问栈顶元素。
string removeDuplicates(string s) {
//匹配问题用栈解决,最后需要反转
stack<char> sta;
for(int i = 0; i < s.size(); i++){
if( sta.empty()||s[i] != sta.top() ) //不能写成 s[i] != sta.top()|| sta.empty()
sta.push(s[i]);
else
sta.pop();
}
string result = "";
while(!sta.empty()){
result += sta.top();
sta.pop();
}
reverse(result.begin(), result.end());
return result;
}
2.可以直接用字符串做栈,省去转换为字符串并进行反转的操作
-
push_back(char c)
:将字符c
添加到字符串的末尾,即相当于栈的入栈操作,将元素压入栈顶。 -
pop_back()
:从字符串的末尾删除一个字符,即相当于栈的出栈操作,将栈顶元素弹出。 -
back()
函数是用来访问字符串的最后一个字符的,即栈顶元素。得到的 的
string removeDuplicates(string S) {
//用字符串直接作为栈
string result;
for(char s : S){
if( result.empty() || s!= result.back() )
result.push_back(s);
else
result.pop_back();
}
return result;
}
6.逆波兰表达式求值
栈的应用。本题后台数据为long long类型。
tokens[i]
是一个运算符("+"
、"-"
、"*"
或 "/"
),或是在范围 [-200, 200]
内的一个整数
※需要注意的是判断数字和判断运算符的逻辑。显然,判断为运算符较简单。
判断数字:bool isdigit()函数
for(string s : tokens){//使用string类型来遍历vector<string>容器
//判断是否为数字isdigit():字符串开头是数字或者负号开头,后面跟数字 则为一个数字
if(isdigit(s[0]) || ( s.size() > 1 && s[0] == '-' && isdigit(s[1]) ))
nst.push(stoll(s)); //stoll()函数将字符串转换为长整型数字
判断运算符:
for(string s : tokens){//使用string类型来遍历vector<string>容器
if(s == "+" || s == "-" || s == "*" || s == "/") { //判断是否为运算符
※另外,可以用switch代替if-else分支.
switch()里面可以是整型或能隐式转换为整型的类型。case后面跟随相应的整型或者字符常量
if(s == "+" || s == "-" || s == "*" || s == "/") {
long long rnum = nst.top();
nst.pop();
long long lnum = nst.top();
nst.pop();
switch(s[0]){ //s本身是一个字符串,不是字符常量,不能直接用于switch语句
case '+' : //case后面需要跟随整数或字符常量,而不是字符串"+"
nst.push(lnum + rnum);
break;
case '-' :
nst.push(lnum - rnum);
break;
case '*' :
nst.push(lnum * rnum);
break;
case '/' :
nst.push(lnum / rnum);
break;
default:
break;
7.滑动窗口最大值
滑动窗口是队列的典型应用,可以选择单调队列(递增或者递减)来实现随着滑动窗口移动找出每个窗口的最大值。
(c++的STL库里stack的底层可以是vector,list,deque。常用的SGI STL栈和队列的缺省情况下底层结构都用deque实现) deque:双向队列
nums = [1,3,-1,-3,5,3,6,7], k = 3
滑动窗口的位置 最大值 --------------- ----- [1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
解决思路:如果实现一个单调递减的队列,每次push(int value)(模拟窗口前移时进入新元素)后都保持让队头元素=窗口最大元素、pop(int value)(模拟窗口前移时去除旧元素),在窗口移动完成后输出队头元素则可以解决问题。
以类的形式实现单调队列:
class MyQueue {
public:
deque<int> que; //用deque来实现单调递减队列
//弹出前判断队列是否为空、
//窗口后挪移除元素时,看是否弹出值等于队头元素如果是则弹出,需要重新选择最大的
void pop(int value){
if(!que.empty() && value == que.front())
que.pop_front();
}
//若push的数值大于当前队尾元素,则弹出队列元素直到push的值小于等于队尾元素
void push(int value){
while(!que.empty() && value > que.back())
que.pop_back();
que.push_back(value);
}
//得到队头(最大)元素
int getfront(){
return que.front();
}
};
滑动窗口使用:
for (int i = k; i < nums.size(); i++) {
que.pop(nums[i - k]); // 滑动窗口移除最前面元素
que.push(nums[i]); // 滑动窗口前加入最后面的元素
result.push_back(que.front()); // 记录对应的最大值
}
8.前k个高频元素
……下次再写罢
二叉树
1.二叉树的递归遍历
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,
※递归三要素:1.上下层需要传递的参数和函数返回值 2.终止条件 3.每层都需要做的操作
二叉树传递参数一般为节点指针,此题需要将访问元素输入到数组故增加结果数组。
终止条件一般为指针为空:①if (cur == NULL) return;
②访问左节点:traversal(cur->left, vec);
③访问右节点:traversal(cur->right, vec);
④对中间节点的处理:vec.push_back(cur->val);
先序:①④②③;中序:①②④③;后序遍历:①②③④
2.二叉树的迭代遍历
迭代遍历主要用到栈结构。
先序遍历根左右,访问顺序就是处理顺序。后序遍历可以用(根左右)—调整栈访问顺序—(根右左)—reverse(result数组)—(左右根)实现。
但是,中序遍历左根右逻辑不同,访问顺序不是处理顺序,增加TreeNode*t 指针进行访问压栈,退栈时对节点进行处理(输出)。
class Solution {
public:
//先访问,出栈的时候再处理
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* t = root;//创建一个t指针
while(!st.empty() || t != nullptr){ //所要遍历的节点没完或者栈内数据未处理完
if(t != nullptr){ //访问节点非空
st.push(t); //则将访问节点入栈
t = t->left; //改为访问当前节点左孩子
}
else{ //访问节点已空
t = st.top(); //栈里要弹出的数据,就是要处理的节点
st.pop();
result.push_back(t->val); //中
t = t->right; //修改当前指针为处理节点右孩子
}
}
return result;
}
};
3.二叉树的层序遍历
利用队列这种数据结构。树的层序遍历相当于图的广度优先遍历
1.非递归的实现并不复杂
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
if(root == nullptr) return result;
que.push(root);
while(!que.empty()){
//每处理完一层进队列的节点个数等于每组需要输出的节点个数
int size = que.size();
vector<int> vec;
for(int i = 0; i < size; i++){ //不能用 que.size()是因为其在循环里会改变
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val) ;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(vec);
}
return result;
}
2.递归实现
//队列的应用,递归法
//depth 表示当前节点所在的层数,一层一层来遍历
void order(TreeNode* t, vector<vector<int>>& res,int depth){
if(t == nullptr) return;
/* res.size() 等于depth,说明当前层还没有被添加到结果数组中,
需要先将一个空的向量 vector<int>() 添加到结果数组中,以便存储当前层的节点值。*/
if(res.size() == depth) res.push_back(vector<int>());
res[depth].push_back(t->val);
order(t->left, res, depth + 1);
order(t->right, res, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;//为了适应空树,depth从0开始
order(root, result, depth);
return result;
}