代码随想录算法训练营第十五天 | 102.二叉树的层序遍历,226.翻转二叉树,101.对称二叉树 [二叉树篇]

LeetCode 102.二叉树的层序遍历

题目链接:102.二叉树的层序遍历
文章讲解:代码随想录#102.二叉树的层序遍历
视频讲解:讲透二叉树的层序遍历 | 广度优先搜索

题目描述

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例1
在这里插入图片描述

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例2

输入:root = [1]
输出:[[1]]

示例3

输入:root = []
输出:[]

提示

  • 树中节点数目在范围 [0, 2000] 内
  • -1000 <= Node.val <= 1000

思路

这是一道典型的层序遍历的题,每层从左到右输出每个节点,层序遍历是二叉树的另一种遍历方式(一种是前中后序遍历)。
既然需要从左到右输出节点,我们可以借助队列从左到到右遍历各个节点,并且将该层的节点push到队列中。
然后从出口处pop出第一个节点,再判断节点有无孩子节点,如果有将孩子节点则push到队列的入口处,直到该层的节点全部遍历完,此时队列中存放的全是下一层的节点。
接着按照上面的方式继续处理下一层的节点,直到队列中节点为空,说明全部的节点已经处理完。

参考代码

struct quenen {
    int *node;
    struct quenen *next;
    struct quenen *pre;
};

typedef struct quenen Quenen;

Quenen *createQuenen(void)
{
    Quenen *head = (Quenen*)malloc(sizeof(Quenen));
    head->next = head;
    head->pre = head;
    return head;
}

bool isQuenenEmpty(Quenen *obj)
{
    if (obj->next == obj) {
        return true;
    }
    return false;
}

void quenenPush(Quenen *obj, int *node)
{
    Quenen *new = (Quenen*)malloc(sizeof(Quenen));
    new->node = node;
    new->next = obj;
    new->pre = obj->pre;
    obj->pre->next = new;
    obj->pre = new;
}

int* quenenPop(Quenen *obj)
{
    Quenen *tmp = obj->next;
    int *val = NULL;
    tmp->next->pre = obj;
    obj->next = tmp->next;
    val = tmp->node;
    free(tmp);
    return val;
}

int** levelOrder(struct TreeNode* root, int* returnSize, int** returnColumnSizes)
{
    if (root == NULL) {
        *returnSize = 0;
        return NULL;
    }
    int **ret = (int**)malloc(2000*sizeof(int*));
    *returnColumnSizes = (int*)malloc(2000*sizeof(int));
    Quenen *obj = createQuenen();
    int curCnt = 0; // 当前层级的节点个数
    int nxtCnt = 0; // 下一层级的节点个数
    int size = 0; // 树的层级,也就是ret的一维下标
    int idx = 0; // ret的二维下标
    
    quenenPush(obj, (int*)root);
    curCnt++;
    while(!isQuenenEmpty(obj)) {
        ret[size] = (int*)malloc(curCnt*sizeof(int));
        (*returnColumnSizes)[size] = curCnt;
        idx = 0;
        nxtCnt = 0;
        while (curCnt--) {
            struct TreeNode* tmp = (struct TreeNode*)quenenPop(obj); // 从队列出口处pop节点
            if (tmp == NULL) {
                continue;
            }
            ret[size][idx++] = tmp->val;
            if (tmp->left != NULL) {
                quenenPush(obj, (int*)tmp->left); // 向队列中添加左孩子
                nxtCnt++;
            }
            if (tmp->right != NULL) {
                quenenPush(obj, (int*)tmp->right); // 向队列中添加右孩子
                nxtCnt++;
            }
        }
        curCnt = nxtCnt;
        size++;
    }

    *returnSize = size;
    return ret;
}

总结

  1. 今天在一个地方卡了一个多小时,程序的逻辑没有问题,只是在动态分配返回的二维指针ret时出错了。
    编译时一直报的这个错误:
    runtime error: store to address 0x625000002048 with insufficient space for an object of type ‘int *’

int **ret = (int**)malloc(2000sizeof(int));
*returnColumnSizes = (int*)malloc(2000
sizeof(int));

我申请了2000个,发现当树节点超过1000个后,就会踩踏returnColumnSizes 的起始内存,在网上百度了这个runtime error,说是代码逻辑有问题,陷入死循环了,导致内存泄露云云…
后来猜想为什么树节点超过1000个,就会影响到returnColumnSizes ,而小于1000个时,就不影响。
ret和returnColumnSizes 都动态分配的,应该恰好地址连续,由于ret的大小分配有问题,所以踩踏了returnColumnSizes 。

对这行代码瞅了半天,没有发现问题出在哪儿了

int **ret = (int**)malloc(2000*sizeof(int));

忽然想到,我明明申请了2000个大小,为什么只能满足1000个,那就只能说明sizeof(int)不对,比相像中少了一半,接着意识到这是二维指针,每个元素大小应该是sizeof(int*)。
然后修改成下面的代码后,用例就通过了。

int **ret = (int**)malloc(2000*sizeof(int*));

还是说明了对二维指针的动态内存分配不熟,在64位机器上sizeof(int*)为8,sizeof(int)为4。 一个小细节竟然卡住了我一个多小时,浪费时间真可惜啊!!!!!


LeetCode 226.翻转二叉树

题目链接:226.翻转二叉树
文章讲解:代码随想录#226.翻转二叉树
视频讲解:LeetCode:226.翻转二叉树

题目描述

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

示例1
在这里插入图片描述

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

提示

  • 树中节点数目范围在 [0, 100] 内
  • -100 <= Node.val <= 100

思路

递归遍历比较简单,尤其是对于这道题,使用前序遍历。

递归三部曲:

  • 确定递归函数的参数和返回值
    函数参数就是二叉树的根节点root,返回值为翻转后的root节点

struct TreeNode* invertTree(struct TreeNode* root)

  • 确定终止条件
    当子节点为NULL时返回

if (root == NULL) {
  return NULL;
}

  • 确定单层递归的逻辑
    前序遍历的顺序是中-左-右,所以先对处理当前节点,即对当前节点的左右孩子进行交换,然后再处理当前节点的左孩子,最后处理当前节点的的右孩子。

也可以尝试着后序遍历,以及迭代等算法。
具体代码实现和思路后面补充。

参考代码

// 递归 前序遍历 中-左-右
struct TreeNode* invertTree(struct TreeNode* root) {
    if (root == NULL) {
        return NULL;
    }
    
    // 先遍历父节点,对两个节点进行交换,交换的是地址,不是值
    struct TreeNode* tmp = root->left;
    root->left = root->right;
    root->right = tmp;

    // 遍历左孩子
    invertTree(root->left);

    // 遍历右孩子
    invertTree(root->right);

    return root;
}

LeetCode 101. 对称二叉树

题目链接:101. 对称二叉树
文章讲解:代码随想录#101. 对称二叉树
视频讲解:LeetCode:101. 对称二叉树

题目描述

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例1
在这里插入图片描述

输入:root = [1,2,2,3,4,4,3]
输出:true

提示

  • 树中节点数目在范围 [1, 1000] 内
  • -100 <= Node.val <= 100

思路

如何判断一棵树是否为对称二叉树呢?
其实就是判断根节点的左子树与右子树是不是相互翻转,也就是比较两个子树的里侧和外侧的元素是否相等。
盗用代码随想录中的图可以更清晰地解释这个问题
在这里插入图片描述
迭代等方法以后有时间二刷时再补上。

参考代码

// 使用后序遍历,判断左右子树是否对称
bool symmetric(struct TreeNode *lnode, struct TreeNode *rnode)
{
    if (lnode == NULL && rnode != NULL) return false;
    if (lnode != NULL && rnode == NULL) return false;
    if (lnode == NULL && rnode == NULL) return true;
    if (lnode->val != rnode->val) return false;

    bool outter = symmetric(lnode->left, rnode->right);
    bool inner = symmetric(lnode->right, rnode->left);
    return outter && inner;
}

bool isSymmetric(struct TreeNode* root) {
    if (root == NULL)
        return false;
    return symmetric(root->left, root->right);
}
  • 8
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值