训练题:序列化二叉树
题目:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/
// 宽度优先遍历string serialize( TreeNode* root ){if( !root ) { return ""; }queue<TreeNode*> q;TreeNode* tmpNode = nullptr;string ret;for( q.push( root ); !q.empty(); q.pop() ){tmpNode = q.front();if( tmpNode ){ret.append( to_string( tmpNode->val ) );q.push( tmpNode->left );q.push( tmpNode->right );}ret.append( "," );}return ret;}// Decodes your encoded data to tree.TreeNode* deserialize( string data ){if( data.empty() ) { return nullptr; }const auto size = data.size();queue<TreeNode*> q;stringstream ss;TreeNode* tmpNewNode = nullptr;TreeNode* tmpNode = nullptr;TreeNode* root = nullptr;bool start = true;bool tmpSuccess = false;bool tmpLeft = true;int tmpVal;for( int startIdx = 0, endIdx = startIdx; endIdx != size; ++endIdx ){if( data[endIdx] == ',' ){tmpSuccess = parseVal( data, ss, startIdx, endIdx, tmpVal );if( tmpSuccess ) { tmpNewNode = new TreeNode( tmpVal ); }else { tmpNewNode = nullptr; }if( start ){if( !tmpNewNode ) { return nullptr; }root = tmpNewNode;q.push( tmpNewNode );tmpLeft = true;start = false;}else{tmpNode = q.front();if( tmpLeft ) { tmpNode->left = tmpNewNode; }else{tmpNode->right = tmpNewNode;q.pop();}if( tmpNewNode ) { q.push( tmpNewNode ); }tmpLeft = !tmpLeft;}startIdx = endIdx + 1;}}return root;}bool parseVal( const string &data, stringstream &ss, const int &start, const int &end, int &val ){if( end <= start ) { return false; }ss << data.substr( start, end - start );ss >> val;ss.clear();return true;}
训练题:二叉树后续遍历序列
题目:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/
方法一:递归(模拟法)
- 时间复杂度:O(n2)
- 空间复杂度:O(n)
bool verifyPostorder( vector<int> &postorder ){if( postorder.size() > 1000 ) { return false; }return traverse( postorder, 0, static_cast<short>( postorder.size() - 1 ) );}bool traverse( vector<int> &postorder, short start, short end ){if( end <= start ) { return true; }// 左子树区间:[start, leftEnd)short leftEnd = start;for( ; leftEnd < end && postorder[leftEnd] < postorder[end]; ++leftEnd );// 存在左子树区间,则递归遍历。if(leftEnd != start && !traverse( postorder, start, leftEnd-1 )) return false;if(leftEnd != end){// 右子树区间:[leftEnd, rightEnd)short rightEnd = leftEnd;for( ; rightEnd < end && postorder[rightEnd] > postorder[end]; ++rightEnd);// 如果是二叉搜索树,则右子树区间应该是[leftEnd, end),即rightEnd = end。if(rightEnd != end) return false;// 递归遍历右子树区间if( !traverse( postorder, leftEnd, rightEnd-1 ) ) { return false; }}return true;}
方法二:栈
- 时间复杂度:O(n)
- 空间复杂度:O(n) ```cpp
bool verifyPostorder( vector
stack<int> stk;int root = INT_MAX;for(auto it = postorder.rbegin(); it != postorder.rend(); ++it){if(root < *it) return false;for(; !stk.empty() && *it < stk.top(); stk.pop()) root = stk.top();stk.push(*it);}return true;
}
<a name="JxWmC"></a># 训练题:二叉树查找路径题目:[http://t.cn/A6qUHzz3](http://t.cn/A6qUHzz3)<a name="M0UNJ"></a>## 方法:递归```cppvector<vector<int>> pathSum(TreeNode* root, int target) {if(!root) return {};vector<vector<int>> data;vector<int> path;int sum = target;traverse(root, data, path, sum);return data;}void traverse(TreeNode* root,vector<vector<int>> &data, vector<int>& path, int &sum){if(!root) return;path.push_back(root->val);sum -= root->val;if(!root->left && !root->right && !sum) data.push_back(path);else{traverse(root->left, data, path, sum);traverse(root->right, data, path, sum);}sum += root->val;path.pop_back();}
训练题:二叉搜索树转链表
递归方法一
Node* treeToDoublyList(Node* root) {if(!root) return root;Node head, *tail = &head;// 中序遍历root,转化成双向链表,并返回链表表尾tail = traverse(root, tail);// 表尾与表头接上。tail->right = head.right;head.right->left = tail;return head.right;}Node* traverse(Node* root, Node* tail){if(!root) return tail;tail = traverse(root->left, tail);tail->right = root;root->left = tail;tail = root;return traverse(root->right, tail);}
递归方法二
class Solution {public:Node* treeToDoublyList(Node* root) {if(!root) return root;traverse(root);head->left = tail;tail->right = head;return head;}private:Node* head, *tail;void traverse(Node* root){if(!root) return;traverse(root->left);if(!head) head = tail = root;else tail->right = root;root->left = tail;tail = root;traverse(root->right);}};
训练题:判断二叉树平衡
方法一:递归一
bool isBalanced(TreeNode* root) {if(!root) return true;return abs(depth(root->left) - depth(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right);}// 递归求root的深度int depth(TreeNode* root){if(!root) return 0;return max(depth(root->left), depth(root->right)) + 1;}
方法二:递归二
bool isBalanced(TreeNode* root) {return traverse(root) != -1;}int traverse(TreeNode* root){if(!root) return 0;int left = traverse(root->left);if(left == -1) return -1;int right = traverse(root->right);if(right == -1) return -1;return abs(left - right) < 2 ? max(left, right) + 1 : -1;}
训练题:最近公共祖先(LCA)
场景一:二叉搜索树
递归法
// 递归方法一:实测结果,这个更快一点TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(!root || root == p || root == q) return root;if((root->val - p->val)*(root->val - q->val) < 0) return root;if(root->val > p->val && root->val > q->val) return lowestCommonAncestor(root->left, p, q);else return lowestCommonAncestor(root->right, p, q);}// 递归方法二:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if(!root || root == p || root == q) return root;if(max(p->val, q->val) < root->val) return lowestCommonAncestor(root->left, p, q);if(min(p->val, q->val) > root->val) return lowestCommonAncestor(root->right, p, q);return root;}
迭代法
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {while(true){if( min(p->val, q->val) > root->val ) root = root->right;else if( max(p->val, q->val) < root->val ) root = root->left;else break;}return root;}
场景二:普通二叉树
方法一:递归
// 由于递归的深度优先特性(从下往上遍历),找到的一个公共祖先其实就是深度最大的祖先,即最近公共祖先。// root为遍历的当前根节点,p、q为其下的节点。TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {// 优先遍历到了p或者q节点、或者到叶节点都没有遍历到。if(!root || root == p || root == q) return root;// 分别遍历了一遍左右子树,分别得到了left/right节点TreeNode* left = lowestCommonAncestor(root->left, p, q);TreeNode* right = lowestCommonAncestor(root->right, p, q);// 如果left为空,而right不为空,则表示p是q的子节点,或者反之,只有这样才会导致遍历到p或者q就停止了。// right为空,left不为空同理。//// 如果left不为空且right不为空,则表示p、q分别在左右子树中,因此公共祖先应该是root。return !left ? right : (!right ? left : root);}
方法二:哈希表
用哈希表记录每个节点的父节点。问题就变成了查找两个链表的第一个公共节点。
见:https://www.yuque.com/tvvhealth/cs/pn55xq
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {map<TreeNode*, TreeNode*> info;recordParent(root, info);TreeNode* nodep = p, *nodeq = q;while(nodep != nodeq){nodep = nodep == root ? q : info[nodep];nodeq = nodeq == root ? p : info[nodeq];}return nodep;}void recordParent(TreeNode* root, map<TreeNode*, TreeNode*>& info){if(!root) return;if(root->left) info[root->left] = root;if(root->right) info[root->right] = root;recordParent(root->left, info);recordParent(root->right, info);}
训练题:二叉搜索树K大数
方法一:递归
int kthLargest(TreeNode* root, int k) {int val;traverse(root, val, k);return val;}void traverse(TreeNode* root, int&val, int &k){if(!k || !root) return;traverse(root->right, val, k);if(!(--k)){val = root->val;return;}traverse(root->left, val, k);}
方法二:迭代
int kthLargest(TreeNode* root, int k) {stack<TreeNode*> stk;stk.push(root);while(!stk.empty()){root = stk.top();stk.pop();if(!root->right && !--k){return root->val;}if(root->left) stk.push(root->left);stk.push(root);if(root->right) stk.push(root->right);}return -1;}
训练题:二叉树深度
方法一:递归
int maxDepth(TreeNode* root) {if(!root) return 0;return max(maxDepth(root->left), maxDepth(root->right)) + 1;}
方法二:迭代
广度优先算法。
int maxDepth(TreeNode* root) {int depth = 0;queue<TreeNode*> q1, q2;TreeNode* node;if(root) q1.push(root);while(!q1.empty() || !q2.empty()){if(!q1.empty()){++depth;while(!q1.empty()){node = q1.front();q1.pop();if(node->left) q2.push(node->left);if(node->right) q2.push(node->right);}}if(!q2.empty()){++depth;while(!q2.empty()){node = q2.front();q2.pop();if(node->left) q1.push(node->left);if(node->right) q1.push(node->right);}}}return depth;}
训练题:重建二叉树
方法一:递归
- 时间复杂度:O(n)
- 空间复杂度:O(n) ```cpp
TreeNode* buildTree( vector
const auto size_pre = preorder.size();const auto size_in = inorder.size();map<int, int> inMap;for( int i = 0; i < size_in; ++i ) { inMap[inorder[i]] = i; }return _build( preorder, inorder, inMap, 0, size_pre - 1, 0, size_in - 1 );
}
TreeNode* _build( const vector
TreeNode* root = new TreeNode( preorder[preStart] );if( preStart == preEnd ) { return root; }// 当前根节点在中序遍历中的索引。int rootIdx_in = inMap[preorder[preStart]];root->left = _build( preorder, inorder, inMap, preStart + 1, preStart + rootIdx_in - inStart, inStart, rootIdx_in - 1 );root->right = _build( preorder, inorder, inMap, preStart + rootIdx_in - inStart + 1, preEnd, rootIdx_in + 1, inEnd );return root;
}
<a name="QQ8hK"></a>## 方法二:迭代(待完成)```cpp
训练题:树的子结构
方法一:双递归
递归一:遍历目标子树
递归二:在递归一的每个子过程中,再递归检测当前递归节点为根节点的目标子树。
- 时间复杂度:O(AB)
- 空间复杂度:O(A) ```cpp
// 递归1 bool isSubStructure( TreeNode A, TreeNode B ) { if( !A || !B ) { return false; } return fuck( A, B ) || isSubStructure( A->left, B ) || isSubStructure( A->right, B ); }
// 递归2 bool fuck( TreeNode A, TreeNode B ) { return !B || A && A->val == B->val && fuck( A->left, B->left ) && fuck( A->right, B->right ); }
<a name="d3WYD"></a># 训练题:树的镜像题目:[http://t.cn/A6GgnP1E](http://t.cn/A6GgnP1E)<a name="e5KM1"></a>## 方法一:递归- 时间复杂度:O(n)- 空间复杂度:O(n)```cppTreeNode* mirrorTree(TreeNode* root) {if(!root) return root;TreeNode* tmpNode = nullptr;tmpNode = root->left;root->left = mirrorTree(root->right);root->right = mirrorTree(tmpNode);return root;}
方法二:迭代(辅助栈/队列)
TreeNode* mirrorTree(TreeNode* root) {if(!root) return root;queue<TreeNode*> q; // 可以用栈/队列q.push(root);for(TreeNode* node, *tmpNode; !q.empty();){node = q.front();q.pop()tmpNode = node->left;node->left = node->right;node->right = tmpNode;if(node->left) q.push(node->left);if(node->right) q.push(node->right);}return root;}
训练题:对称二叉树
方法一:递归
bool isSymmetric(TreeNode* root) {if(!root) return true;return fuck(root->left, root->right);}bool fuck(TreeNode* left, TreeNode* right){if(!left && !right) return true;if(!left || !right || left->val != right->val) return false;return fuck(left->left, right->right) && fuck(left->right, right->left);}
方法二:迭代
既然可以递归,那就可以考虑迭代。
如果是对称的,则中-左-右和中-右-左遍历的每一步都是对称的。可以用队列或者栈。
bool isSymmetric(TreeNode* root) {if(!root || !root->left && !root->right) return true;if(!root->left || !root->left) return false;queue<TreeNode*> q1, q2;q1.push(root);q2.push(root);for(TreeNode* tmpNode1, *tmpNode2; !q1.empty() && !q2.empty();) {tmpNode1 = q1.front();tmpNode2 = q2.front();q1.pop();q2.pop();if(!tmpNode1 && !tmpNode2){}else if(tmpNode1 && tmpNode2 && tmpNode1->val == tmpNode2->val){}else return false;if(tmpNode1){q1.push(tmpNode1->left);q1.push(tmpNode1->right);q2.push(tmpNode2->right);q2.push(tmpNode2->left);}}return true;}
方法三:迭代(改进)
可以将两个队列合并,将两个遍历的过程每一步都是先后放即可。
bool isSymmetric(TreeNode* root) {if(!root || !root->left && !root->right) return true;if(!root->left || !root->left) return false;queue<TreeNode*> q;q.push(root);q.push(root);for(TreeNode* tmpNode1, *tmpNode2; !q.empty();) {tmpNode1 = q.front();q.pop();tmpNode2 = q.front();q.pop();if(!tmpNode1 && !tmpNode2){}else if(tmpNode1 && tmpNode2 && tmpNode1->val == tmpNode2->val){}else return false;if(tmpNode1){q.push(tmpNode1->left);q.push(tmpNode2->right);q.push(tmpNode1->right);q.push(tmpNode2->left);}}return true;}
训练题:二叉树遍历
场景一:递归遍历(前、中、后)
/*** struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/// 前序遍历void preOrder( TreeNode* root ){if( !root ) { return; }printf("%d", root->val);preOrder( root->left, container );preOrder( root->right, container );}// 中序遍历void midOrder( TreeNode* root, vector<int> &container ){if( !root ) { return; }midOrder( root->left, container );printf("%d", root->val);midOrder( root->right, container );}// 后序遍历void postOrder( TreeNode* root, vector<int> &container ){if( !root ) { return; }postOrder( root->left, container );postOrder( root->right, container );printf("%d", root->val);}
场景二:层序遍历
自顶向下
题目:http://t.cn/A6A1oc4c
用队列结构。
vector<vector<int>> levelOrder(TreeNode* root) {vector<vector<int>> results{};if( !root ) { return results; }// 用了两个队列交替进行,避免了记录数的深度// 每个队列的节点在同一深度,每次交替深度就会+1queue<TreeNode*> q1, q2;q1.push( root );TreeNode* tmpNode = nullptr;auto shit = [&]( queue<TreeNode*> &q_out, queue<TreeNode*> &q_in ){if( !q_out.empty() ) results.push_back( {} );auto &vec = results.back();while( !q_out.empty() ){tmpNode = q_out.front();q_out.pop();vec.push_back( tmpNode->val );if( tmpNode->left ) { q_in.push( tmpNode->left ); }if( tmpNode->right ) { q_in.push( tmpNode->right ); }}};while( !q1.empty() || !q2.empty() ){shit( q1, q2 );shit( q2, q1 );}return results;}
自底向上
- 思路一
- 将上面(自顶向下)的结果逆序。
- 思路二
- 相对更高效,用list结构代替vector结构,在list头部插入遍历的当前层的结果即可。vector在头部插入的效率很低。不过本地返回类型限制了vector。 ```cpp
vector
if( !root ) { return results; }queue<pair<TreeNode*, int>> q;q.push( { root, 0 } );TreeNode* tmpNode = nullptr;int tmpDepth = -1;while( !q.empty() ){auto data = q.front();tmpNode = data.first;tmpDepth = data.second;q.pop();if( tmpDepth == results.size() )results.push_back( {} );results[tmpDepth].push_back( tmpNode->val );if( tmpNode->left ) q.push( { tmpNode->left, tmpDepth + 1 } );if( tmpNode->right ) q.push( { tmpNode->right, tmpDepth + 1 } );}reverse(results.begin(), results.end());return results;
}
<a name="rgOWB"></a>## 场景三:之字形遍历题目:[http://t.cn/A6VhF78F](http://t.cn/A6VhF78F)<a name="FlJ11"></a>### 方法一按场景二层序遍历,然后对偶数位置的vector进行逆序。```cppvector<vector<int> > zigzagLevelOrder( TreeNode* root ){vector<vector<int>> results{};if( !root ) { return results; }// 用了两个队列交替进行,避免了记录数的深度// 每个队列的节点在同一深度,每次交替深度就会+1queue<TreeNode*> q1, q2;q1.push( root );TreeNode* tmpNode = nullptr;auto shit = [&]( queue<TreeNode*> &q_out, queue<TreeNode*> &q_in ){if( !q_out.empty() ) results.push_back( {} );auto &vec = results.back();while( !q_out.empty() ){tmpNode = q_out.front();q_out.pop();vec.push_back( tmpNode->val );if( tmpNode->left ) { q_in.push( tmpNode->left ); }if( tmpNode->right ) { q_in.push( tmpNode->right ); }}};while( !q1.empty() || !q2.empty() ){shit( q1, q2 );shit( q2, q1 );}for( int i = 0; i < results.size(); i++ ){if( i & 0x1 ) { reverse( results[i].begin(), results[i].end() ); }}return results;}
方法二
类似层序遍历用两个queue,这里用两个stack代替。
vector<vector<int> > zigzagLevelOrder( TreeNode* root ){vector<vector<int>> results{};if( !root ) { return results; }stack<TreeNode*> q1, q2;q1.push( root );TreeNode* tmpNode = nullptr;auto shit = [&]( stack<TreeNode*> &q_out, stack<TreeNode*> &q_in, bool flag ){if( !q_out.empty() ) results.push_back( {} );auto &vec = results.back();while( !q_out.empty() ){tmpNode = q_out.top();q_out.pop();vec.push_back( tmpNode->val );if( flag ){if( tmpNode->right ) { q_in.push( tmpNode->right ); }if( tmpNode->left ) { q_in.push( tmpNode->left ); }continue;}if( tmpNode->left ) { q_in.push( tmpNode->left ); }if( tmpNode->right ) { q_in.push( tmpNode->right ); }}};while( !q1.empty() || !q2.empty() ){shit( q1, q2, false );shit( q2, q1, true );}return results;}
场景四:N叉树的遍历
原理和二叉树类似,不过是对子节点访问有不同。
