LeetCode 树习题

树的遍历


非递归实现

        树的非递归有四种实现,先序中序后序 层次。


先序遍历

        是单个栈模拟,先存入根节点。当栈不为空的时候弹出一个元素。先存入其右孩子,再存入左孩子。

using pti = pair<TreeNode *, int>;
pti temp;
stack< pti >s;  s.push(pti(root, 1));
int ans = 0;
while(!s.empty())
{
    temp = s.top(); s.pop();
    if(temp.second > ans) ans = temp.second;
    if(temp.first->right) s.push(pti(temp.first->right, temp.second + 1));
    if(temp.first->left) s.push(pti(temp.first->left, temp.second + 1));
}

中序遍历

中序遍历也是单个栈模拟。

  • 一个临时节点指向根节点。
  • 当栈不为空或者临时节点不为空的时候。递归获取压入临时节点,然后指向左子节点,直至为空。此时到达第一个意义上的中根节点。
  • 如果右子节点不为空,临时节点指向右子节点。返回2.
using pti = pair<TreeNode *, int>;
stack< pti > s;
int ans = 0, d = 0;
pti temp,cur(root, 1);
while(!s.empty() || cur.first != nullptr)
{
    while(cur.first != nullptr)
    {
        s.push(cur);
        cur = pti(cur.first->left, cur.second + 1);
    }
    temp = s.top(); s.pop();
    if(temp.second > ans ) ans = temp.second;
    if(temp.first->right != nullptr)
        cur = pti(temp.first->right, temp.second + 1);
}

后序遍历

后序遍历需要两个栈模拟。一个栈用来存储待访问的孩子节点,一个用来保存已经访问的节点,然后逆序弹出的顺序就是后序的顺序。(先访问根节点,再访问右孩子,再左孩子,逆序就是后续)

  • 先存入根节点
  • 当待访问栈不空,弹出节点,加入访问栈。如果左孩子不空访问左孩子,右孩子不空访问右孩子。
  • 最后逆序弹出访问栈的节点即遍历顺序。
f.push(pti(root, 1));
int ans = 0;
while(!f.empty())
{
    tmp =  f.top(); f.pop(); // fd.push(tmp);
    if(tmp.second > ans) ans = tmp.second;
    if(tmp.first->left  != nullptr) 
        f.push(pti(tmp.first->left,  tmp.second + 1));
    if(tmp.first->right != nullptr) 
        f.push(pti(tmp.first->right, tmp.second + 1));
}
/*while(!fd.empty())
{
    temp = fd.top(); fd.pop();
}*/

层序遍历

层序遍历使用que。

  • 压入根节点。
  • 队列不为空的时候,弹出一个元素。左孩子不为空压入左孩子,右孩子不为空压入右孩子。
queue<pti>q;q.push(pti(root, 1));
while(!q.empty())
{
    temp = q.front(); q.pop();
    if(temp.second > ans) ans = temp.second;
    if(temp.first->left  != nullptr) 
        q.push(pti(temp.first->left,  temp.second + 1));
    if(temp.first->right != nullptr) 
        q.push(pti(temp.first->right, temp.second + 1));
}


剑指 Offer 55 - I. 二叉树的深度

int maxDepth(TreeNode* root) {
    if(root == nullptr) return 0;
    return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}

        递归的方式很简单,面试的时候大概会要回非递归。而且树很深的时候,递归的方式会导致栈溢出。由此看我们必须熟练掌握树的各种遍历形式。以及排序算法的非递归实现。

int maxDepth(TreeNode* root) {
    if(root == nullptr) return 0;
    using pti = pair<TreeNode *, int>;
    pti temp;
    int ans = 0;
    queue<pti>q;q.push(pti(root, 1));
    while(!q.empty())
    {
        temp = q.front(); q.pop();
        if(temp.second > ans) ans = temp.second;
        if(temp.first->left  != nullptr) q.push(pti(temp.first->left,  temp.second + 1));
        if(temp.first->right != nullptr) q.push(pti(temp.first->right, temp.second + 1));
    }
    return ans;
}

剑指 Offer 26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)  B是A的子结构, 即 A中有出现和B相同的结构和节点值

        此题的思路是,对于任意一棵子树a, b,判断是否匹配的终止条件是,如果b已经为空了,说明之前的节点均匹配上,返回true。如果b不为空但是a为空,或者a节点和b节点的值不相等,说明a节点已经匹配不上了,返回false。如果相等,递归判断他们的左右子树节点是否匹配。

        主函数中,我们遍历a树的节点,判断其子树是否与b树匹配。否则的话, 递归其左右子树分别与b进行匹配。        

bool isSubStructure(TreeNode* A, TreeNode* B) {
    if(A == nullptr || B == nullptr) return false;
    return helper( A, B) || isSubStructure( A->left, B) ||
                            isSubStructure( A->right, B);
}
bool helper(TreeNode * l, TreeNode * r)
{
    if(r == nullptr) return 1;
    if(l == nullptr || l->val != r->val) return 0;
    return helper( l->left, r->left) && helper( l->right, r->right);
}

剑指 Offer 07. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

        对于前序遍历,第一个是根节点,其后是左子树部分,在之后是右子树部分。对于中序遍历,根节点左侧是左子树的接单,右侧是右子树的节点。有了这两条性质,我们可以前序遍历创建根节点,然后在中序遍历中找到根节点,根节点左侧的节点的数目即左子树的数目,右侧即右子树的数目。由此可以在前序序列中分别找到左子节点,右子节点。

TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
    return build(preorder, inorder, 0, 0, inorder.size() - 1);
}
TreeNode* build(vector<int>& preorder, vector<int>& inorder, int root, int start, int end){// 中序的start和end
    if(start > end) return NULL;
    TreeNode *tree = new TreeNode(preorder[root]);
    int i = start;
    while(i < end && preorder[root] != inorder[i]) i++;
    tree->left = build(preorder, inorder, root + 1, start, i - 1);
    tree->right = build(preorder, inorder, root + 1 + i - start, i + 1, end);
    return tree;
}

剑指 Offer 27. 二叉树的镜像

        从根节点开始自上而下的反转每一个节点的左右子节点。

        如果当前根节点为空,返回空

        然后调整根节点的左右子树,其左子树指向对原来右子树镜像反转后返回的根节点,其右子树指向对原来的左子树镜像反转后的根节点。

        调整完毕,返回当前根节点

if(root == nullptr) return root;
TreeNode * templ = mirrorTree(root->right);
root->right = mirrorTree(root->left);
root->left = templ;
return root;

剑指 Offer 28. 对称的二叉树

如果一棵二叉树和它的镜像一样,那么它是对称的。

        设置一个辅助函数,判断两个子树根节点是否相等,不相等返回false,相等的话递归判断,左子树的右子树和右子树的左子树,以及左子树的左子树和右子树的右子树。

    bool isSymmetric(TreeNode* root) {
        if(root == nullptr) return 1;
        return helper(root->left, root->right);
    }

    bool helper(TreeNode * l,TreeNode * r)
    {
        if((l== nullptr && r == nullptr )) return 1;
        if ( l== nullptr|| r == nullptr || l->val != r->val ) return 0;
        return helper(l->left, r->right) &&  helper(l->right, r->left);
    }


剑指 Offer 33. 二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false

        以及二叉搜索树left < root < right 。且后序遍历的顺序是left -> right ->root。由此我们可知,第一个大于最右侧节点的是右子树,其每一个元素都应该大于根节点的值。其左侧元素为左子树,其每一个元素都应该小于根节点的值。将其分为两个子树后,再分别判断两个子树的是否是正确的。

bool helper(vector<int>&nums, int l, int r)
{
    if (r <= l) return 1;
    int i = l, tar = nums[r];
    while (i < r)
    {
        if (nums[i] > tar)
            break;
        i++;
    }
    for (int j = i; j < r; j++)
        if (nums[j] <= tar)
            return 0;
    return helper(nums, l, i - 1) && helper(nums, i, r - 1);
}

剑指 Offer 37. 序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树。

        序列化和反序列化本质上是对二叉树进行编码再解码。既然要对每一个元素编码就涉及到树的遍历。层序遍历对树的信息的保留是最完全的,优先考虑使用层序遍历。

        编码:层序遍历节点,如果不为空,保留字符,在queue中加入左右子节点。如果为空,则保留空字符,方便解码时确定。

    string serialize(TreeNode* root) {
        if ( root == nullptr ) return "";
        ostringstream output;
        queue<TreeNode*> que;
        que.push(root);
        while ( !que.empty() ) {
            TreeNode* node = que.front();
            que.pop();
            if ( node == nullptr ) output << "# ";
            else {
                output << node->val << ' ';
                que.push(node->left);
                que.push(node->right);
            }
        }
        return output.str();
    }

        解码:首先根据编码str[id]解码根节点,压入queue。queue不为空的时候,每次弹出一个元素。当前节点不为空的时候,因为编码阶段是层序遍历,那么其左右子节点必为str[id + 1], str[id + 2]。就可以更新当前根节点左右孩子节点,然后queue中压入左右孩子节点。当前节点为空的时候说明当前子树为空,pass。

    TreeNode* deserialize(string data) {
        if ( data.empty() ) return nullptr;
        vector<TreeNode*> nodes;
        string val;
        istringstream input(data);
        while ( input >> val ) {
            if ( val == "#" ) nodes.push_back(nullptr);
            else nodes.push_back(new TreeNode(stoi(val)));
        };
        int pos = 1;
        for ( int i = 0; i < nodes.size(); ++i ) {
            if ( nodes[i] == nullptr ) continue;
            nodes[i]->left = nodes[pos++];
            nodes[i]->right = nodes[pos++];
        }
        return nodes[0];
    };

剑指 Offer 36. 二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树原地转换成一个排序的循环双向链表。

        二叉搜索树为递增序列,其中序遍历即有有序链表。依赖于一个指针pre。中序遍历树,递归左孩子。如果pre为空,说明是第一个节点,最小的元素,head指向root。如果不为空,那么root是仅比pre大的节点,即pre的下一个节点,将pre右孩子指向root。 root左孩子指向pre。pre更新为root。再递归右孩子。

void inorder(Node * root)
{
    if(root == nullptr ) return;
    inorder(root->left);
    if(pre == nullptr) head = root;
    else pre->right = root;
    root->left = pre;
    pre = root;
    inorder(root->right);
}

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先    

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先        

        

如果一个root是pq的公共祖先,要么,pq在root的两侧,要么,p或者q是root,另外一个是其左右子树中的节点。利用二叉树的性质,小于根节点的都在根节点的左侧,大于根节点的都在根节点的右侧。他们的公共祖先应该就在从根节点到他们的第一个大于a小于b的分叉节点。

TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if(root == nullptr) return root;
    if(root->val > p->val && root->val > q->val)
        return lowestCommonAncestor(root->left, p, q);
    if(root->val < p->val && root->val < q->val) 
        return lowestCommonAncestor(root->right, p, q);
    return root;
}

剑指 Offer 68 - II. 二叉树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

        考虑对二叉树后序遍历,搜索pq节点,找到后返回,自底向上回溯。

        当一个节点第一次同时左右子树都找到pq两个节点的时候,说明他就是最近公共祖先,我们对其返回。

        当前一个root左子树有p或者q,而右子树没有,要么,pq均在左子树,这个左子树节点就是作为公共祖先返回,要么,pq在最近公共祖先的两侧,那么返回该节子点作为指示该子树包含一个节点。

        如果root两个子树搜都没搜到,说明不可能为祖先,返回空节点

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root == nullptr) return root;
        
        if(root->val == p->val || root->val == q->val)
            return root;
        
        TreeNode * left = lowestCommonAncestor(root->left, p, q);
        TreeNode * right = lowestCommonAncestor(root->right, p, q);

        if(left != nullptr && right != nullptr) return root;
        else if(left == nullptr && right != nullptr) return right;
        else if(right == nullptr && left != nullptr) return left;
        return nullptr;
        
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值