C++篇(14)二叉树进阶算法题
1、根据二叉树创建字符串
https://leetcode.cn/problems/construct-string-from-binary-tree
算法思路:左为空,右不为空,不能省略空括号。其他情况,空括号都可以省略。
参考题解:
/*** Definition for a binary tree 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 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、二叉树的层序遍历
https://leetcode.cn/problems/binary-tree-level-order-traversal
算法思路:在层序遍历的过程中,增加一个levelSize,记录每层的数据个数。树不为空的情况下,第一层levelSize = 1,循环控制,第一层出完了,第二层就都进队列了,队列中size就是第二层的数据个数。以此类推,假设levelSize为第n层的数据个数,因为层序遍历思想为当前层结点出队列,带入下一层结点(也就是子结点),循环控制第n层数据出完了,那么第n+1层结点都进队列了,队列的size就是下一层结点的levelSize。
参考题解:
/*** Definition for a binary tree 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 Solution {
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;// 一层一层出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;}
};
这道题还有一个变式题:
https://leetcode.cn/problems/binary-tree-level-order-traversal-ii
算法思路:刚刚是让我们自上而下来层序遍历,现在要自下而上来层序遍历,该怎么做呢?其实就是把我们刚刚得到的二维数组逆置一下即可。
参考题解:
/*** Definition for a binary tree 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 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()){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;}
};
3、二叉树的最近公共祖先
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree
算法思路1:仔细观察一下,两个结点,最近公共祖先的特征就是一个结点在最近公共祖先的左边,一个结点在最近公共祖先的右边。比如,6和4的公共祖先有5和3,但是只有最近的公共祖先5满足6在左边,4在右边。其他公共祖先都不满足,只有最近公共祖先满足这个规则。
参考题解1:
/*** 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:bool IsInTree(TreeNode* t, TreeNode* x){if(t == nullptr)return false;return t == x|| IsInTree(t->left, x)|| IsInTree(t->right, x);}TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(root == nullptr)return nullptr;if(root == p || root == q)return root;bool pInLeft = IsInTree(root->left, p);bool pInRight = !pInLeft;bool qInLeft = IsInTree(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);}
};
算法思路2:如果能求出两个结点到根结点的路径,那么就可以转换为链表相交问题。比如:6到3的路径为6->5->3,4到3的路径为4->2->5->3,那么看做两个链表找交点,交点5就是最近公共祖先。如果是三叉链的话就很好获取路径,但是这里是二叉链,该怎么得到路径呢?这里就要用到前序遍历的思路,用栈来记录查找路径,遇到结点先入栈,因为该结点就算不是我们要找的结点x,但是也有可能是分支结点。如果左右子树都没有结点x,那么说明入栈的结点不在路径上,所以要出栈,继续去其他分支路径进行查找。
参考题解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: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();}
};
4、将二叉搜索树转化为排序的双向链表
https://leetcode.cn/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof
算法思路1:中序遍历搜索二叉树,遍历顺序是有序的,将二叉树的结点指针放到一个vector中,再把前后结点的连接关系进行修改。这个思路最简单,但是要消耗O(N)的空间复杂度。
算法思路2:依旧中序遍历搜索二叉树,遍历顺序是有序的,遍历过程中修改左指针为前驱,右指针为后继。记录一个cur和prev,cur为当前中序遍历遍历到的结点,prev为上一个中序遍历的结点,cur->left指向prev,prev->right指向cur。
参考题解:
/*
// Definition for a Node.
class Node {
public:int val;Node* left;Node* right;Node() {}Node(int _val) {val = _val;left = NULL;right = NULL;}Node(int _val, Node* _left, Node* _right) {val = _val;left = _left;right = _right;}
};
*/
class Solution {
public:void InOrderConvert(Node* cur, Node*& prev){if(cur == nullptr)return;InOrderConvert(cur->left, prev);// cur中序// left指向中序前一个,左变成前驱cur->left = prev;// 中序前一个节点的右指向cur,右变成后继if(prev)prev->right = cur;prev = cur;InOrderConvert(cur->right, prev);}Node* treeToDoublyList(Node* root) {if(root == nullptr)return nullptr;Node* prev = nullptr;InOrderConvert(root, prev);Node* head = root;while(head->left){head = head->left;}//循环链表head->left = prev;prev->right = head;return head;}
};
5、从前序与中序遍历序列构造二叉树
https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal
算法思路:前序确定根,中序分割出左右子树。
参考题解:
/*** Definition for a binary tree 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 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;elserooti++;}prei++;// [inbegin, rooti-1] rooti [rooti+1, inend]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, inorder.size() - 1);}
};
6、从中序与后序遍历序列构造二叉树
https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal
算法思路:和上一题类似,只不过这次是通过后序遍历序列确定根,先递归构建右子树,再构建左子树。
参考题解:
/*** Definition for a binary tree 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 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 = inbegin;while(rooti <= inend){if(inorder[rooti] == postorder[posti])break;elserooti++;}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, inorder.size() - 1);}
};
7、二叉树的非递归前序遍历
https://leetcode.cn/problems/binary-tree-preorder-traversal
算法思路:先访问左路结点,再访问左路结点的右子树。
参考题解:
/*** Definition for a binary tree 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 Solution {
public:vector<int> preorderTraversal(TreeNode* root) {stack<TreeNode*> st;TreeNode* cur = root;vector<int> v;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;}
};
8、二叉树的非递归中序遍历
https://leetcode.cn/problems/binary-tree-inorder-traversal
算法思路:中序遍历和前序遍历思想是类似的,就是访问根的时候不一样。
参考题解:
/*** Definition for a binary tree 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 Solution {
public:vector<int> inorderTraversal(TreeNode* root) {stack<TreeNode*> st;TreeNode* cur = root;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;}
};
9、二叉树的非递归后序遍历
https://leetcode.cn/problems/binary-tree-postorder-traversal
算法思路:取到一个左路结点时,左子树已经访问过了。如果左路结点右子树不为空,右子树没有访问,那么上一个访问结点是左子树的根。如果左路结点右子树不为空,右子树已经访问过了,那么上一个访问结点是右子树的根。
参考题解:
/*** Definition for a binary tree 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 Solution {
public:vector<int> postorderTraversal(TreeNode* root) {stack<TreeNode*> st;TreeNode* cur = root;TreeNode* prev = nullptr;vector<int> v;while(cur || !st.empty()){while(cur){st.push(cur);cur = cur->left;}// 取一个左路结点的右子树出来访问,此时代表左路结点的左子树已经访问过了TreeNode* top = st.top();// 右子树为空或者上一个访问的结点是右子树的根,代表右子树也访问过了// 可以访问当前根结点if(top->right == nullptr || prev == top->right){v.push_back(top->val);st.pop();prev = top;}else{// 循环子问题的方式访问右子树cur = top->right;}}return v;}
};