集合
1. 力扣【606. 根据二叉树创建字符串】
描述:
给你二叉树的根节点 root ,请你采用前序遍历的方式,将二叉树转化为一个由括号和整数组成的字符串,返回构造出的字符串。
空节点使用一对空括号对 “()” 表示,转化后需要省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
-
分析:
- 空节点使用一对空括号对 “()” 表示
- 1.左右都为空 – 省略
- 2.右为空 – 省略
- 3.左为空 – 不省略
/* Definition for a binary tree node. 后面的题都是这个Node结构 */
/**
*struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), *right(right) {}
*};
**/
class Solution1 {
public:
string tree2str(TreeNode* root) {
if (root == nullptr)
return "";
string str = to_string(root->val); // C++11
// 左右都不空,有()
// 左为空,右不为空才有 ()
if (root->left || root->right) // 左不为空--不能省略,左为空的话再判断右是否为空
{
str += '(';
str += tree2str(root->left);
str += ')';
}
// 右边非空才有()
if (root->right)
{
str += '(';
str += tree2str(root->right);
str += ')';
}
return str;
}
};
2. 力扣【102. 二叉树的层序遍历】
描述:
给你二叉树的根节点 root ,返回其节点值的 层序遍历,vector<vector> 。
即逐层地,从左到右访问所有节点。
-
思路一:
-
给两个 queue,一个放节点,出的时候带进两个子节点;
另一个放每个节点的层数,出的时候一起出。
相同层数放一个vector v,最后放到 vector<vector> vv
思路二:
- 给一个 queue<Node*> q,放节点,出的时候带进子节点。(走完这层,q 里有下一层的所有元素,即下一层的levelsize = q.size())
- 再给一个 int levelsize,每层的个数,大小是 q.size()
class Solution2 {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> q;
int levelSize;
if (root)
{
q.push(root);
levelSize = 1;
}
vector<vector<int>> vv;
while (!q.empty())
{
vector<int> v;
// 分层,通过控制 levelsize 一层层出
while (levelSize--)
{
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;
}
};
3. 力扣【107. 二叉树的层序遍历 II】
描述:
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。
即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历
-
思路:
- 和上题一样做法,最后逆置一下 vv
class Solution3 {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> q;
int levelSize;
if (root)
{
q.push(root);
levelSize = 1;
}
vector<vector<int>> vv;
while (!q.empty())
{
vector<int> v;
// 分层
while (levelSize--)
{
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;
}
};
4. 力扣【236. 二叉树的最近公共祖先】
描述:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。(一个节点自己也算他自己的祖先)
-
思路一:
- 如果是三叉链(每个节点有 parent),可以转化成链表相交问题!!! 思路二:
- 公共祖先的特征:如果一个在我的左子树,一个在我的右子树,我就是最近公共祖先
- 这个思路的时间复杂度是 N^2
class Solution4_1 {
public:
// 查找 x 节点是否在以 root 节点为根的这个树下
bool IsInTree(TreeNode* root, TreeNode* x)
{
if (root == nullptr)
return false;
// if(root == x)
// return true;
// else
// return IsInTree(root->left, x) || IsInTree(root->right, x);
return root == x
|| IsInTree(root->left, x)
|| IsInTree(root->right, x); // 翻译一下:不是我就是你,不然就是他...
}
// 主程序
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == NULL)
return NULL;
// p或q是根,另一个是孩子,root就是最近公共祖先
if ((root == p) || (root == q))
return root;
// 判断p和q的位置
bool pInLeft = IsInTree(root->left, p);
bool pInRight = !pInLeft; // 一个在,一个就不在
bool qInLeft = IsInTree(root->left, q);
bool qInRight = !qInLeft;
// 1. 一个在我的左树,一个在我的右树,我就是最近公共祖先
// 2. 都在左,转换成子问题,去左子树找公共祖先
// 3. 都在右,转换成子问题,去右子树找公共祖先
if ((pInLeft && qInRight) || (pInRight && qInLeft))
{
return root;
}
else if (pInLeft && qInLeft)
{
return lowestCommonAncestor(root->left, p, q);
}
else
{
return lowestCommonAncestor(root->right, p, q);
}
}
};
-
注意:
- 如果是搜索二叉树,上述思路可以优化到 O(N)
- 1.一个比根小,一个比根大,根就是最近公共祖先
- 2.都比根小,递归左树查找
- 3.都比根大,递归右树查找
不是搜索二叉树,如何才能优化到 O(N) 呢?
解决方案:DFS求出p和q的路径放到容器中,转换成路径相交问题
-
思路三:
-
两个 stack 存放两个节点的路径,栈底直至最近公共祖先的路径都相同。
意味着,在两个栈 size() 相等的情况下,top() 相等,那个率先相等的 top() 就是最近公共祖先。
class Solution4_2 {
public:
// 以 root 为根的树中,将节点 x 所在位置,保存在 path 中
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);
// pop 到一样长
while (pPath.size() != qPath.size())
{
if (pPath.size() > qPath.size())
pPath.pop();
else
qPath.pop();
}
// 找 top() 第一个相等的(最后一个入栈的相同路线的节点),就是最近公共祖先
while (pPath.top() != qPath.top())
{
pPath.pop();
qPath.pop();
}
return pPath.top();
}
};
5. 牛客【JZ36 二叉搜索树与双向链表】
描述:
输入一棵二叉搜索树,将该二叉搜索树转换成一个(中序)排序的双向链表。
-
思路一:
- vector<TreeNode*> v,将树进行中序遍历,把节点放进 vector 思路二:
- 利用中序遍历,定义两个节点 cur 和 prev 进行相互链接
class Solution5 {
public:
// 中序遍历并链接
void InOrderConvert(TreeNode* cur, TreeNode*& prev) // 因为这里的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;
}
};
6. 力扣【105.从前序与中序遍历序列构造二叉树】
描述:
给定两个整数数组 preorder 和 inorder,其中 preorder 是二叉树的先序遍历,inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
输入:
preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出:
[3, 9, 20, null, null, 15, 7]
-
思路:
- 前序: 根 左子树 右子树
- 中序: 左子树 根 右子树
- 在前序中确定根,在中序中分割出左右子树区间
class Solution6 {
public:
TreeNode* _buildTree(vector<int>& preorder, vector<int>& inorder, int& prei, int inbegin, int inend) {
if (inbegin > inend) // 不存在的区间 [inbegin, rooti-1]
return nullptr;
// 在前序,确定根
TreeNode* root = new TreeNode(preorder[prei]);
// 在中序,通过根,分割出左右子区间
int rooti = inbegin;
while (rooti <= inend)
{
if (inorder[rooti] == preorder[prei])
break;
else
rooti++;
}
prei++;
// [inbegin, rooti-1] rooti [rooti+1 inend]
root->left = _buildTree(preorder, inorder, prei, inbegin, rooti - 1);
root->right = _buildTree(preorder, inorder, prei, rooti + 1, inend);
return root;
}
// 主程序
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int i = 0;
return _buildTree(preorder, inorder, i, 0, inorder.size() - 1);
}
};
7. 力扣【144. 二叉树的前序遍历】
描述:
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
要求:
使用迭代(非递归)完成
C语言递归写法
int TreeSize(struct TreeNode* root)
{
return root == NULL ? 0 : TreeSize(root->left) + TreeSize(root->right) + 1;
}
void preOrder(struct TreeNode* root, int* a, int* pi) // 这里的数组下标要跟着每次递归叠加,所以必须传地址
{
if (root == NULL)
return;
a[(*pi)++] = root->val;
preOrder(root->left, a, pi);
preOrder(root->right, a, pi);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize)
{
// 数组大小通过查 root 的结点个数确定
*returnSize = TreeSize(root);
// malloc 数组
int* a = (int*)malloc(*returnSize * sizeof(int));
if (a == NULL)
{
perror("malloc fail");
exit(-1);
}
// 前序遍历,并将遍历的结果放入数组
int i = 0;
preOrder(root, a, &i);
return a;
}
-
C++ 非递归写法
- 1.root
- 2.左路节点
- 3.左路节点的右子树
class Solution7 {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
vector<int> v;
while (cur || !st.empty())
{
// 开始访问一棵树
// 1. 左路节点
while (cur)
{
v.push_back(cur->val);
st.push(cur);
cur = cur->left;
}
// 2. 左路节点的右子树
TreeNode* top = st.top();
st.pop();
// 子问题访问右子树
cur = top->right;
// (构造迭代的子问题的时候,观察初始条件,回归到子问题的初始条件,这里的思路就是,一棵树的root)
}
return v;
}
};
8. 力扣【94. 二叉树的中序遍历】
描述:
给定一个二叉树的根节点 root ,返回 它的 中序 遍历 。
要求:
非递归
-
思路:
- 1.左路节点
- 2.根
- 3.左路节点的右子树
class Solution8 {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
vector<int> v;
while (cur || !st.empty())
{
// 开始访问一棵树
// 1. 左路节点
// 从栈里面取到左路节点,意味着这个节点的左子树访问完了
while (cur)
{
st.push(cur);
cur = cur->left;
}
v.push_back(st.top()->val);
// 2. 左路节点的右子树
TreeNode* top = st.top();
st.pop();
// 子问题访问右子树
cur = top->right;
// (构造迭代的子问题的时候,观察初始条件,回归到子问题的初始条件,这里的思路就是,一棵树的root)
}
return v;
}
};
9. 力扣【145. 二叉树的后序遍历】
描述:
给定一个二叉树的根节点 root ,返回它的 后续 遍历 。
要求:
非递归
-
思路一:
- 两个栈
class Solution9_1 {
public:
vector<int> postorderTraversal(TreeNode* root) {
TreeNode* cur = root;
stack<TreeNode*> st1; // 存 cur
stack<TreeNode*> st2; // 存 cur 的子节点
st2.push(root);
vector<int> v;
while (cur || !st1.empty())
{
// 1. 左路节点
while (cur)
{
// 当前节点入栈 st1
st1.push(cur);
// 当前节点的非空子节点入栈 st2,先右后左
if (cur->right)
st2.push(cur->right);
if (cur->left)
st2.push(cur->left);
cur = cur->left;
// 如果两个栈的 top 相同,都 pop() 出来
while (st1.top() == st2.top())
{
v.push_back(st1.top()->val);
st1.pop();
st2.pop();
if (st1.empty())
return v;
}
}
// 2. 进入左路节点的右子树
// 回归到子问题
cur = st1.top()->right;
}
return v;
}
};
-
思路二:
- 一个栈(更优),分析
- 第一次取到节点 a 时,上一个访问的节点是 a 的左子树
- 第二次取到节点 a 时,上一个访问的节点是 a 的右子树
- 记录后序的时机:cur 的右子树为空 or cur 访问完了右子树
class Solution9_2 {
public:
vector<int> postorderTraversal(TreeNode* root) {
TreeNode* cur = root;
stack<TreeNode*> st;
vector<int> v;
TreeNode* prev = nullptr;
while (cur || !st.empty())
{
// 1. 左路节点
while (cur)
{
st.push(cur);
cur = cur->left;
}
// 栈里面取到左路节点,说明该左路节点的左子树访问完了
TreeNode* top = st.top();
// 处理右边:
// 右为空,或者右子树已经访问过了(右==上一个访问过的节点),可以访问根节点
if (top->right == nullptr || top->right == prev)
{
v.push_back(top->val);
st.pop();
prev = top; // 等于的是pop掉的那个值
}
else
{
// 回归到子问题 -- 进入左路节点的右子树
cur = top->right;
}
}
return v;
}
};
/*************** 传送门 ******** 传送门 ******** 传送门 **************/
# 🔗利用树型关联式容器的经典题目
持续更新 _(:з)∠)_ ing~