训练题:序列化二叉树
题目: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>
## 方法:递归
```cpp
vector<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)
```cpp
TreeNode* 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; }
// 用了两个队列交替进行,避免了记录数的深度
// 每个队列的节点在同一深度,每次交替深度就会+1
queue<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进行逆序。
```cpp
vector<vector<int> > zigzagLevelOrder( TreeNode* root )
{
vector<vector<int>> results{};
if( !root ) { return results; }
// 用了两个队列交替进行,避免了记录数的深度
// 每个队列的节点在同一深度,每次交替深度就会+1
queue<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叉树的遍历
原理和二叉树类似,不过是对子节点访问有不同。