根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
示例 1:
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: ((2 + 1) * 3) = 9
示例 2:
输入: ["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
题目中给定的字符串为有效的逆波兰表达式, 在进行计算之前首先要解决两个问题:
通过分析之后,将主要解决问题划分为3个子问题进行处理:
在解决这些问题后,就可以进行对逆波兰表达式的计算过程
class Solution {
public:
// 判断是否为数字字符串
bool isNum(string &str){
for(auto ch: str){
if(isalnum(ch)){
return true;
}
}
return false;
}
// 数字字符串转int数字
int strToNum(string &str){
int res = 0;
bool negative = false;
if(str[0] == '-'){
negative = true;
for(int i = 1; i < str.size(); i++){
res = res*10 + str[i]-'0';
}
}
else{
for(int i = 0; i < str.size(); i++){
res = res*10 + str[i]-'0';
}
}
return negative? -res: res;
}
// 计算
int caculate(int &a, int &b, char operat){
int res = 0;
switch(operat){
// 注意,由于除法中除数与被除数的位置关系,将从数字栈中弹出的第一个元素作为被除数
// 第二个元素为除数, 其它运算同样遵循此法则
case'+': res = b + a; break;
case'-': res = b - a; break;
case'*': res = b * a; break;
case'/': res = b / a; break;
default: break;
}
return res;
}
int evalRPN(vector<string>& tokens) {
int target = 0;
if(tokens.empty()){
return target;
}
stack<int> st;
for(int i = 0; i < tokens.size(); i++){
if(isNum(tokens[i])){
st.push(strToNum(tokens[i]));
}
else{
int a = st.top(); st.pop();
int b = st.top(); st.pop();
st.push(caculate(a, b, tokens[i][0]));
}
}
return st.top();
}
};
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
作为一道基础的二叉树问题,递归的解法自然不同多说。
从题目本质的性质来说,二叉树的前序遍历,中序遍历以及后序遍历都属于深度优先搜索算法的一种变形形式,正因如此,才要一定使用栈的结构来存储父节点的数据,以此达到类似于路径搜索的目的。
另外,前序遍历的顺序为根->左子树->右子树,这就意味着栈首先压入根节点,再压入左子树节点,再压入右子树节点。
至此,迭代方式来完成二叉树的前序遍历的准备工作已经完成。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
while(root || st.size()){
while(root){
// 节点非空,将节点的左子树压入栈中,作为下一个将要搜索的节点
// 通俗来说,这个while 循环的作用在于,遍历完一个节点左子树的的所有左节点
// 在保证左子树的左节点全部遍历过后,“最左节点”的左节点一定为NULL, 此时循环跳出
res.push_back(root->val);
st.push(root);
root = root->left;
}
// 左子树的“左节点”遍历结束,之后开始遍历左子树的“右节点”
// 同理,当左子树遍历结束,开始遍历右子树;
// 之后,右子树的“最左节点”,每一个左节点的左右子树,以此类推
root = st.top();
st.pop();
root = root->right;
}
return res;
}
};
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
中序遍历的顺序为左子树->根节点->右子树,从前序遍历的方法中可以大致推导出基本框架为:
while(root || st.size()){
while(root){
// 这个while循环保证最终到达的二叉树“最左”的节点
st.push(root);
root = root->left;
}
// while结束,表明已经抵达该子树的“最左”节点,目前栈顶元素就是“最左”节点的地址
// 毫无疑问,“最左节点”的左右子树均为NULL
root = st.top();
st.pop();
res.push_back(root->val);
// 正因“最左节点”的左右子树均为NULL,且“最左节点”已经确定,因此“最左节点”的右子树,就成了“回溯”的关键条件
root = root->right;
}
一句话来总结,就是不断确定目前节点的“最左节点”,并以“最左节点”的右子树是否为NULL为回溯的条件。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
while(root || st.size()){
while(root){
st.push(root);
root = root->left;
}
root = st.top();
st.pop();
res.push_back(root->val);
root = root->right;
}
return res;
}
};