1.606. 根据二叉树创建字符串 - 力扣(LeetCode)
思路:前序遍历创建字符串,注意点:(左子树不为空)或者(左子树为空,右子树不为空),左子树要加();右子树不为空,右子树要加()。
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; } };
2.102. 二叉树的层序遍历 - 力扣(LeetCode)和 107. 二叉树的层序遍历 II - 力扣(LeetCode)
思路:用一个levelSize记录每层节点的数量,按层数出,达到分层遍历的效果。
102解答:
class Solution { public: vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> vv; if(root==nullptr) return vv; queue<TreeNode*> q; q.push(root); int levelSize = 1; while(!q.empty()) { vector<int> v; while(levelSize--) { TreeNode* tmp = q.front(); q.pop(); if(tmp->left) q.push(tmp->left); if(tmp->right) q.push(tmp->right); v.push_back(tmp->val); } vv.push_back(v); levelSize = q.size(); } return vv; } };
107解答:在return vv; 前,将vv逆置一下,reverse(vv.begin(),vv.end());
3.236. 二叉树的最近公共祖先 - 力扣(LeetCode)
思路一:最近公共祖先,两个子节点一定分布在它的左右子树。
还有种特殊情况,如果一节点是另一节点的祖先,那么该节点也是两节点的最近公共祖先。
实现过程:根节点位置,如果其中一个节点等于根节点,说明根结点就是最近公共祖先,直接返回根节点;若两个节点都不等于根结点,从左右子树去找两个节点,(如果同时在左子树/右子树,就说明最近公共祖先在左子树/右子树,就递归去对应的左子树/右子树找;如果一个节点在左子树,另一个节点在右子树,就说明根节点就是最近公共祖先,返回根结点)
class Solution { public: bool find(TreeNode* root,TreeNode* x) { if(root==nullptr) return false; if(root==x) return true; return find(root->left,x) || find(root->right,x); } TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(root==nullptr) return nullptr; if(root==p || root==q) return root; bool pInLeft = find(root->left,p); bool pInRight = !pInLeft; bool qInLeft = find(root->left,q); bool qInRight = !qInLeft; //1.p,q在不同的左右子树,root就是最近公共祖先 //2.p,q都在左子树,递归左子树 //3.p,q都在右子树,递归右子树 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),总体时间复杂度为O(N^2),如果是一颗满二叉树,时间复杂度为O(N*logN)。
思路2:通过前序遍历加栈,实现存储节点到根节点的路径;得到两条路径后,把这两条路径转换成“链表相交”的模型,最近公共祖先就是交点。
实现过程:如果root为nullptr,返回false;前序遍历root,遇到节点先入栈,然后判断是否等于要找的节点x。若等于,返回true;若不等于,继续递归找左子树和右子树(此时不回退,因为root可能是路径上的一个节点);如果左右子树都没有,那么删除栈pop(),回溯,返回false。
class Solution { public: bool getPath(TreeNode* root,stack<TreeNode*>& st,TreeNode* x) { if(root==nullptr) return false; st.push(root); //找到了节点,路径完整了 if(st.top()==x) return true; //在左子树或右子树找到了,路径完整了 if(getPath(root->left,st,x) || getPath(root->right,st,x)) return true; //没有找到,回退root st.pop(); return false; } TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { stack<TreeNode*> pst; stack<TreeNode*> qst; getPath(root,pst,p); getPath(root,qst,q); //让长的栈和短的栈一样长 while(pst.size()!=qst.size()) { if(pst.size()>qst.size()) { pst.pop(); } else { qst.pop(); } } //找交点 while(pst.top()!=qst.top()) { pst.pop(); qst.pop(); } return pst.top(); } };
此算法的时间复杂度为O(N)。
4.二叉搜索树与双向链表_牛客题霸_牛客网 (nowcoder.com)
思路:用两个节点指针prev和cur,prev初始化成nullptr,cur初始化成根。cur指向当前中序遍历的节点,prev指向当前中序遍历节点的上一个节点,每次走,cur的前驱(left)指向prev,prev的后继(right)指向cur,直到cur走到空,此时prev为中序最后一个。找到中序的第一个,返回。
图中顺序为中序遍历的链接顺序。
class Solution { public: void Inorder(TreeNode* cur,TreeNode*& prev) { if(cur==nullptr) return; Inorder(cur->left,prev); if(prev) prev->right = cur; cur->left = prev; prev = cur; Inorder(cur->right,prev); } TreeNode* Convert(TreeNode* pRootOfTree) { if(pRootOfTree==nullptr) return nullptr; TreeNode* prev = nullptr; Inorder(pRootOfTree,prev); TreeNode* firstInorder = pRootOfTree; //找到中序第一个 while (firstInorder->left) { firstInorder = firstInorder->left; } return firstInorder; } };
5.105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
105思路:采用前序遍历递归构建二叉树,先构建根,然后根据中序遍历分割左右子树的区间,然后根据中序遍历的区间,递归构建左子树和右子树与根链接,这样一颗二叉树就构建好了。(思想是分治思想,类似快速排序,先排一个元素,然后分割左右区间,递归排序左右区间,整个区间就排序好了)
class Solution { public: TreeNode* build(vector<int>& preorder, vector<int>& inorder, int inbegin,int inend,int& prei) { //区间不存在,不用构建 if(inbegin>inend) return nullptr; int val = preorder[prei++]; TreeNode* root = new TreeNode(val); //分割左右子树 int rooti; for(int i = inbegin;i<=inend;++i) { if(inorder[i]==val) { rooti = i; break; } } //分割出左右子树区间, //[inbegin,rooti-1] rooti [rooti+1,inend] //递归构建左右子树并链接 root->left = build(preorder,inorder,inbegin,rooti-1,prei); root->right = build(preorder,inorder,rooti+1,inend,prei); return root; } TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { int prei = 0; TreeNode* root = build(preorder,inorder,0,inorder.size()-1,prei); return root; } };
106思路:和前序遍历反过来差不多,后序遍历是 [左子树,右子树,根],根在最后,是倒序的方式,但是倒序是:根,右子树,左子树,划分区间与105思路一致,但是递归构建时优先构建右子树。
class Solution { public: TreeNode* build(vector<int>& postorder, vector<int>& inorder, int inbegin,int inend,int& prei) { //区间不存在,不用构建 if(inbegin>inend) return nullptr; int val = postorder[prei--]; TreeNode* root = new TreeNode(val); //分割左右子树 int rooti; for(int i = inbegin;i<=inend;++i) { if(inorder[i]==val) { rooti = i; break; } } //分割出左右子树区间, //[inbegin,rooti-1] rooti [rooti+1,inend] //优先递归构建右子树 root->right = build(postorder,inorder,rooti+1,inend,prei); root->left = build(postorder,inorder,inbegin,rooti-1,prei); return root; } TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { int prei = postorder.size()-1; TreeNode* root = build(postorder,inorder,0,inorder.size()-1,prei); return root; } };
6.二叉树的前中后序遍历的非递归。
144. 二叉树的前序遍历 - 力扣(LeetCode)
思路:
1.先访问左路节点(根和左子树)2.访问左路节点的右子树
实现过程:从根开始一直访问左子树,存入根的值,直到左路节点被访问完了,然后从栈上获取数据,顶层节点的右即为第一个被访问的右节点,访问左子树的右,用cur记录。(再次循环,从根开始一直访问左子树,存入根的值,直到左路节点被访问完了,取出左路节点,访问右,赋值给cur)
class Solution { public: vector<int> preorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> v; TreeNode* cur = root; //cur不为空(当前根不是空)和栈不为空(还有右子树未访问)就继续 while(cur || !st.empty()) { //左路节点入栈,由于先访问根,存入vector中 while(cur) { st.push(cur); v.push_back(cur->val); cur = cur->left; } //此时左路节点访问完了,访问左路节点的右子树 //取出最顶层节点 TreeNode* top = st.top(); st.pop(); //访问右子树 cur = top->right; } return v; } };
思路:
1.先访问左路节点
2.访问左路节点的右子树
实现过程:与前序遍历的思想类似,只不过区别在于根的访问时机,中序遍历是:左子树 根 右子树,第一个访问节点为最左节点,即栈上存左路节点的最顶层节点,于是只需将原访问根的顺序改为,取出节点时访问即可。
class Solution { public: vector<int> inorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> v; TreeNode* cur = root; //cur不为空(当前根不是空)和栈不为空(还有右子树未访问)就继续 while(cur || !st.empty()) { //左路节点入栈,由于先访问根,存入vector中 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.先访问左路节点
2.访问左路节点的右子树
问题就在于:后序遍历是:左子树,右子树,根的顺序,左子树的访问是可以确定能访问完的(先访问完左路节点),因此上一个题目中序非递归是能确定左子树访问完了,此时问题就在于如何判断右子树是否已经访问完了。
分析:
1.取出节点表示左子树是被访问完了。
2.如果取到的节点的右子树为空,说明右子树访问完了,可以访问根
3.如果取到的节点的右子树为后序遍历的上一个节点(等于prev),说明右子树访问完了,可以访问根。
4.如果取到的节点的右子树不是后序遍历的上一个节点(不等于prev),说明右子树还没有被访问完,要访问右子树。
class Solution { public: vector<int> postorderTraversal(TreeNode* root) { stack<TreeNode*> st; vector<int> v; TreeNode* prev = nullptr; TreeNode* cur = root; //cur不为空(当前根不是空) //栈不为空(还有右子树和根或者根未访问)就继续 while(cur || !st.empty()) { //左路节点入栈 while(cur) { st.push(cur); cur = cur->left; } //取出最顶层节点 TreeNode* top = st.top(); //右节点为空 或者 右节点等于后序遍历的上一个节点 可以访问根 if(top->right==nullptr || top->right==prev) { v.push_back(top->val); prev = top; st.pop(); } //右子树还未被访问,访问右子树 else { cur = top->right; } } return v; } };