剑指offer面试题32-III:从上到下打印二叉树

 题目描述:请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

层序遍历 + 倒序

思路分析

I. 按层打印: 题目要求的二叉树的 从上至下 打印(即按层打印),又称为二叉树的 广度优先搜索(BFS)。BFS 通常借助 队列 的先入先出特性来实现。
II. 每层打印到一行: 将本层全部节点打印到一行,并将下一层全部节点加入队列,以此类推,即可分为多行打印。
III. 每层打印到一行: 将本层全部节点打印到一行,并将下一层全部节点加入队列,以此类推,即可分为多行打印。最后需要判断是奇数层还是偶数层,如果是奇数层就进行反转
算法流程

1、特例处理: 当根节点为空,则返回空列表 [] ;
2、初始化: 打印结果列表 res = [] ,包含根节点的队列 queue = [root] ;
3、BFS 循环: 当队列 queue 为空时跳出;
      大小:计算队列的大小也就是当前根结点有多少孩子结点;
      新建一个临时列表 temp ,用于存储当前层打印结果;
      当前层打印循环: 循环次数为当前层节点数(即队列 queue 长度);
    (1)出队: 队首元素出队,记为 node;
    (2)打印: 将 node->val 添加至 temp 尾部;
    (3)添加子节点: 若 node 的左(右)子节点不为空,则将左(右)子节点加入队列 queue ;4、将当前层结果 temp 添加入 res 。

5、判断当前是奇数还是偶数层,若是奇数层则将数据反向;
6、返回值: 返回打印结果列表 res 即可。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        if(root == NULL) return {}; // 处理边界
        vector<vector<int>> res; // 设置数组容器 res, 用来保存输出结果
        queue<TreeNode*> que; // 设置队列 que, 用来存储二叉树中的元素
        que.push(root); // 队列添加二叉树的根节点
        // 遍历队列,直到队列为空,说明访问了二叉树中所有的节点
        while(!que.empty())
        {
            vector<int> temp; // 临时数组,用来保存每一层的节点,保存成功后添加到res中
            int len = que.size(); // 用来记录队列的长度,每层节点的个数
            // 将队列中的元素添加到临时tmp中
            for(int i = 0; i < len; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                temp.push_back(node->val);
                 // 判断当前节点是否有左/右子节点,添加到队列中
                if(node->left != NULL) que.push(node->left);
                if(node->right != NULL) que.push(node->right);
            }
            // 把存放了每一层元素的数组tmp添加到res中
            res.push_back(temp);
        }
        reverseVec(res);
        return res;
    }
    
    //将二维数组的奇数行 元素 进行反转
    void reverseVec(vector<vector<int>>& res)
    {
        for(int i = 0; i < res.size(); i++)
        {
            if(i % 2 == 1)
            {
                reverse(res[i].begin(),res[i].end());
            }
        }
    }
};
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        if(root == NULL) return {}; // 处理边界
        vector<vector<int>> res; // 设置数组容器 res, 用来保存输出结果
        queue<TreeNode*> que; // 设置队列 que, 用来存储二叉树中的元素
        que.push(root); // 队列添加二叉树的根节点
        // 遍历队列,直到队列为空,说明访问了二叉树中所有的节点
        while(!que.empty())
        {
            vector<int> temp; // 临时数组,用来保存每一层的节点,保存成功后添加到res中
            int len = que.size(); // 用来记录队列的长度,每层节点的个数
            // 将队列中的元素添加到临时tmp中
            for(int i = 0; i < len; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                temp.push_back(node->val);
                 // 判断当前节点是否有左/右子节点,添加到队列中
                if(node->left != NULL) que.push(node->left);
                if(node->right != NULL) que.push(node->right);
            }
            // 把存放了每一层元素的数组tmp添加到res中
            res.push_back(temp);
        }
         
        //将二维数组的奇数行 元素 进行反转
        for(int i = 1;i < res.size();i += 2)
        {
            reverse(res[i].begin(),res[i].end());
        }
        return res;
    }
};
//将二维数组的奇数行 元素 进行反转
        for(int i = 1;i < res.size();i += 2)
        {
            for(int l = 0, r = res[i].size()-1; l < r; l++, r--)
            {
                swap(res[i][l],res[i][r]);
            }
        }

对当前层节点的存储我们维护一个变量 isOrderRight 记录是从左至右还是从右至左的:

如果从左至右,我们每次将被遍历到的元素插入至临时数组 temp 的末尾。

如果从右至左,我们每次将被遍历到的元素插入临时数组 temp 的末尾,再进行反转。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        if(root == NULL) return {}; // 处理边界
        vector<vector<int>> res; // 设置数组容器 res, 用来保存输出结果
        queue<TreeNode*> que; // 设置队列 que, 用来存储二叉树中的元素
        que.push(root); // 队列添加二叉树的根节
        bool isOrderRight = false; //默认从从左到右打印
        // 遍历队列,直到队列为空,说明访问了二叉树中所有的节点
        while(!que.empty())
        {
            vector<int> temp; // 临时数组,用来保存每一层的节点,保存成功后添加到res中
            int len = que.size(); // 用来记录队列的长度,每层节点的个数
            // 将队列中的元素添加到临时tmp中
            for(int i = 0; i < len; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                temp.push_back(node->val);
                 // 判断当前节点是否有左/右子节点,添加到队列中
                if(node->left != NULL) que.push(node->left);
                if(node->right != NULL) que.push(node->right);
            }

            //isOrderRight = true 时,从右到左打印输出
            if(isOrderRight) 
            {
                reverse(temp.begin(),temp.end());
            }
   
            // 把存放了每一层元素的数组tmp添加到res中
            res.push_back(temp);

            //每层节点输出后,下一层改变打印顺序
            isOrderRight = !isOrderRight;
          
        }
        
        return res;
    }
};

层序遍历 + 双端队列

此题是「剑指 Offer 32 - II. 从上到下打印二叉树 II」的变种,最后输出的要求有所变化,要求我们按层数的奇偶来决定每一层的输出顺序。规定二叉树的根节点为第 0 层,如果当前层数是偶数,从左至右输出当前层的节点值,否则,从右至左输出当前层的节点值。

我们依然可以沿用「剑指 Offer 32 - II」的思想,修改广度优先搜索,对树进行逐层遍历,用队列维护当前层的所有元素,当队列不为空的时候,求得当前队列的长度 len,每次从队列中取出 len 个元素进行拓展,然后进行下一次迭代。

为了满足题目要求的返回值为「先从左往右,再从右往左」交替输出的锯齿形,我们可以利用「双端队列」的数据结构来维护当前层节点值输出的顺序。

双端队列是一个可以在队列任意一端插入元素的队列。在广度优先搜索遍历当前层节点拓展下一层节点的时候我们仍然从左往右按顺序拓展,但是对当前层节点的存储我们维护一个变量isOrderLeft 记录是从左至右还是从右至左的:

         如果从左至右,我们每次将被遍历到的元素插入至双端队列的末尾。

         如果从右至左,我们每次将被遍历到的元素插入至双端队列的头部。

当遍历结束的时候我们就得到了答案数组。

利用双端队列的两端皆可添加元素的特性,设打印列表(双端队列) temp ,并规定:
奇数层 则添加至 temp 头部 ,
偶数层 则添加至 temp 尾部 。
算法流程:
1、特例处理: 当树的根节点为空,则直接返回空列表 [] ;
2、初始化: 打印结果空列表 res ,包含根节点的双端队列 deque ;
3、BFS 循环: 当 deque 为空时跳出;
      (1)新建双端数组 temp ,用于临时存储当前层打印结果;
      (2)当前层打印循环: 循环次数为当前层节点数(即 deque 长度);
             出队: 队首元素出队,记为 node;
             打印: 若为偶数层,将 node->val 添加至 temp 尾部;否则,添加至 temp 头部;
             添加子节点: 若 node 的左(右)子节点不为空,则加入 deque ;
     (3)将当前层结果 tmp 转化为 单端数组 并添加入 res ;
4、返回值: 返回打印结果列表 res 即可;

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) 
    {
        
        if(root == NULL) return {}; // 处理边界
        vector<vector<int>> res; // 设置数组容器 res, 用来保存输出结果
        queue<TreeNode*> que; // 设置队列 que, 用来存储二叉树中的元素
        que.push(root); // 队列添加二叉树的根节点
        bool isOrederleft = true;
        // 遍历队列,直到队列为空,说明访问了二叉树中所有的节点
        while(!que.empty())
        {
            deque<int> temp; // 临时双端数组,支持头插和尾插,用来保存每一层的节点,保存成功后添加到res中
            int len = que.size(); // 用来记录队列的长度,每层节点的个数
            // 将队列中的元素添加到临时tmp中
            for(int i = 0; i < len; i++)
            {
                TreeNode* node = que.front();
                que.pop();
                
                if(isOrederleft) temp.push_back(node->val); //从左到右输出,则双端数组选择尾插
                else temp.push_front(node->val); //从右到左输出,则双端数组选择头插
                
                 // 判断当前节点是否有左/右子节点,添加到队列中
                if(node->left != NULL) que.push(node->left);
                if(node->right != NULL) que.push(node->right);
            }
            //每层节点输出后,下一层改变打印顺序
            isOrederleft = !isOrederleft;
            // 把存放了每一层元素的数组tmp添加到res中
            res.push_back(vector<int>{temp.begin(),temp.end()});
        }
         
        return res;
    }
};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值