1.剑指offer:26题.树的子结构
力扣
https://leetcode.cn/problems/shu-de-zi-jie-gou-lcof/
方法:递归
首先,通过遍历A树寻找B树的根结点rootB,如果存在则继续遍历,否则返回false。
再是,在A树中找到B树的rootB后,遍历AB两树的左右孩子是否相同,
然后,当出现不同时,在A树中rootB位置的基础上,遍历寻找下一个rootB,重复上步。
最后,如果在A树中找到B树,返回true,否则返回false。
算法思路
1. 构造辅助函数isSub,遍历AB树,及其左右孩子,同时对二者结点进行比较。
1> 当B树为空
表示已遍历完B树,且已遍历的AB树结点均一致,说明B是A的子树。返回true。
2> 当A树为空
表示已遍历完A树,但B树还有结点未遍历,说明B不是A的子树。返回false。
3> 当AB树对应结点不一致
说明当前的A树不包含B树,返回false。
注:代码中将2,3结合。且1,2,3均为递归的限制条件。
4> 当前的AB树对应结点一致(递归)
既然当前的结点一致,那么要继续遍历并判断AB树该结点的左右孩子是否一致。
2. 在isSubStructure函数中,
1> 据题意,首先明确当A,B任一树为空树时,返回false。
2> 调用函数isSub遍历AB树,判断A树中是否含B树。
3> 再调用函数本身isSubStructure,判断A的左子树和右子树中是否含B树。
4> 最后,当2,3中任一函数返回true,那么就代表B树是A树的子树,返回true。
算法图
复杂度分析
假设A树结点有M个,B树结点有N个。
时间复杂度:O(M*N)。
递归算法的时间复杂度 = 递归的次数 * 每次递归的时间复杂度。
本题的递归次数取决于A树结点数M,每次递归的时间复杂度取决于B树结点数N。
空间复杂度:O(M)。
递归算法的空间复杂度 = 递归的深度 * 每次递归的空间复杂度。
当AB树退化为链表时,递归深度最深。
当M<=N时,遍历A树和判断递归的深度为M。
当M>N时,最坏情况即B树不是A树的子树时,需要遍历A树的深度为M。
代码
// isSub函数的作用: 遍历AB树,并判断A树中是否含B树
bool isSub(TreeNode* A, TreeNode* B){
// B树为空,表示已经遍历完B树,且与A树结点均一致,所以B树是A树的子集
if(B == NULL)
return true;
// A树为空,表示已经遍历完A树,但B树还未遍历完,所以B树不是A树的子集
// 当A,B树的结点不同时,表示A不包含B
if(A == NULL || A->val != B->val)
return false;
// 递归循环遍历A的左孩子和B的左孩子、A的右孩子和B的右孩子
// 只有对应结点都相等时,才能判断B是A的子树
return isSub(A->left,B->left) && isSub(A->right,B->right);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
// 题目约定空树不是任一树的子树
if(A == NULL || B == NULL)
return false;
// 判断A树中是否含B树,并循环遍历A树的左右孩子是否含B树
return isSub(A,B) || isSubStructure(A->left,B) || isSubStructure(A->right,B);
}
2.剑指offer:27题.二叉树的镜像
力扣https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/
方法:递归法
遍历树的每一层,
第一层只有一个结点不需要镜像,
从第二层开始(两个结点)互换两结点顺序,每个结点又交换自己的左右孩子,依次类推。
算法思路
1. 已知需要不停交换两结点位置,故构造辅助函数exchange_node,完成结点的交换。
2. 将mirrorTree函数写成一个递归函数。
1> 限制条件
当本树为空树时,没有结点可镜像,故返回NULL。
2> 递归方程
交换当前结点的左右孩子。
3> 递推方程
递归当前结点的左右孩子,方便进一步交换其左右孩子的左右孩子。
算法图
复杂度分析
假设树结点有N个。
时间复杂度:O(N)。
递归算法的时间复杂度 = 递归的次数 * 每次递归的时间复杂度。
本题的递归次数取决于树结点数,每次递归的时间复杂为O(1)。
当树退化为链表时,递归次数最多,为结点数N。
空间复杂度:O(N)。
递归算法的空间复杂度 = 递归的深度 * 每次递归的空间复杂度。
当树退化为链表时,递归深度最深,为结点个数N,每次的空间复杂度为O(1)。
代码
// 辅助函数:用于交换结点左右孩子
void exchange_node(TreeNode* root){
TreeNode* node = root->left;
root->left = root->right;
root->right = node;
}
TreeNode* mirrorTree(TreeNode* root) {
if(root == NULL)
return NULL;
exchange_node(root); // 交换root结点的左右孩子
mirrorTree(root->left); // 交换root左结点的左右孩子
mirrorTree(root->right); // 交换root右结点的左右孩子
return root;
}
3.剑指offer:28题.对称的二叉树
力扣https://leetcode.cn/problems/dui-cheng-de-er-cha-shu-lcof/
方法:递归法
根据对称性,把树分为外部和内部。
当外部的结点和内部的结点都对应相等时,说明该树具有对称性。
需要一个辅助函数,用来比较对称的结点是否相等。
当树为空树时,返回true。
当树不为空树时,利用辅助函数比较每一层的外部结点和内部节点是否相等,相等返回true。
算法思路
1. 构造函数isCompare,比较结点的左右子树是否相等。
1> 比较结点的左右子树 (递归的限制条件&递归方程)
i. 左右子树为空
说明它们的父节点为叶子结点,而已比较的结点都对称,所以返回true。
ii. 只有其中一个子树为空
说明该二叉树不可能对称,所以返回false。
iii. 左右子树都存在
相同时表示对称,返回true,反之,返回false。
注:i,ii为限制条件,iii为递归方程。
2> 确定外部和内部,比较树的对称位置结点是否相同(递推方程)
外部=左孩子的左孩子&右孩子的右孩子
内部=左孩子的右孩子&右孩子的左孩子
3> 只有当外部和内部都对应相等时,整棵树才对称,返回true。
2. 在函数isSymmetric中,
当树为空树时,返回true。
不为空,调用函数isCompare比较结点左右子树是否有对称性。
算法图
复杂度分析
假设树结点有N个。
时间复杂度:O(N)。
递归算法的时间复杂度 = 递归的次数 * 每次递归的时间复杂度。
每一次递归都递归两个结点,递归次数为N/2,所以时间复杂度为O(N)。
空间复杂度:O(N)。
递归算法的空间复杂度 = 递归的深度 * 每次递归的空间复杂度。
当树退化为链表时,递归深度为结点个数N,
每次递归左右子树,空间复杂度为O(2),是常数阶可记为O(1),
所以空间复杂度为O(N)。
代码
// 比较节点的左右孩子是否对称
bool isCompare(TreeNode* left, TreeNode* right){
if(left == NULL && right == NULL) // 表示只有一个结点,不用比较孩子,返回true
return true;
// 表示节点左右孩子不对称
else if(left != NULL && right == NULL)
return false;
else if(left == NULL && right != NULL)
return false;
if(left->val != right->val)
return false;
// 确定外部和内部
bool outside = isCompare(left->left,right->right);
bool inside = isCompare(right->left,left->right);
// 只有当外部和内部对应结点都相同时,才说明树有对称性。
return outside && inside;
}
bool isSymmetric(TreeNode* root) {
if(root == NULL) // 空树,返回true
return true;
return isCompare(root->left,root->right); // 递归比较根节点的左右子树是否对称
}
23. 1.6 第七次
今天三题都用递归解决,在此,对递归的写法做个小结:
递归三部曲:
1. 确定限制条件
递归到递无可递的时候,就让递归调用的栈全部弹出。
通常都是结点为空,递归到0等情况,不再递归,而是返回,标志调用完毕,可弹出。
2. 写出递归方程
递归方程就是每次递归要做的事情,所以递归适用于需要重复执行某一块代码的情况。
3. 明确递推方程
让栈朝着我们需要的方向进行调用,只有遇到限制条件时,结束递归,否则持续调用。
闲谈:
制图依旧需要大量的时间,但我已经有点熟能生巧了,速度越来越快(但其实还是很慢)。
所以今天比以往写博客的速度要更快一点,很开心,能感受到进步啦。
过去,在要写递归的时候,我总是害怕,担忧,认为自己肯定写不对,
但今天我顺着自己的思路,用递归写出一道题了,首次即通过,
虽然题目确实不难,但对我来说,是一个不小的肯定!
再接再厉,继续加油!
最后,本文如有问题,欢迎斧正!