当前位置: 首页 > news >正文

【OJ】二叉树的经典OJ题

目录

1. 二叉树创建字符串

2. 二叉树的层序遍历

3. 二叉树的最近公共祖先

优化到 O(N)

4. 二叉搜索树转化为有序双向链表

5. 按前序、中序遍历构造二叉树

6. 非递归 · 前序

7. 非递归 · 中序

8. 非递归 · 后序


1. 二叉树创建字符串

606. 根据二叉树创建字符串 - 力扣(LeetCode)

class Solution {
public:string tree2str(TreeNode* root) {}
};

用前序遍例

输出:"1(2(4))(3)" 简化前:"1(2(4)()())(3()())"

输出:"1(2()(4))(3)"

思路:前序遍例(根、左、右)
        1. 左空,右不空:左括号保留
        2. 左不空,右空:右括号删
        3. 左空,右空:左右括号都删

class Solution {
public:string tree2str(TreeNode* root) {string str;if (root == nullptr)return str;str += to_string(root->val);if (root->left || root->right){str += '(';str += tree2str(root->left);str += ')';}if (root->right){str += '(';str += tree2str(root->right);str += ')';}return str;}
};

第10行的写法非常经典:判断是否往左递归,录值或保留左括号
左不空:往左递归,录值
左空,右不空:往左递归,以保留左括号
左空,右空:不需要保留左括号

再到第17行:
右不为空:录值
右为空:不保留右括号

2. 二叉树的层序遍历

102. 二叉树的层序遍历 - 力扣(LeetCode)

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {}
};

广度优先遍历想到 queue

以前的层序遍历光让把数据打印出来,用一个队列:出一个节点并打印、入它的俩娃,直至队列为空
这道题不一样,要求一层一层遍历,并把每一层放到二维数组的每一行

思路1:双队列,一个存节点,一个记录行数

思路2:用 levelSize 控制层数,一层一层出

以前:

while (!empty(q))
{TreeNode* front = q.front();q.pop();if (front->left)q.push(front->left);if (front->right)q.push(front->right);
}

队列中最多有两层的数据,以前每出一个,不知道出的在哪一层

现在:

class Solution {
public:vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> vv;queue<TreeNode*> q;int levelSize = 0;if (root){q.push(root);levelSize = 1;}while (!q.empty()){vector<int> v;for (int i = 0; i < levelSize; i++){TreeNode* front = q.front();v.push_back(front->val);q.pop();if (front->left)q.push(front->left);if (front->right)q.push(front->right);}vv.push_back(v);levelSize = q.size();}return vv;}
};

原本 levelSize = 1,把 3 出了,levelSize = 2,就知道再往下的俩数据是一层


二叉树的层序遍历II:自底向上层层遍历,放到二维数组

107. 二叉树的层序遍历 II - 力扣(LeetCode)

这样即可:

reverse(vv.begin(), vv.end());
return vv;

3. 二叉树的最近公共祖先

236. 二叉树的最近公共祖先 - 力扣(LeetCode)

class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {}
};

树中所有节点的值都不相同,一个节点的祖先可以是自己
找 p q 俩节点的最近公共祖先 x p != q 且 都在二叉树中

思路:
A 链表相交类似问题
        a 三叉链(存父节点)O(N)
        b 用栈找路径 O(N)
B 普通解法:
        a 普通二叉树:通过查找函数,确定节点在左(右)树 O(N^2)
                1. 若当前根就是 p 或 q 节点,则当前根为最近公共祖先
                2. 若 p q 节点,一个在左树、一个在右树,则当前根为最近公共祖先
                3. 若 p q 节点,都在左(右)树,则递归去左(右)树,直至满足 1 2 的条件
        b 二叉搜索树通过比较大小,确定节点在左(右)树 O(N)

class Solution {
public:bool Find(TreeNode* tree, TreeNode* x){if (tree == nullptr)return false;return tree == x|| Find(tree->left, x)|| Find(tree->right, x);}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root == nullptr)return nullptr;if (root == p || root == q)return root;bool pleft, pright, qleft, qright;pleft = Find(root->left, p);pright = !pleft;qleft = Find(root->left, q);qright = !qleft;if (pleft && qleft)return lowestCommonAncestor(root->left, p, q);else if (pright && qright)return lowestCommonAncestor(root->right, p, q);elsereturn root;}
};

20 - 25行很巧妙:
如果 p 在左,Find 能找到,pleft 是真,pright 自动为假
如果 p 在右,Find 找不到,pleft 是假,pright 自动为真

这样写,时间复杂度是 O(N^2)

从 root 开始,每层往下走等差数列次 ↓

优化到 O(N)

查找过程 + 栈,就能在普通二叉树里搞定节点的路径,存到栈里

遇到不相等的节点也要入栈,因为不确定这个节点在不在路径上
要用节点比较,不能用值比较,可能有相同的值

递归全程是在一个栈上入、删数据的,所以 path 引用传参

节点先入栈,在该节点构成的子树中找 x(找的顺序:根、左子树、右子树)
找到了(10-17 行):该节点在 x 的路径上
没找到(到第 19 行):该节点不在 x 的路径上,出栈

class Solution {
public:bool FindPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& path){if (root == nullptr)return false;path.push(root);if (root == x)return true;if (FindPath(root->left, x, path))return true;if (FindPath(root->right, x, path))return true;path.pop();return false;}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {stack<TreeNode*> pPath, qPath;FindPath(root, p, pPath);FindPath(root, q, qPath);while (pPath.size() > qPath.size()){pPath.pop();}while (pPath.size() < qPath.size()){qPath.pop();}while (pPath.top() != qPath.top()){pPath.pop();qPath.pop();}return pPath.top();}
};

4. 二叉搜索树转化为有序双向链表

二叉搜索树与双向链表_牛客题霸_牛客网

#include <cstddef>
class Solution {
public:TreeNode* Convert(TreeNode* pRootOfTree) {}
};

说了二叉搜索树、有序:中序遍历

先写个中序遍历的框架,再不断往里面加东西
要找到当前根的左娃(前一个节点)、右娃(后一个节点),连接起来,所以要传 prev

上面栈帧的 prev 要用下面栈帧的 prev来确保连接关系,所以 prev 要传引用确保整个代码中只有一个 prev

下面的栈帧里改变了 prev,并且上面的栈帧要利用这次改变,prev 就要传引用或二级指针

站在当前节点,找左娃(前一个节点)很容易实现;找右娃(后一个节点)根本实现不了,怎么能预测明天的事呢
换个思路:当前节点能和左娃(前一个节点)链接,左娃(前一个节点)就能和当前节点链接

#include <cstddef>
class Solution {
public:void InOrder(TreeNode* cur, TreeNode*& prev){if (cur == nullptr)return;InOrder(cur->left, prev);// 当前节点的left指向前一个cur->left = prev;// 前一个节点的right指向当前节点(链接上了)if (prev)prev->right = cur;prev = cur;InOrder(cur->right, prev);}TreeNode* Convert(TreeNode* pRootOfTree) {TreeNode* prev = nullptr;InOrder(pRootOfTree, prev);TreeNode* head = pRootOfTree;while (head && head->left){head = head->left;}return head;}
};

5. 按前序、中序遍历构造二叉树

105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

class Solution {
public:TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {}
};

前序:根 左子树 右子树 确定根
中序:左子树 根 右子树 确定左右子树,分割左右区间

preorder = [3,9,20,15,7]
  inorder = [9,3,15,20,7]

class Solution {
public:TreeNode* _build(vector<int>& preorder, vector<int>& inorder, int& prei, int inbegin, int inend){if (inbegin > inend)return nullptr;TreeNode* root = new TreeNode(preorder[prei]);// 找中序的分割区间int rooti = inbegin;while (rooti <= inend){if (preorder[prei] == inorder[rooti])break;rooti++;}// prei创节点、找分割区间的任务完成了// [inbegin, rooti-1] rooti [rooti+1, inend]// 开始递归++prei;root->left = _build(preorder, inorder, prei, inbegin, rooti - 1);root->right = _build(preorder, inorder, prei, rooti + 1, inend);return root;}TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {int i = 0;return _build(preorder, inorder, i, 0, preorder.size() - 1);}
};

按中序、后序遍历构造二叉树

https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/description/

后序最后一个确定根,倒着来,先构建右子树
如果右树不为空,后序的下一个值就是右子树的根

    inorder = [9,3,15,20,7]
postorder = [9,15,7,20,3]

class Solution {
public:TreeNode* _build(vector<int>& inorder, vector<int>& postorder,int& posti, int inbegin, int inend){if (inbegin > inend)return nullptr;// 创建根节点TreeNode* root = new TreeNode(postorder[posti]);// 分割中序左右区间int rooti = inend;while (inbegin <= rooti){if (postorder[posti] == inorder[rooti])break;--rooti;}// posti使命完成// [inbegin, rooti-1] rooti [rooti+1, inend]--posti;root->right = _build(inorder, postorder, posti, rooti + 1, inend);root->left = _build(inorder, postorder, posti, inbegin, rooti - 1);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {int i = postorder.size() - 1;return _build(inorder, postorder, i, 0, i);}
};

按前序、后序不能重建二叉树,分割不出左子树右子树

6. 非递归 · 前序

144. 二叉树的前序遍历 - 力扣(LeetCode)

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {}
};

前序遍历刚开始肯定是把左边一路节点遍历,所以可以这么看一颗二叉树:

1. cur 指向一颗节点,意味着要访问它的右子树
2. 栈里的节点,意味着要访问它的右子树

class Solution {
public:vector<int> preorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;while (cur || !st.empty()){// 访问一颗树的开始// 访问左路节点,左路节点入栈,后续依次访问左路节点的右子树while (cur){v.push_back(cur->val);st.push(cur);cur = cur->left;}// 依次访问左路节点的右子树TreeNode* top = st.top();st.pop();// 子问题的方式访问右子树cur = top->right;}return v;}
};

7. 非递归 · 中序

94. 二叉树的中序遍历 - 力扣(LeetCode)

前序遍历,左路节点入栈时访问;中序遍历只是节点访问时机不同

思路:
        1. 左路节点入栈
        2. 访问左路节点、左路节点右子树

左路节点入完时,此时栈顶 top 指向 1,cur 指向 1 的左,cur == nullptr,不用访问(1 的左已经访问,可以访问 1 这个节点了),访问它;再访问 1 的右子树

本质:从栈里取到栈顶节点,则这个节点的左子树已经访问完了,可以访问节点、右子树了

class Solution {
public:vector<int> inorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;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;}
};

8. 非递归 · 后序

145. 二叉树的后序遍历 - 力扣(LeetCode)

右子树访问完,才能访问根

右不为空,怎么识别能不能访问 3 这个节点?
怎么知道是第一次访问 3 的右,还是 3 的右已经访问过了,避免死循环

如果 3 的右子树没有访问过,上一个访问的节点是左子树的根 1
如果 3 的右子树访问过,上一个访问的节点是右子树的根 6

一个节点右子树为空,或上一个访问的节点是右子树的根,则说明右子树访问过了,可以访问当前根节点

本质:从栈里取到栈顶节点,则这个节点的左子树已经访问完了

class Solution {
public:vector<int> postorderTraversal(TreeNode* root) {vector<int> v;stack<TreeNode*> st;TreeNode* cur = root;TreeNode* prev = nullptr;while (cur || !st.empty()){// 左路节点入栈while (cur){st.push(cur);cur = cur->left;}TreeNode* top = st.top();// 能不能访问这个节点?有2个标志if (top->right == nullptr || top->right == prev){prev = top;v.push_back(top->val);st.pop();}else // 不能访问这个节点,要先访问它的右子树{cur = top->right;}}return v;}
};

本篇的分享就到这里了,感谢观看,如果对你有帮助,别忘了点赞+收藏+关注
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章

http://www.dtcms.com/a/519859.html

相关文章:

  • Excel 重磅更新 AI技术走进公式
  • div嵌套影响网站收录建设公司需要网站吗
  • VBA技术资料MF383:处理Excel中存储为文本的数据
  • 注册网站的公司名字网站项目建设流程图
  • 大数据存储组件分别位于数据仓库的哪一层
  • Dubbo应用开发之RPC直连开发
  • 坦电容做电源滤波,放在陶瓷电容的前面还是后面好
  • 北京城建亚泰建设集团有限公司网站首页wordpress中文教程 下载
  • 虚幻引擎5 GAS开发俯视角RPG游戏 P06-13 属性菜单 - 边框值
  • Bash 括号:()、{}、[]、$()、$(() )、${}、[[]] 到底有什么区别?
  • bash执行脚本 CondaError: Run ‘conda init‘ before ‘conda activate‘
  • 虚幻引擎5 GAS开发俯视角RPG游戏 P06-11 初始化生命值和法力值属性
  • 做家政网站网络公司基础建设
  • 比特币白皮书
  • 网站速度诊断 慢坚持以高质量发展为首要任务戈
  • 苹果 T2 芯片如何影响 Mac 数据恢复
  • Open XR 手势控制模块开发。Monado 自定义手势识别模型 基于UltraLeap python实现
  • SQL Server ODBC 数据源配置指南(本地 + 远程,附实操细节)
  • 惠普电脑VT虚拟化技术开启指南:新旧BIOS设置全解析
  • SQL NULL 值
  • wordpress建站做客户端WordPress网站图片预加载
  • 动态规划核心原理与高级实战:从入门到精通(Java全解)
  • java设计模式七、代理模式
  • 【底层机制】【Android】AIDL原理与实现机制详解
  • 网站提交链接入口网站 seo优化
  • idea建有servlet类的web项目
  • Redis相关八股
  • zookeeper数据迁移
  • Java 大视界 -- Java 大数据机器学习模型在智能客服多轮对话系统中的优化策略
  • 怎么上网做网站dede网站模板怎么改