1.8 LeetCode总结(树)_二叉树类

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧

二叉树的遍历 – 前中后序遍历–递归法 – 等级:白银

  1. 确定递归函数的参数和返回值
  2. 确定终止条件
  3. 确定单层递归的逻辑
// 前序 根左右
void PreOrder(struct TreeNode *root, int *ret, int *retIndex)
{
    if (root == NULL) {
        return;
    }
    ret[(*retIndex)++] = root->val; // 根计算
    PreOrder(root->left, ret, retIndex); // 左
    PreOrder(root->right, ret, retIndex); // 右
}
// 中序 左根右
void inorder(struct TreeNode *root, int *res, int *resSize) 
{
    if (root == NULL) {
        return;
    }
    inorder(root->left, res, resSize); // 左
    res[(*resSize)++] = root->val; // 根
    inorder(root->right, res, resSize); // 右
}
// 后序  左右根
void postorder(struct TreeNode *root, int *res, int *resSize)
{
    if (root == NULL) {
        return;
    }
    postorder(root->left, res, resSize);
    postorder(root->right, res, resSize);
    res[(*resSize)++] = root->val;
}

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int *preorderTraversal(struct TreeNode *root, int *returnSize)
{
    int retIndex = 0;
    int *ret = (int *)malloc(sizeof(int) * 100);
    memset(ret, 0, sizeof(int) * 100);
    PreOrder(root, ret, &retIndex);
    *returnSize = retIndex;
    return ret;
}

二叉树的层序遍历 – BFS – 等级:青铜

在这里插入图片描述

int **levelOrder(struct TreeNode *root, int *returnSize, int **returnColumnSizes)
{
	*returnSize = 0;
	if (root == NULL) {
		return NULL;
	}
	struct TreeNode *queue[2000];
	int **res = (int **)malloc(sizeof(int *) * 2000);
	*returnColumnSizes = (int *)malloc(sizeof(int *) * 2000);
	int rear = 0, front = 0, count = 0;
	queue[rear++] = root; // 入队列
	while (front != rear) { // 终止条件,队列不为空
		int len = rear - front; // 当前层的节点数量
		count = 0;
		res[*returnSize] = (int *)malloc(sizeof(int)*(len));
		for (int i = 0; i < len; i++) {
			struct TreeNode *temp = queue[front++]; // 出队列
			res[*returnSize][count++] = temp->val;
			if (temp->left) {
				queue[rear++] = temp->left; // 左节点入队列
			}
			if (temp->right) {
				queue[rear++] = temp->right; // 右节点入队列
			}
		}
		(*returnColumnSizes)[*returnSize] = len; // returnColunmnSizes每一层有多少元素
		(*returnSize)++; // returnSize有多少层
	}
	return res;
}

翻转二叉树 – 等级:青铜

在这里插入图片描述

90% of our engineers use the softwar you wrote(Homebrew), but you can’t invert a binary tree on a whiteboard so fuck off.

struct TreeNode *invertTree(struct TreeNode *root) 
{
	if (root == NULL) {  // 1.递归终止条件
		return NULL;
	}
	// 后序遍历,左右根
	struct TreeNode *left  = invertTree(root->left);
	struct TreeNode *right = invertTree(root->right);
	// 交换 root 的左右孩子
	root->left  =  right; // 先递下去,归的时候遇到节点时,交换其左右节点
	root->right =  left;  // 本质时交换每个除根节点外节点的左右子节点
  	return root;
}

101. 对称二叉树 – 等级:青铜

在这里插入图片描述

bool isSymmetric(struct TreeNode *root) 
{
	if (root == NULL) {
		return true;
	}

	return compareTree(root->left, root->right);
}
// 后序 -- 左右根 l_root 左子树, r_root 右子树
bool compareTree(struct TreeNode *left, struct TreeNode *right)
{
	if (left == NULL && right == NULL) {
		return true;
	} else if (left == NULL && right != NULL) { // 异常情况:左空右不空;左不空右空;
		return false;
	} else if (left != NULL && right == NULL) {
		return false;
	}
	if (left->val == right->val) {
		bool outside = compareTree(left->left, right->right); // 外侧
		bool inside  = compareTree(left->right, right->left); // 内侧
		return (outside && inside); // 后序遍历,左右根
	}
	return false;
}

二叉树最大深度

 int maxDepth(struct TreeNode *root) 
{
	int depth1 = 0,depth2 = 0;
	if (root == NULL) {
		return 0;
	}
	// 后序遍历 -- 左右根
	depth1 = maxDepth(root->left);
	depth2 = maxDepth(root->right);
	return fmax(depth1, depth2) + 1;
}

思路:

  1. 先递归 depth1 = maxDepth(root->right)找到右边最下的叶子节点,返回0;
  2. 执行 depth2 = maxDepth(root->left) 返回0;
  3. 返回 fmax(0,1) 为1,继续归回来的叶子节点的上层遍历。

二叉树最小深度

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

int minDepth(struct TreeNode *root)
{
	int min_depth  = 0;
	int leftHight  = 0;
	int rightHight = 0;
	if (root == NULL) {
		return 0;
	}
	// 后序遍历 左右根
	leftHight  = minDepth(root->left);
	rightHight = minDepth(root->right);
	// 处理左右节点是空节点的情况,左为空右不为空,说明右边高1
	if (root->left == NULL && root->right != NULL) {
		return rightHight + 1;
	}
	// 右为空左不为空,说明左边高1
	if (root->left != NULL && root->right == NULL) {
		return leftHight + 1;
	}
	// 左右子树均不为空
	min_depth = fmin(leftHight, rightHight) + 1;
	return min_depth;
}

-------------------------------------------分割线---------------------------------------------------

110. 平衡二叉树

给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

思路:在下面求解平衡二叉树时,只不过是二叉树深度做了一点点变化,在每次都先判断深度差是否大于1.

int maxDepth(struct TreeNode *root) {

	int depth1 = 0, depth2 = 0;
	if (root == NULL) {
		return 0;
	}
	//printf("root is %d, depth1 is %d ,depth2 is %d\n", root->val, depth1, depth2);
	depth1 = maxDepth(root->left);
	depth2 = maxDepth(root->right);
	if (abs(depth1 - depth2) > 1) {
		return -999999;  // 在层次比较深的时候,注意返回值过小,后面+1可能会影响最终结果
	}
	return fmax(depth1, depth2) + 1;
}


bool isBalanced(struct TreeNode *root) {
	if (root == NULL) {
		return true;
	}
	
	return  maxDepth(root) >= 0; // 判断非负即可
}

1)先递下去,往左开始,递到根节点
在这里插入图片描述
2)归回来一个节点,执行depth2 = maxDepth(root->right);
在这里插入图片描述
3)左右都执行完成后,回到节点
在这里插入图片描述
4)往右节点递下去
在这里插入图片描述
5)继续按之前的方式递,递归到满足终止条件为止
在这里插入图片描述
6)归到图中节点时,其右节点的深度减掉左节点NULL的深度 大于1,为非平衡节点,返回 -2
abs(depth1 - depth2) > 1
在这里插入图片描述
7)再归到上一节点时,由于depth2 == -2 ,上一节点直接返回 -2

在这里插入图片描述
8)再归到上一节点时,由于depth1 == -2 ,上一节点直接返回 -2,程序完成
在这里插入图片描述

563. 二叉树的坡度

给定一个二叉树,计算整个树的坡度。
一个树的节点的坡度定义即为,该节点左子树的结点之和和右子树结点之和的差的绝对值。空结点的的坡度是0。
整个树的坡度就是其所有节点的坡度之和。

int tilt = 0;
int traverse(struct TreeNode *root)
{
	if (root == NULL) {
		return 0;
	}
	int left = traverse(root->left);
	int right = traverse(root->right);
	tilt += abs(left - right);
	return (left + right + root->val);
}
int findTilt(struct TreeNode *root) {
	tilt = 0;     // Attention : 用例之间有重复利用全局变量,一定要清除全局变量的值
	traverse(root);
	return tilt;  // 返回坡度
}

建议不使用全局变量:

int traverse(struct TreeNode *root, int *res)
{
	if (root == NULL) {
		return 0;
	}
	int left  = traverse(root->left, res);
	int right = traverse(root->right,res);
	*res += abs(left - right);
	return left + right + root->val;
}
int findTilt(struct TreeNode *root) {
	int res = 0;
    traverse(root, &res);
	return res;  //返回坡度
}

1)先递左子树
在这里插入图片描述
2)递到叶子节点3,遇到3节点的左右子树均为NULL,递终止,计算该节点和,left + right + root->val 为 3.
在这里插入图片描述
3) 节点2 的左子树3完成,回到节点2,此时程序继续执行代码:int right = traverse(root->right);
在这里插入图片描述
4)对于节点1,其叶子左右节点均为NULL,递终止,返回
在这里插入图片描述
5)此时,节点2的左右节点均已完成,执行代码 tilt += abs(left - right),计算坡度
在这里插入图片描述
6)此时,左子树归完成,执行代码 right = traverse(root->right),继续对于3节点的右子树遍历一遍。
在这里插入图片描述
7)递到左节点6,其子节点都为NULL,所以递终止,执行代码:int right = traverse(root->right);
在这里插入图片描述
8)右节点5,其子节点都为NULL,递终止,执行归 return left + right + root->val;
在这里插入图片描述
9)回到节点3,其左右节点都访问完成,归回来了,执行代码 return left + right + root->val,
在这里插入图片描述
在这里插入图片描述

617. 合并二叉树

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

struct TreeNode *mergeTrees(struct TreeNode *t1, struct TreeNode *t2) {
	if (t1 == NULL) {    // 递终止条件
		return t2;
	}
	if (t2 == NULL) {
		return t1;
	}
	t1->val += t2->val;
	t1->left  = mergeTrees(t1->left, t2->left);
	t1->right = mergeTrees(t1->right, t2->right);

	return t1;
}

在遍历时,如果两棵树的当前节点均不为空,我们就将它们的值进行相加,并对它们的左孩子和右孩子进行递归合并;如果其中有一棵树为空,那么我们返回另一颗树作为结果;如果两棵树均为空,此时返回任意一棵树均可(因为都是空)

1)考虑合并的是如下的二叉树,左和右,计算 t1->val = t1->val + t2->val;
在这里插入图片描述
2)同时递左子树
在这里插入图片描述
3)继续递
在这里插入图片描述
4)到叶子节点了,递终止,返回 NULL
在这里插入图片描述
5)再往节点5的右节点递下去,此时,t2 == NULL 返回 t1
在这里插入图片描述
6)这时根节点的左边节点都已经遍历完成,开始往节点遍历:
在这里插入图片描述
7)遇到节点9的左节点为NULL,return t2
在这里插入图片描述
8)此时本来节点9无左节点,由于 t1->left = t2(赋值),所以有了新节点
在这里插入图片描述
9)接着访问右节点
在这里插入图片描述
10)同理赋值给了 t1->right
在这里插入图片描述
11)递归结束,回到根节点
在这里插入图片描述

965.单值二叉树

如果二叉树每个节点都具有相同的值,那么该二叉树就是单值二叉树。
只有给定的树是单值二叉树时,才返回 true;否则返回 false。
思路:就是判断二叉树的所有值都相等
但是二叉树里可以有NULL,NULL节点不算和其他节点不相等,所以需要处理好。
方法是如下,挺巧妙的

int value = 0;
int UnivalTree(struct TreeNode *root)
{
	if (root == NULL) { // 递终止条件,也避免了和NULL节点比较
		return true;
	}
	if (root->val != value) { // 都与根节点去比较,不用再多构造变量左右来比较
		return false;
	}
	
	return UnivalTree(root->left) && UnivalTree(root->right);
}

bool isUnivalTree(struct TreeNode *root) {
	if (root == NULL) { 
		return true;
	}
	value = root->val;
	return UnivalTree(root);
}

572. 另一个树的子树

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
示例 1:
给定的树 s:

 3
/ \

4 5
/
1 2
给定的树 t:

4
/
1 2
返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。

bool compare(struct TreeNode* root1,struct TreeNode* root2)
{
    if(root1 == NULL && root2 == NULL)
        return true;
    if(root1 == NULL || root2 == NULL)
        return false;
    if(root1->val != root2->val)
        return false;
    return compare(root1->left,root2->left) && compare(root1->right,root2->right);  // 递归比较左右  
}

bool isSubtree(struct TreeNode* s, struct TreeNode* t)
{
    if(s == NULL && t != NULL)   // s已经到底
         return false;
    return compare(s,t) || isSubtree(s->left,t) || isSubtree(s->right,t);  // 判 根  左  右
}

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
在这里插入图片描述

struct TreeNode {
     int val;
     struct TreeNode *left;
     struct TreeNode *right;
 };
 
bool check(TreeNode *o, TreeNode *t) {

	if (!o && !t) { // o 和 t 都到根节点了,返回 true
		return true;
	}
	if ((o && !t) || (!o && t) || (o->val != t->val)) { // o 和 t有一个到了根节点另一个没有到,或者值不相等,返回false
		return false;
	}
	// 在 o 节点和 t 节点相等时,递归下去
	return check(o->left, t->left) && check(o->right, t->right);
}

bool dfs(TreeNode *o, TreeNode *t) {

	if (!o) {
		return false;
	}
	// 如果没有check(o,t)不相等,就递归o->left 和 o->right
	return check(o, t) || dfs(o->left, t) || dfs(o->right, t);
}

bool isSubtree(TreeNode *s, TreeNode *t) {

	return dfs(s, t);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

114. 二叉树展开为链表

给你二叉树的根结点 root ,请你将它展开为一个单链表:

展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
在这里插入图片描述

void PreOrder(struct TreeNode *root, int *ret, int *retIndex)
{
	if (root == NULL) {
		return;
	}
	ret[(*retIndex)++] = root->val;
	PreOrder(root->left, ret, retIndex);
	PreOrder(root->right, ret, retIndex);
}

int preorderTraversal(struct TreeNode *root, int *returnSize)
{
	int retIndex = 0;
	int i = 1;
	int *ret = (int *)malloc(sizeof(int) * 2000); // 2000是因为题目树中节点数为2000
	memset(ret, 0, sizeof(int)*2000);
	PreOrder(root, ret, &retIndex);
	*returnSize = retIndex;
	struct TreeNode *root_tmp = root;
	root_tmp->val = ret[0];
	root_tmp->right = NULL;
	root_tmp->left  = NULL;

	if (*returnSize == 1) {
		root = root_tmp;
		return 0;
	}
    // 先做先序遍历,结果保留在ret里面,然后将二叉树重组回去
	while (i < *returnSize) {
		struct TreeNode *tmp = NULL;
		tmp = (struct TreeNode *)malloc(sizeof(struct TreeNode));
		tmp->val = ret[i];
		tmp->right = NULL;
		tmp->left  = NULL;
		root_tmp->right = tmp;
		root_tmp->left  = NULL;
		root_tmp = root_tmp->right;
		i++;
	}
	free(ret);

	return 0;
}

void flatten(struct TreeNode *root) 
{
	int returnSize = 0;
	if (root == NULL) {
		return;
	}

	preorderTraversal(root, &returnSize);
}

剑指 Offer 54. 二叉搜索树的第k大节点

给定一棵二叉搜索树,请找出其中第k大的节点。
本文解法基于此性质:二叉搜索树的中序遍历为 递增序列
在这里插入图片描述

void PreOrder(struct TreeNode *root, int *ret, int *retIndex)
{
	if (root == NULL) {
		return;
	}
	PreOrder(root->left, ret, retIndex);
	ret[(*retIndex)++] = root->val;      // 中序遍历
	PreOrder(root->right, ret, retIndex);
}

int kthLargest(struct TreeNode *root, int k) 
{
	int retIndex = 0;
	int i = 1;
	int *ret = (int *)malloc(sizeof(int) * 10000);
	memset(ret, 0, sizeof(int) * 10000);
	PreOrder(root, ret, &retIndex);

	return ret[retIndex - k];
}

112. 路径总和

在这里插入图片描述

思路是:

  1. 先判断根节点,因为下面要访问 root->left 和 root->right;
  2. 递归终止条件:判断叶子节点是否满足;
  3. 递归左节点下去;
  4. 递归右节点下去;
// 注意递归终止条件
bool hasPathSum(struct TreeNode *root, int sum)
{
	if (root == NULL) {
		return false; // 手法1:不能找根节点,这是不符合题意的,不能写 return sum == 0; 根节点返回false.

	}
	// 手法2:找到叶子节点,题目要求找叶子节点的路径和,此时判断 sum 是否== target.
	if (root->left == NULL && root->right == NULL) {
		return sum == root->val;
	}
	if (hasPathSum(root->left, sum - root->val) == true) {
		return true;
	}
	if (hasPathSum(root->right, sum - root->val) == true) {
		return true;
	}

	return false;
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值