文章目录
- 二叉树OJ
- 根据二叉树创建字符串
- 思路
- 示例代码
- 二叉树的层序遍历
- 思路
- 示例代码
- 二叉树的层序遍历 II
- 思路
- 示例代码
- 二叉树的最近公共祖先
- 思路1
- 示例代码1
- 思路2
- 示例代码2
- 二叉搜索树与双向链表
- 思路1
- 示例代码1
- 思路2
- 示例代码2
- 迭代实现二叉树的三种遍历
- 前序遍历
- 思路
- 示例代码
- 中序遍历
- 思路
- 示例代码
- 后序遍历
- 思路1
- 示例代码1
- 思路2
- 示例代码2
二叉树OJ
根据二叉树创建字符串
606. 根据二叉树创建字符串 - 力扣(LeetCode)
思路
观察题目, 得出规律:
- 节点左右都为空时, 两个括号均省略
- 节点左为空时, 不省略左括号
- 节点右为空时, 省略右括号
我们创建一个字符串, 先插入根节点(要将数字转为字符串插入) , 然后按照规律递归插入其他节点即可
示例代码
class Solution {
public:
string tree2str(TreeNode* root) {
//递归出口: 左右都为空,省略括号,返回空串
if(root == nullptr)
return "";
string s=to_string(root->val); //先将根节点插入字符串(需要转换成字符串格式)
//两种情况: 1.左不为空, 不省略括号 2. 左为空, 右不为空, 不省略括号
if(root->left || root->right)
{
s+='(';
s+= tree2str(root->left);
s+=')';
}
//右为不空, 不省略括号
if(root->right)
{
s+='(';
s+= tree2str(root->right);
s+=')';
}
return s;
}
};
二叉树的层序遍历
102. 二叉树的层序遍历 - 力扣(LeetCode)
思路
定义一个保存节点指针的队列和一个记录当前层节点数量的变量levelSize;
利用队列进行层序遍历:
- 先将根节点入队列, levelSize更新为1
- 定义一个二维数组来存储整棵树层序遍历的情况, 当队列不为空时,通过levelSize控制一层一层地出
- 定义一个一维数组存储当前层的数据, levelSize>0的条件下,出当前队列的头节点后保存到一维数组中,带入下一层(带入当前节点的左右节点)
- 继续出当前层的其他节点,当前层节点出完了即levelSize=0时,把这一整层的数据插入到二维数组中
- levelSize=0时当前层节点出完了,下一层节点一定全部进队列了 ,更新levelSize,此时levelSize=q.size()[上一层数全部出完, 这一层全部进队列了,这一层节点个数就是队列中元素的个数]
- 重复上面的步骤, 出上一层带下一层,直至队列为空,最后返回二维数组即可
示例代码
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
int levelSize=0;
//根节点入队列
if(root)
{
q.push(root);
levelSize=1;
}
vector<vector<int>> vv;
while(!q.empty())
{
//通过levelSize控制一层一层地出
vector<int> v;
while(levelSize--) //levelSize减到0当前层就出完了, 当前层出完了, 下一层一定进队列了
{
//出上一层
TreeNode*front=q.front();
q.pop();
v.push_back(front->val); //当前层的每个数据插入到一维数组中
//带入下一层
if(front->left)
{
q.push(front->left);
}
if(front->right)
{
q.push(front->right);
}
}
vv.push_back(v); //将这一整层的数据插入到二维数组中
//更新下一层的数据
levelSize=q.size();
}
return vv;
}
};
二叉树的层序遍历 II
107. 二叉树的层序遍历 II - 力扣(LeetCode)
思路
观察发现这题和上一题输出顺序相反,这题是自底向上的层序遍历,上一题是自顶向下的层序遍历;
所以只需按照上一题的步骤先自顶向下的层序遍历,在返回前逆置这个二维数组就是自底向上的层序遍历
示例代码
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
//先自顶向下层序遍历后逆置即可
queue<TreeNode*> q;
int levelSize=0;
//根节点入队列
if(root)
{
q.push(root);
levelSize=1;
}
vector<vector<int>> vv;
while(!q.empty())
{
//通过levelSize控制一层一层地出
vector<int> v;
while(levelSize--) //levelSize减到0当前层就出完了, 当前层出完了, 下一层一定进队列了
{
//出上一层
TreeNode*front=q.front();
q.pop();
v.push_back(front->val); //当前层的每个数据插入到一维数组中
//带入下一层
if(front->left)
{
q.push(front->left);
}
if(front->right)
{
q.push(front->right);
}
}
vv.push_back(v); //将这一整层的数据插入到二维数组中
//更新下一层的数据
levelSize=q.size();
}
reverse(vv.begin(),vv.end());
return vv;
}
};
二叉树的最近公共祖先
236. 二叉树的最近公共祖先 - 力扣(LeetCode)
思路1
观察题目发现特征,分为两种情况:
- 两个节点中一个是根, 另一个是孩子, 那么根就是公共祖先
- 一个节点在我的左树,一个节点在我的右树,那么我就是公共祖先
所以找公共祖先的步骤:
- 满足情况1直接返回
- 一般情况下,要判断两个节点在左右哪个子树,写一个isinTree函数来判断
- 判断结果分3种: (1) 一个节点在我的左树,一个节点在我的右树,那么我就是公共祖先 (2) 两个节点都在左子树, 转化成子问题, 去左子树找 (3) 两个节点都在右子树, 转化成子问题, 去右子树找
但此思路效率较低,我们用思路2来改进
示例代码1
class Solution {
public:
bool isinTree(TreeNode* root,TreeNode* x)
{
if(root==nullptr)
return false;
if(root==x)
return true;
return isinTree(root->left,x) || isinTree(root->right,x);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==nullptr)
return nullptr;
//两个节点中有一个是根, 那么这个节点就是公共祖先
if(p==root || q==root)
return root;
//真正找公共祖先的过程
//判断两个节点在左右哪个子树
bool pInleft=isinTree(root->left,p);
bool pInright=!pInleft;
bool qInleft=isinTree(root->left,q);
bool qInright=!qInleft;
//1. 一个在我的左树, 一个在我的右树, 我就是公共祖先
if((pInleft && qInright) || (qInleft && pInright))
{
return root;
}
else if(pInleft && qInleft) //2. 都在左子树, 转化成子问题, 去左子树找
{
return lowestCommonAncestor(root->left, p, q);
}
else //3. 都在右子树, 转化成子问题, 去右子树找
{
return lowestCommonAncestor(root->right, p, q);
}
}
};
思路2
前提: 如果是三叉链(每个节点都有parent), 可以转化成链表相交的问题
此解法以这个前提为思路:DFS, 求出这两个节点的路径保存在容器中, 转换成路径相交问题(这里用栈保存)
求两个节点的路径:
- 先让节点入栈, 若此节点恰好是直接返回
- 递归到左子树去找, 左是直接返回 ; 递归到右子树去找, 右是直接返回
- 若左右都不是, 说明当前节点不在目标节点的路径上, 直接让当前节点出栈, 返回false到上一层继续找
转换成路径相交:
- 用栈来存储两个节点的路径上的各个节点
- 两个栈中元素个数不相等, 先出元素多的栈
- 两个栈中元素个数相等, 两个栈中元素都出栈
- 直至两个栈中栈顶元素都相等, 则此节点就是公共祖先
示例代码2
class Solution {
public:
bool GetPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path)
{
if(root==nullptr)
return false;
//先让节点入栈
path.push(root);
if(root==x)
return true;
//左是, 直接返回
if(GetPath(root->left, x, path))
return true;
//右是, 直接返回
if(GetPath(root->right, x, path))
return true;
//这个节点本身及左右都不是, 出栈
path.pop();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//用栈来存储这一路径上的各个节点, 最后转换成链表相交的问题
stack<TreeNode*> pPath, qPath;
GetPath(root, p, pPath);
GetPath(root, q, qPath);
//两个栈中元素个数不相等, 先出元素多的栈
while(pPath.size() != qPath.size())
{
if(pPath.size() > qPath.size())
pPath.pop();
else
qPath.pop();
}
//两个栈中元素个数相等, 都出栈
while(pPath.top() != qPath.top())
{
pPath.pop();
qPath.pop();
}
return pPath.top();
}
};
二叉搜索树与双向链表
二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
思路1
思路1比较简单,首先将这棵二叉树进行中序遍历并将其节点存到数组中,后更改数组中的链接关系,返回数组的首元素即可
示例代码1
class Solution {
public:
vector<TreeNode*> v; //用来存放中序遍历的节点
//中序遍历将结点放入数组中
void Inorder(TreeNode* cur)
{
if(cur==nullptr)
return;
Inorder(cur->left);
v.push_back(cur);
Inorder(cur->right);
}
TreeNode* Convert(TreeNode* pRootOfTree) {
if(pRootOfTree==nullptr)
return pRootOfTree;
Inorder(pRootOfTree);
//更改数组中的链接关系
for(int i=0;i<v.size()-1;++i)
{
v[i]->right=v[i+1];
v[i+1]->left=v[i];
}
return v[0]; //返回双向链表的头节点
}
};
思路2
中序遍历,更改树的链接关系,定义一个prev节点和一个cur节点,让cur节点从根开始走一个中序遍历,让cur -> left = prev, prev->right=cur 递归更改链接关系即可;更改完后找一下这个双向链表的头节点返回头节点
示例代码2
class Solution {
public:
void InorderConvert(TreeNode* cur, TreeNode*& prev)
{
if(cur==nullptr)
return;
InorderConvert(cur->left, prev);
//这里cur出现顺序就是中序
cur->left=prev;
if(prev)
prev->right=cur;
prev=cur;
InorderConvert(cur->right, prev);
}
TreeNode* Convert(TreeNode* pRootOfTree) {
TreeNode*prev=nullptr;
InorderConvert(pRootOfTree, prev);
//找头节点
TreeNode*head=pRootOfTree;
while(head && head->left)
{
head=head->left;
}
return head;
}
};
迭代实现二叉树的三种遍历
144. 二叉树的前序遍历 - 力扣(LeetCode)
94. 二叉树的中序遍历 - 力扣(LeetCode)
145. 二叉树的后序遍历 - 力扣(LeetCode)
前序遍历
思路
先访问这棵树的左路节点, 将访问到的元素入栈同时插入到数组中,左路节点访问完后, 开始从栈中取左路节点(即栈顶元素), 这时表示左路节点的左子树已访问完了;后开始访问左路节点的右子树,当栈为空和当前节点为空时循环结束
总结: 访问左路节点 + 转化成子问题访问左路节点的右子树
示例代码
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> v;
TreeNode*cur=root;
while(!st.empty() || cur)
{
//访问一棵树: 1. 访问左路节点 2. 访问左路节点的右子树
//访问左路节点
while(cur)
{
v.push_back(cur->val);
st.push(cur);
cur=cur->left;
}
//开始访问右子树
TreeNode*top=st.top();
st.pop();
//转换成子问题
cur=top->right;
}
return v;
}
};
中序遍历
思路
思路与上面相似
改变的地方是开始访问的左路节点不插入数组中, 等左路节点访问完后再从栈里面取左路节点,插入数组中,这样遍历的结果就是中序
示例代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
TreeNode*cur=root;
stack<TreeNode*> st; //临时存储树中的节点
vector<int> v; //保存这棵树的遍历结果
while(cur || !st.empty())
{
//开始访问左路节点
while(cur)
{
st.push(cur);
cur=cur->left;
}
//从栈里面取到左路节点, 表示左路节点的左子树访问完了
TreeNode*top=st.top();
st.pop();
v.push_back(top->val);
//访问左路节点的右子树
cur=top->right;
}
return v;
}
};
后序遍历
思路1
思路与上面两种遍历相似
后序遍历要找到根节点可以访问的两种情况: 1. 当其右为空时 2. 当右子树已经访问过时
右为空直接判断;右子树是否已经访过了, 用一个前驱节点prev来记录访问, 直接判断根的右节点是否为前驱节点
示例代码1
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
TreeNode*cur=root;
stack<TreeNode*> st; //临时存储树中的节点
vector<int> v; //保存这棵树的遍历结果
TreeNode*prev=nullptr;
while(cur || !st.empty())
{
//开始访问左路节点
while(cur)
{
st.push(cur);
cur=cur->left;
}
//从栈里面取到左路节点, 表示左路节点的左子树访问完了
TreeNode*top=st.top();
//可以访问根节点的两种情况: 1. 右为空 2. 右子树已经访问过了
if( top->right==nullptr || top->right==prev)
{
st.pop();
v.push_back(top->val);
prev=top;
}
else
{
//访问左路节点的右子树
cur=top->right;
}
}
return v;
}
};
思路2
还是前序遍历的思路, 只是这次是访问右路节点 + 转化成子问题访问右路节点的左子树
最后将这个数组逆置就由根-右-左 变成左-右-根
示例代码2
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
TreeNode*cur=root;
stack<TreeNode*> st; //临时存储树中的节点
vector<int> v; //保存这棵树的遍历结果
while(cur || !st.empty())
{
//开始访问右路节点
while(cur)
{
v.push_back(cur->val);
st.push(cur);
cur=cur->right;
}
//从栈里面取到右路节点, 表示右路节点的右子树访问完了
TreeNode*top=st.top();
st.pop();
cur=top->left;
}
//由根-右-左 变成左-右-根
reverse(v.begin(), v.end());
return v;
}
};