二叉树中序遍历
给定一个二叉树,返回它的中序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,3,2]
##代码
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
// 中序遍历 先遍历左子树,根节点,右子数
// 如果是递归做法则递归遍历左子树,访问根节点,递归遍历右子数
// 非递归过程:先访问 最左子树绩点 在访问其父节点 在访问其兄弟
// while循环条件 中序遍历需先判断当前结点是否存在,若存在把该节点放入栈中,再将当前节点设置为节点的做孩子
// 若不存在则取栈顶元素为CUR,当且仅当栈顶元素也为空,循环结束
// c++] 非递归,都是套路。
// 注:为每一种算法,以及基本步骤取名,是记忆算法的捷径。学过象棋的朋友都知道,学象棋,先学基本杀招,象棋中的每种基本杀法都有固定的名字,比如双马饮泉,马后炮、闷宫、二鬼拍门之类的。不取名字,临时想出来的,可能每次写的步骤都不一样,导致你实现起来速度慢。同样算法你也可以这样学习,每一种经典算法,取一个自己熟悉的“杀法”名称,以助你快速杀题。
// 先入栈,在访问
// 中序遍历,左臂入栈法,
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode *> path;
while(root){
path.push(root);
root=root->left;
}
while(!path.empty()){
auto node = path.top();
path.pop();
res.push_back(node->val);
//每 pop 一个节点,将其右子数做*左臂入站*操作。
node = node ->right;
while(node){
path.push(node);
node =node->left;
}
}
return res;
}
};
二叉树前序遍历
给定一个二叉树,返回它的 前序 遍历。
示例:
输入: [1,null,2,3]
1
\
2
/
3
输出: [1,2,3]
##代码
最简单的非递归遍历,前面方法的加强版
/**
* 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> preorderTraversal(TreeNode* root) {
vector<int>res;
stack<TreeNode *> path;
TreeNode * cur=root;
while(!path.empty() || cur)
{
if(cur!=NULL ){
res.push_back(cur->val);
path.push(cur);
cur=cur->left;
}
else{
cur =path.top();
path.pop();
cur = cur -> right;
}
}
return res;
}
};
##题目:简化路径
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (…) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。
##代码:
class Solution {
// 按"/"分割,去除空白和'.', 遇到".."把前面的一个和".."都删除。最后如果是空, //就返回"/", c++代码如下:
public:
string simplifyPath(string path) {
istringstream is(path); //从String对象中读取字符
vector<string> parts;
string temp;
while (getline(is ,temp,'/')){//is输入流,把读入的字符串存到temp中
//遇到/就停止
if(!temp.empty())
parts.push_back(temp);
}
auto it = parts.begin();//用于代替冗长复杂、变量使用范围专一的变量声明。
while(it != parts.end()){
if(*it == ".")
it = parts.erase(it);
else if (*it == ".."){
int count =0;
if(it != parts.begin()){
it--;
it = parts.erase(it);
}
it = parts.erase(it);
}
else {
it++;
}
}
if(parts.empty())
return "/";
string re;
for (auto each :parts){
re+="/";
re+=each;
}
return re;
}
};
新知识总结
此题思路:
按“/”分割,如果非空就压入vector中,双“//”中间一定是空的,不需要区分斜杠的树目
开始处理vector,如果遇到“.”删除,遇到"…",删除两个
遇到其他的正常操作
把无冗杂操作的vector拼接成一个string,及时简化的路径
遇到的新知识:
istringstream is(path); //从String对象中读取字符
getline(is ,temp,’/’)//is输入流,把读入的字符串存到temp中遇到"/"就停止
auto it = parts.begin();//用于代替冗长复杂、变量使用范围专一的变量声明。
删除操作:
if(*it == ".")
it = parts.erase(it);
拼接操作:
string re;
for (auto each :parts){
re+="/";
re+=each;
}
##逆波兰式表达式求值
根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: [“2”, “1”, “+”, “3”, “*”]
输出: 9
解释: ((2 + 1) * 3) = 9
##代码
class Solution {
public:
int evalRPN(vector<string>& tokens) {
//遇到字符串就预算然后在压入
int A ,B,C;
vector<int> ans;
auto it = tokens.begin();
while(it != tokens.end()){
if(* it == "+"){
it++;
A =ans.back();
ans.pop_back();
B= ans.back();
ans.pop_back();
C= A + B;
ans.push_back(C);
}
else if(* it == "-"){
it++;
A =ans.back();
ans.pop_back();
B= ans.back();
ans.pop_back();
C= B - A;
ans.push_back(C);
}
else if(* it == "*"){
it++;
A =ans.back();
ans.pop_back();
B= ans.back();
ans.pop_back();
C= A * B;
ans.push_back(C);
}
else if(* it == "/"){
it++;
A =ans.back();
ans.pop_back();
B= ans.back();
ans.pop_back();
C= B / A;
ans.push_back(C);
}
else{
ans.push_back(stoi(*it));
it++;
}
}
return ans.back();
}
};
二叉树的锯齿形层次遍历
给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回锯齿形层次遍历如下:
[
[3],
[20,9],
[15,7]
]
代码
/**
* 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<vector<int>> zigzagLevelOrder(TreeNode* root) {
//先层次遍历,在奇数行reverse翻转一下就行了
vector<vector<int>> ans;
if(!root) {
return ans;
}
stack<TreeNode*> node;
node.push(root);
int Ce=0;
while(!node.empty()){
vector<int> t;
vector<TreeNode *> level;
while(!node.empty()){
level.push_back(node.top());
node.pop();
}
for(auto i:level){
t.push_back(i->val);
if(Ce%2==0){
if(i->left) node.push(i->left);
if(i->right) node.push(i->right);
}
if(Ce%2==1){
if(i->right){
node.push(i->right);
}
if(i->left) {
node.push(i->left);
}
}
}
Ce++;
ans.push_back(t);
}
return ans;
}
};
代码2
/**
* 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<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>> ans;
if (!root) return ans;
vector<TreeNode*> d;
d.push_back(root);
while(!d.empty()){
vector<int> t;
vector<TreeNode*> node(d.begin(),d.end());
d.clear();
for(auto i :node){
t.push_back(i->val);
if(i->left) d.push_back(i->left);
if(i->right) d.push_back(i->right);
}
ans.push_back(t);
}
//以上是层次遍历
for(auto i=1;i<ans.size();i = i+2){
reverse(ans[i].begin(),ans[i].end());
}
//奇数序号用reverse翻转
return ans;
}
};
每日温度
根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高的天数。如果之后都不会升高,请输入 0 来代替。
例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。
提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的都是 [30, 100] 范围内的整数。
#代码
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& T) {
/**
* 根据题意,从最后一天推到第一天,这样会简单很多。因为最后一天显然不会再有升高的可能,结果直接为0。
* 再看倒数第二天的温度,如果比倒数第一天低,那么答案显然为1,如果比倒数第一天高,又因为倒数第一天
* 对应的结果为0,即表示之后不会再升高,所以倒数第二天的结果也应该为0。
* 自此我们容易观察出规律,要求出第i天对应的结果,只需要知道第i+1天对应的结果就可以:
* - 若T[i] < T[i+1],那么res[i]=1;
* - 若T[i] > T[i+1]
* - res[i+1]=0,那么res[i]=0;
* - res[i+1]!=0,那就比较T[i]和T[i+1+res[i+1]](即将第i天的温度与比第i+1天大的那天的温度进行比较)
*/
stack<int> s;
vector<int> res(T.size(),0);//T.size()个元素,值均为0
for(int i=T.size()-2;i>=0;i--){
for(int j=i+1;j<T.size();j+=res[j]){
if(T[i]<T[j]){
res[i]=j-i;
break;
}else if(res[j]==0){
res[i]==0;
break;}
}
}
return res;
}
};
##验证二叉树的前序序列化
序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。
_9_
/ \
3 2
/ \ / \
4 1 # 6
/ \ / \ / \
# # # # # #
例如,上面的二叉树可以被序列化为字符串 “9,3,4,#,#,1,#,#,2,#,6,#,#”,其中 # 代表一个空节点。
给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。
每个以逗号分隔的字符或为一个整数或为一个表示 null 指针的 ‘#’ 。
你可以认为输入格式总是有效的,例如它永远不会包含两个连续的逗号,比如 “1,3” 。
代码
class Solution {
public:
bool isValidSerialization(string preorder) {
//二叉树的叶子节点的数目必须是根节点数+1;
//入度和初度必须相等即可
//根节点入度为0,出度为2,其他非叶子节点入度为1出度为2,叶子节点入度为一出度为零
//入度减一,出度加一,先给度数赋值为1,即可把根节点看作是非叶子节点了
int n=1;
string str;
istringstream is(preorder);
while(getline(is,str,','))
{
--n;
if(n<0) return false;
if(str != "#") n+=2;
}
return n==0;
}
};
下一个更大的元素II
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
示例 1:
输入: [1,2,1]
输出: [2,-1,2]
解释: 第一个 1 的下一个更大的数是 2;
数字 2 找不到下一个更大的数;
第二个 1 的下一个最大的数需要循环搜索,结果也是 2。
注意: 输入数组的长度不会超过 10000。
##单调递减栈解法220ms
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
//小技巧:增大一倍数组,不用考虑循环
vector<int>doubleNums;
stack<int> st;
for(int num : nums){
doubleNums.push_back(num);
}
for(int num:nums){
doubleNums.push_back(num);
}
int sz = nums.size();
vector<int> res(sz,-1);//sz在运行时才有正确值,所以这两个定义放在函数内部
for(int i=0; i<2*sz ; ++i){
while(!st.empty() && nums[st.top()]<doubleNums[i]){
res[st.top()]=doubleNums[i];
st.pop();
}
if(i<sz) st.push(i);//只需要原nums中的数字所处位置的【下标】进栈
//因为res的长度必须是sz,超过sz的部分我们只是为了给之前栈中的数字找较大值,所以不能压入栈
}
return res;
}
};
暴力代码904ms
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int size=nums.size();
vector<int> result(size,-1);
for(int i=0;i<nums.size();i++){
int k=i+1;
while(k<nums.size()+i){
if(nums[k%nums.size()]>nums[i]){
result[i]=nums[k%nums.size()];
break;
}
k++;
}
//if(k==(nums.size()+1)) result.push_back(-1);
}
return result;
}
};