代码随想录算法训练营第十五天
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;
}
总结
- 今天在一个地方卡了一个多小时,程序的逻辑没有问题,只是在动态分配返回的二维指针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(2000sizeof(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);
}