110.平衡二叉树 (优先掌握递归)
题目链接/文章讲解/视频讲解: 代码随想录
1.分析及思路
判断一个树是不是平衡二叉树,就要判断,其任意结点的左右孩子高度差的绝对值不能超过1。
用后续遍历判断其是不是平衡二叉树,因为左右孩子判断完毕才能判断父节点是不是符合平衡二叉树。
递归三部曲:
1.确定返回值以及传入的参数
因为是根据高度来判断是不是平衡二叉树,所以返回值为int高度值,传入参数就是结点
2.终止条件
传入NULL时结束,返回值为0,因为空结点没有高度。
3.确定单层递归逻辑
对任意结点判断的是,左子树是不是平衡二叉树,右子树是不是平衡二叉树,最后再判断该结点的左右子树高度差是否符合平衡二叉树。若任意一个不是平衡二叉树,就返回-1作为高度。
2.代码及注释
int GetHigh(struct TreeNode* root) {
if(root == NULL)//终止条件
return 0;
int leftHigh = GetHigh(root->left);//获取左子树高度
if(leftHigh == -1)//若左子树不是平衡二叉树,则返回-1
return -1;
int rightHigh = GetHigh(root->right);//获取右子树高度
if(rightHigh == -1)//右子树不是平衡二叉树,则返回-1
return -1;
//判断结点是否符合二叉树,不符合返回-1
if( !((leftHigh-rightHigh>=-1)&&(leftHigh-rightHigh<=1)) )
return -1;
//若符合二叉树,则返回该结点的高度
return (leftHigh>rightHigh)?(1+leftHigh):(1+rightHigh);
}
bool isBalanced(struct TreeNode* root) {
if(root == NULL)//若结点为空结点,则为平衡二叉树
return true;
//判断传入结点是否是平衡二叉树
return (GetHigh(root)==-1)?false:true;
}
257. 二叉树的所有路径 (优先掌握递归)
题目链接/文章讲解/视频讲解: 代码随想录
1.分析及思路
路径是从根节点开始,到叶子结点结束。所以我们需要用前序遍历进行二叉树的访问。把访问的结点加入栈中用来保存,当遇到叶子结点时,就把所有的元素存储下来。然后进行下一次的路径寻找。
递归三部曲:
1.传入的参数及返回的类型
返回值为空,因为不需要返回什么。
传入的参数:1.结点
2.最终需要一个二维数组来存储结果,所以传入一个二维数组。需要对二维数组进行赋值,所以把int* returnSize传入进来
3.每次遍历都需要存储路径,所以需要一个栈来实现。
2.递归终止条件
遇到叶子结点时,我们终止,再次之前我们需要把路径存储下来。
3.确定单层递归逻辑
对每一个结点,都是寻找路径,然后回溯。
2.代码及注释
#define MaxSize 20 //定义栈中元素的最大个数
typedef int ElemType;
typedef struct{
ElemType data[MaxSize]; //存放栈中的元素
int top; //栈顶指针
}SqStack;
/***************************************************************
** 功 能:初始化顺序栈
** 参 数:Stack 顺序栈的地址
** 返回值:无
****************************************************************/
void InitStack(SqStack *Stack){
Stack->top = -1;//初始化栈顶指针
}
/***************************************************************
** 功 能:顺序栈的入栈
** 参 数:Stack顺序栈的地址,data传入的数据
** 返回值:true入栈成功 false入栈失败
****************************************************************/
bool Push(SqStack *Stack,ElemType data){
if(Stack->top == MaxSize-1) //判断栈是否满,若满则无法入栈
return false;
Stack->data[++Stack->top] = data;//栈顶指针先上移1,然后再入栈
return true;
}
/***************************************************************
** 功 能:顺序栈的出栈
** 参 数:Stack顺序栈的地址,data保存数据的地址
** 返回值:true出栈成功 false出栈失败
****************************************************************/
bool Pop(SqStack *Stack,ElemType *data){
if(Stack->top == -1) //判断栈是否为空,为空无法出栈
return false;
if(data != NULL)
*data = Stack->data[Stack->top];//不为空先赋值,然后指针再向下移动
Stack->top--;
return true;
}
/***************************************************************
** 功 能:顺序栈的获取栈顶元素
** 参 数:Stack顺序栈,data保存数据的地址
** 返回值:true获取成功 false获取失败
****************************************************************/
bool GetTop(SqStack Stack,ElemType *data){
if(Stack.top == -1) //判断栈是否为空,为空没有栈顶元素
return false;
*data = Stack.data[Stack.top];//读取栈顶元素
return true;
}
void construct_paths(struct TreeNode* root, char** paths, int* returnSize, SqStack* sta) {
Push(sta,root->val); // 将当前节点的值压入栈中
if (root->left == NULL && root->right == NULL) { // 如果当前节点是叶子节点
char* path = (char*)malloc(100 * sizeof(char)); // 分配存储路径的内存空间
int path_len = 0; // 路径长度初始化为0
for (int i = 0; i < sta->top; i++) { // 遍历栈中的元素
path_len += sprintf(path + path_len, "%d->", sta->data[i]); // 将节点值按格式写入路径中
}
path_len += sprintf(path + path_len, "%d",sta->data[sta->top]); // 将最后一个节点值写入路径中
paths[(*returnSize)++] = path; // 将路径存入结果数组中并更新返回大小
return; // 返回
}
if(root->left){ // 如果左子树不为空
construct_paths(root->left, paths, returnSize, sta); // 递归处理左子树
Pop(sta,NULL); //回溯
}
if(root->right){ // 如果右子树不为空
construct_paths(root->right, paths, returnSize, sta); // 递归处理右子树
Pop(sta,NULL); //回溯
}
}
char** binaryTreePaths(struct TreeNode* root, int* returnSize) {
char** paths = (char**)malloc(sizeof(char*) * 1001); // 分配存储路径的内存空间
*returnSize = 0; // 返回大小初始化为0
if (root == NULL) { // 如果根节点为空
return paths; // 直接返回空路径数组
}
SqStack sta; // 定义一个栈
InitStack(&sta); // 初始化栈
construct_paths(root, paths, returnSize, &sta); // 构建二叉树路径
return paths; // 返回路径数组
}
3.部分代码讲解
path_len += sprintf(path + path_len, "%d->", sta->data[i]);
path + path_len代表起始地址。sprintf的返回值:如果成功,则返回写入的字符总数,不包括字符串追加在字符串末尾的空字符。如果失败,则返回一个负数。
为什么中的代码在最前面那,因为最后一个结点也要加入到栈中。才能保证路径的完整。
模拟运行:
404.左叶子之和 (优先掌握递归)
题目链接/文章讲解/视频讲解: 代码随想录
1.分析及思路
把左右子树的左叶子之和返回给根节点,所以用后序遍历。
递归三部曲:
1.传入的参数和返回值
传入的参数:结点,返回值:左右子树的左叶子之和。
2.终止条件
毫无疑问NULL返回。当传入叶子结点时,它没有什么作用左右子树的左叶子之和为0,所以在终止条件中加上叶子结点。
3.确定单层递归的逻辑
对于所有的结点都是,先求出左子树的左叶子之和,再求出右子树左叶子之和。最终加一起。怎么判断左叶子那,就是
if(root->left!=NULL && root->left->left==NULL && root->left->right==NULL)
leftSum = root->left->val;
2.代码及注释
// 定义一个函数,计算左叶子节点的和
int sumOfLeftLeaves(struct TreeNode* root){
// 如果根节点为空,返回0
if(root == NULL)
return 0;
// 如果根节点的左右子节点都为空,返回0
if(root->left == NULL && root->right == NULL)
return 0;
// 递归计算左子树的左叶子节点的和
int leftSum = sumOfLeftLeaves(root->left);
// 如果左子树不为空且左子树的左右子节点都为空,将左子树的值赋给leftSum
if(root->left!=NULL && root->left->left==NULL && root->left->right==NULL)
leftSum = root->left->val;
// 递归计算右子树的左叶子节点的和
int rightSum = sumOfLeftLeaves(root->right);
// 返回左子树和右子树左叶子节点和的总和
return rightSum+leftSum;
}