面试热点二叉树那点事儿(一)中

简介: 前面写了很多篇工程相关的文章,今天准备写个数据结构和算法相关的文章。

0x02.寻找二叉树的门

不好意思前面又让大家喝了一碗鸡汤,现在准备开始啃鸡腿了呦!


前面提到我是最近两天做了3道二叉树的问题,发现了一些共性问题,所以才决定写一写的,或许后面做了更多的题目会有更多的心得,所以大家持续关注吧!


首先声明一点:笔者的迭代做法均不是最优解,基本上在AC的时候被一大半的人从时间和空间打败了,可能有人会问那你还写它干啥?


笔者看来最优解诚可贵,但是很多时候在没有见过题目,现场Coding时能有个正确的思路比啥都强,当时ACM大神就另当别论了,我们固然追求最优解,多种尝试解题,但是有个保底的通用思维也是双保险嘛,这也是本文的重点。


前面的三道题:两棵相同的树、一棵自成镜像对称的树、Z型遍历树,笔者除了用递归实现,最终都尝试了一种迭代层次遍历来解决,因为遍历对我们来说更加容易,紧张场景下我们必然选择自己熟悉的东西,来看下笔者在做这几道题是的一些过程:


  • 100题 两棵相同的树问题

迭代层次遍历,保留树中的空节点,由于树节点的值是int,为了区分空节点,统一转换为string存储,这样一棵树经过转换就成为了vector类型,从而树的问题转换为了数组问题。


  • 101题 一棵镜像的树

这个还是采用迭代层次遍历,int转string 保存空节点为null存储到vector中,本题是一棵树的问题,有两种路子:

a.层次遍历中每一层的节点时回文式的

b.层次遍历时先左后右存储一个vector 再从右到左存储一个vector 两次遍历 两个vector相等 表明树是镜像的

笔者使用b方法编码并AC,a方法因为涉及分层判定回文稍微复杂一些


  • 103题 锯齿状Z型遍历树

这个问题和镜像树有些类似,还是可以采用迭代分层遍历,由于涉及到按照层编号来修改遍历方向,因此需要做一些额外工作,对此笔者进行了一个AC实现,但是我并不觉得这个是我想要的通用方法,所以我并没有在遍历过程中判断层,因为在树上做其他操作容易让我晕,索性遍历存储为vector,其实最开始是按照满二叉树进行存储的,在提交过程中发现并不是最优的,所以做了一些调整,但是时间和空间都不算很好。

从上面的三道题可以看到,我均使用了迭代式层次遍历,区别在于根据每道题的特性做了数组级别的调整,而不是树级别的调整,我们知道数组更好理解也更好处理,这是个降维过程


写到这里,仿佛有点意思了,所以再次重申本文不是找最优解而是通用策略,目的是我们在面试场上迅速找个一个可靠的解决方法,先实现再优化和Offer更搭哦


0x03.单挑Z型变换遍历


Talk is Cheap,Show Me The Code!


3.1 Z型变换草稿


我们从我认为更难一些的第103题来体验一下这个二叉树的门,开始我们的分析过程:


  • 从一般到特殊的思维

现实世界中大部分东西都是一般存在的,但是我们在课堂上学习的很多东西都是特例化存在,比如线性代数里面的方阵、二次型、物理中也是如此,这么做的原因是特例的东西富含更多的规律,更容易掌握,说道这个让我想起一句话:"山不过来,我们就过去"。

二叉树本身就是树的一种简单特例,不是吗?所以这个启发很重要。


我们掌握规律更多的是完全二叉树和满二叉树,所以我引入虚拟null节点让普通树变为规律树,其实引入虚拟节点这个套路在分布式一致性哈希的时候就用过,我们为何不尝试一下呢?

20.png

  • 从树到数组的降维

引入虚拟节点之后,我们就拥有了一棵完全二叉树,当然有时候补齐之后我们拥有的是满二叉树,满二叉树的情况就是比如在上图的倒数第二层叶子节点7上随便甩出来一个节点,引入虚拟节点null之后就是满二叉树了,我们可以把满二叉树当做完全二叉树的特例即可。

仍旧以上图的完全二叉树为例进行迭代层次遍历并且将int转换为string且存储null节点,这样整个二叉树就成了这样:[3,9,20,7,15,15,7,7,null,19,null]


在遍历过程中我们不好判断null之后是否还会有其他非空节点,因此额外增加一个变量记录迭代遍历时队列中的非null节点个数,当队列中没有非空节点时遍历就可以结束了,这样我们存储的二叉树是没有空洞的,这个很重要,后面代码可以看到。


  • 数组的处理

我们知道完全二叉树/满二叉树的节点索引是存在内存联系的,由于我们填充了null所以我们就可以根据index关系来进行分层再反转了,从而避免在树的遍历过程中进行层次的记录,两件事情没有关联,处理起来更加清爽,看下:

21.png

经过上面几个过程,我们初步达到了目标,所以这个方案是行得通的,那么愉快地开始编码吧!


3.2 我的糙代码


前面说了,这个版本的代码肯定不是最优的,不过还是看下究竟有多粗糙和糟糕吧:

22.png

具体的代码实现(未优化版本):


/**
 * 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:
    //处理每个层的数据:将null清除 将string转换为int 根据层数进行翻转
    bool revseit(vector<string> &vec, int curlevl, vector<int> &res){
        //由于层数是从1开始编号 因此满足奇数层从左到右不翻转 偶数层翻转为从右向左
        vector<string>::iterator iter = vec.begin();
        for(;iter!=vec.end();iter++){
            if(*iter=="null")
                continue;
            res.push_back(std::stoi(*iter));
        }
        if(curlevl%2==0)
            std::reverse(res.begin(),res.end());
        return true;
    }
    //开始处理由二叉树构造满二叉树生成的vector
    bool dealit(vector<string> &vec, vector<vector<int> > &res){ 
        //从顶向下按照满二叉树切割每层 每层结点数遵循 等比数列 1 2 4 8 .... 2^(k-1) k=1,2,3...
        //满二叉树的总结点数量S=2^k-1 由此可以计算层数 也就是子vector的数量
        int nodecnt = vec.size();
        int levcnt = log(nodecnt+1)/log(2);
        //这一步是判断完全二叉树的情况
        bool notfull = false;
        if(pow(2,levcnt)-1!=nodecnt){
            notfull=true;
        }
        //我们从第1层开始向后分层切割
        int curlevl = 1;
        vector<string> tmpvec;
        vector<int> tmpsubres;
        while(curlevl<=levcnt+1){
            //临时结构清空
            tmpvec.clear();
            tmpsubres.clear();
            //计算本层之前的全部结点数量 作为本次切片的起点
            int lastsum = pow(2,curlevl-1)-1;
            //计算本层的节点数 作为切片时的偏移量
            int gap = pow(2,curlevl-1);
            if(curlevl==levcnt+1){
                if(notfull)
                    gap = nodecnt-lastsum;
                else
                    break;
            }     
            tmpvec.assign(vec.begin()+lastsum,vec.begin()+lastsum+gap);
            revseit(tmpvec,curlevl,tmpsubres);
            if(tmpsubres.size()>0)
                res.push_back(tmpsubres);
            curlevl++;
        }
        return true;
    }
    //非递归层次遍历 注意空节点的处理
    void travese(TreeNode *root, vector<string> &vec){
        //相当于一个标记位 记录队列中非空节点数量
        int oknodecnt = 0; 
        TreeNode *node = root;
        queue<TreeNode*> q;
        q.push(node);
        oknodecnt++;
        while(!q.empty()&&oknodecnt>0)
        {
            TreeNode *top = q.front();
            if(top){
                //向队列装载左结点
                if(top->left){
                    q.push(top->left);
                    oknodecnt++;
                }else
                    q.push(NULL);
                //向队列装载右节点
                if(top->right){
                    q.push(top->right);
                    oknodecnt++;
                }else
                    q.push(NULL);
                //队头节点任务完成 可以弹出并加入到vector中
                q.pop();
                oknodecnt--;
                vec.push_back(std::to_string(top->val));
            }else{
                //当队头节点时NULL时 为了保证满二叉树的特性 向队列中增加两个NULL作为其虚拟孩子节点
                q.pop();
                q.push(NULL);
                q.push(NULL);
                vec.push_back("null");
            }
        }
    }
    vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
        vector<vector<int> > res;
        vector<string> vectree;
        if(!root)
            return res;
        //层次遍历
        travese(root,vectree);
        //处理遍历后生成的vector
        dealit(vectree,res);
        return res;
    }
};


其实笔者之所以这么绕地去实现一个问题,也是为了由一道题练更多的知识点,代码中的注释写的还算详细,感兴趣的可以用web版的页面查看,手机上阅读体验差点意思。

            </div>
目录
相关文章
|
7月前
|
C++
二叉树进阶面试题(精华总结)【C++版本】
二叉树进阶面试题(精华总结)【C++版本】
|
7月前
力扣面试经典题之二叉树
力扣面试经典题之二叉树
50 0
|
3月前
|
算法
二叉树面试题
本文介绍了二叉树相关的几个经典算法问题。首先讲解了如何判断两棵树是否完全相同(LeetCode 100),并给出了代码示例。接着讨论了如何判断一棵树是否是另一棵树的子树(LeetCode 572),详细分析了子树的定义及判断方法。然后介绍了翻转二叉树(LeetCode 226)的实现方法,即在遍历时交换每个节点的左右子树。随后探讨了如何判断一棵树是否是对称的(LeetCode 101),通过对左右子树的递归比较来实现。最后分析了平衡二叉树的概念(LeetCode 110)及判断方法,并给出了优化后的代码示例。此外,还简要介绍了二叉树遍历及二叉树最近公共祖先(LeetCode 236)的问题
31 6
二叉树面试题
|
1月前
|
算法 Java
JAVA 二叉树面试题
JAVA 二叉树面试题
19 0
|
7月前
【一刷《剑指Offer》】面试题 23:从上往下打印二叉树
【一刷《剑指Offer》】面试题 23:从上往下打印二叉树
|
7月前
【一刷《剑指Offer》】面试题 19:二叉树的镜像
【一刷《剑指Offer》】面试题 19:二叉树的镜像
|
7月前
【一刷《剑指Offer》】面试题 6:重建二叉树
【一刷《剑指Offer》】面试题 6:重建二叉树
|
7月前
|
算法 C++
数据结构和算法面试题:实现一个函数,将一棵二叉树转换为它的镜像。(递归或者非递归实现)
数据结构和算法面试题:实现一个函数,将一棵二叉树转换为它的镜像。(递归或者非递归实现)
44 0
|
7月前
|
算法 Java C++
数据结构与算法面试题:实现二叉树的遍历(前序、中序、后序、层序)。
数据结构与算法面试题:实现二叉树的遍历(前序、中序、后序、层序)。
48 0
|
7月前
数据结构之二叉树及面试题讲解(三)
数据结构之二叉树及面试题讲解(三)
48 0

热门文章

最新文章