目录
101比226难
226.翻转二叉树
针对二叉树的问题,解题之前一定要想清楚究竟是前中后序遍历,还是层序遍历。
递归法(前中后序)
前序后序好实现,中序理解不一样。
举例前序:先进行交换左右孩子节点,然后反转左子树,反转右子树。
swap(root->left, root->right);
invertTree(root->left);
invertTree(root->right);
后序就是把swap放到最后一行。
注意:中序swap不可直接放在中间。因为先反转左子树,交换左右孩子节点后,反转后的左子树到了现在的右子树,再进行反转右子树的时候其实是对同一个左子树反转了两遍。那么此时就应该修改为 先反转左子树,交换左右孩子节点,再反转左子树 即可。
invertTree(root->left);
swap(root->left, root->right);
invertTree(root->left);
递归法代码如下:(优先使用前序,简单易理解)
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
// 1.前序迭代法:中左右
// swapChildren(root);
// invertTree(root.left);
// invertTree(root.right);
// 2.后序迭代法:左右中
// invertTree(root.left);
// invertTree(root.right);
// swapChildren(root);
// 3.中序迭代法:左中左
invertTree(root.left);
swapChildren(root);
invertTree(root.left);
return root;
}
private void swapChildren(TreeNode root) {
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
}
}
迭代法(前中后序)
有点忘了迭代法,等二刷再想,如果想了解的友友参考之前的迭代法。
广度优先遍历
也就是层序遍历,层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍
BFS过程,树的每一层节点都被逐层翻转,最终得到一棵左右子树完全翻转的二叉树。
这段代码的时间复杂度为 O(n),其中 n 是树中节点的数量,因为每个节点都被访问了一次。空间复杂度为 O(w),其中 w 是树的最大宽度,因为在最坏情况下,队列中可能会包含一整层的节点。
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
// 4.层序遍历法BFS
ArrayDeque<TreeNode> deque = new ArrayDeque<>();
deque.offer(root);
while(!deque.isEmpty()){
int size = deque.size();
while(size-- > 0){
//取出队列中的一个节点,交换其左右子节点,然后将其非空的子节点加入队列,准备下一层的处理。
TreeNode node = deque.poll();
swapChildren(node);
if(node.left != null) deque.offer(node.left);
if(node.right != null) deque.offer(node.right);
}
}
return root;
}
private void swapChildren(TreeNode root) {
TreeNode tmp = root.left;
root.left = root.right;
root.right = tmp;
}
}
101. 对称二叉树
二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,即就是比较的是两个子树的里侧和外侧的元素是否相等。
递归法
递归三部曲
1. 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
返回值自然是bool类型。
2. 确定终止条件
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
- 左右都不为空,比较节点数值,不相同就return false
- else 剩下的就是 左右节点都不为空,且数值相同的情况。
3. 确定单层递归的逻辑
单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理)
return isSame;
完整代码如下:
class Solution {
/**
* 递归法
*/
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return compare(root.left, root.right);
}
private boolean compare(TreeNode left, TreeNode right) {
if (left == null && right != null) return false;
else if (left != null && right == null) return false;
else if (left == null && right == null) return true;
else if (left.val != right.val) return false; //没有为空的情况了
else {
boolean compareOutside = compare(left.left, right.right); // 比较外侧
boolean compareIntside = compare(left.right, right.left); // 比较内侧
return compareOutside && compareIntside;
}
}
}
迭代法
总结:在迭代法中使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。
使用队列
可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历)
通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等
条件判断和递归的逻辑是一样的。
class Solution {
public boolean isSymmetric(TreeNode root) {
/**
* 迭代法
* 使用普通队列
*/
Queue<TreeNode> deque = new LinkedList<>();
deque.offer(root.left); // 将左子树头结点加入队列
deque.offer(root.right); // 将右子树头结点加入队列
while (!deque.isEmpty()) { // 接下来就要判断这两个树是否相互翻转
TreeNode leftNode = deque.poll();
TreeNode rightNode = deque.poll();
// 左节点为空、右节点为空,此时说明是对称的
if (leftNode == null && rightNode == null) {
continue;
}
// 左右一个节点不为空,或者都不为空但数值不相同,返回false
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
deque.offer(leftNode.left); // 加入左节点左孩子
deque.offer(rightNode.right);// 加入右节点右孩子
deque.offer(leftNode.right); // 加入左节点右孩子
deque.offer(rightNode.left); // 加入右节点左孩子
}
return true;
}
}
使用栈
细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。
只要把队列原封不动的改成栈就可以了
不建议使用栈,会第一个队列就可以了(个人见解)
class Solution {
public boolean isSymmetric(TreeNode root) {
/**
* 迭代法
* 使用双端队列,相当于两个栈
*/
Deque<TreeNode> deque = new LinkedList<>();
deque.offerFirst(root.left);
deque.offerLast(root.right);
while(!deque.isEmpty()){
TreeNode leftNode = deque.pollFirst();
TreeNode rightNode = deque.pollLast();
if (leftNode == null && rightNode == null) {
continue;
}
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
// 这里顺序与使用Deque不同
deque.offerFirst(leftNode.left); // 加入左节点左孩子
deque.offerFirst(leftNode.right);// 加入左节点右孩子
deque.offerLast(rightNode.right); // 加入右节点右孩子
deque.offerLast(rightNode.left); // 加入右节点左孩子
}
return true;
}
}
个人思路
借助226先将root翻转,然后依次遍历这棵树,看是否与翻转前的树完全相等。
有点过于麻烦,不用看了。
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
TreeNode revTree = invertTree(cloneTree(root)); // 克隆一棵新的树再翻转
return isSameTree(root, revTree); // 比较原树和翻转后的树是否相同
}
// 递归交换左右子树
private TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
// 比较两棵树是否相同
private boolean isSameTree(TreeNode tree, TreeNode revTree) {
if (tree == null && revTree == null) {
return true;
}
if (tree == null || revTree == null) {
return false;
}
if (tree.val != revTree.val) {
return false;
}
// 递归比较左右子树
return isSameTree(tree.left, revTree.left) && isSameTree(tree.right, revTree.right);
}
// 克隆树
private TreeNode cloneTree(TreeNode root){
if (root == null) {
return null;
}
TreeNode newNode = new TreeNode(root.val);
newNode.left = cloneTree(root.left);
newNode.right = cloneTree(root.right);
return newNode;
}
}
这两道题目基本和本题是一样的,只要稍加修改就可以AC。有空可以做。
104.二叉树的最大深度
递归法
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
- 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
- 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
后序(左右中)(推荐,易理解)
class Solution {
public int maxDepth(TreeNode root) {
// 递归法--后序
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
int depth = Math.max(leftDepth, rightDepth) + 1;
return depth;
}
}
代码精简之后代码如下:
class Solution {
public int maxDepth(TreeNode root) {
// 递归法--后序--精简法
if (root == null) {
return 0;
}
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
前序(中左右)
充分表现出求深度回溯的过程
class Solution {
/**
* 递归法(求深度法)
*/
//定义最大深度
int maxnum = 0;
public int maxDepth(TreeNode root) {
ans(root,0);
return maxnum;
}
//递归求解最大深度
void ans(TreeNode tr,int tmp){
if(tr==null) return;
tmp++;
maxnum = maxnum<tmp?tmp:maxnum;
ans(tr.left,tmp);
ans(tr.right,tmp);
tmp--;
}
}
迭代法(层序遍历)
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度。
所以这道题的迭代法就是一道模板题,可以使用二叉树层序遍历的模板来解决的。
class Solution {
/**
* 迭代法,使用层序遍历
*/
public int maxDepth(TreeNode root) {
if(root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
int depth = 0;
while (!deque.isEmpty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode node = deque.poll();
if (node.left != null) {
deque.offer(node.left);
}
if (node.right != null) {
deque.offer(node.right);
}
}
}
return depth;
}
}
559.n叉树的最大深度
递归法(后序)
class Solution {
/*递归法,后序遍历求root节点的高度*/
public int maxDepth(Node root) {
if (root == null) return 0;
int depth = 0;
if (root.children != null){
for (Node child : root.children){
depth = Math.max(depth, maxDepth(child));
}
}
return depth + 1; //中节点
}
}
迭代法(层序遍历)
根据104进行修改
注意:
1. 在处理 n 叉树时,每个节点可能有多个子节点。对于 n 叉树,每个节点有一个子节点列表,因此你需要遍历每个节点的子节点列表,而不是只检查 left
和 right
子节点。
2. TreeNode
和 Node
TreeNode
用于二叉树,每个节点最多有两个子节点(left
和right
)。Node
用于 n 叉树,每个节点可以有任意数量的子节点,存储在children
列表中。
class Solution {
public int maxDepth(Node root) {
if (root == null) {
return 0;
}
Deque<Node> deque = new LinkedList<>();
int depth =0;
deque.offer(root);
while(!deque.isEmpty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
Node node = deque.poll();
for (Node child : node.children) {
if (child != null) {
deque.offer(child);
}
}
}
}
return depth;
}
}
111.二叉树的最小深度
注意!有坑。
不能直接把最大深度的max改为min。如图所示。
题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
递归法--后序(左右中)
1. 确定递归函数的参数和返回值
参数为要传入的二叉树根节点,返回的是int类型的深度。
2. 确定终止条件
终止条件也是遇到空节点返回0,表示当前节点的高度为0。
3. 确定单层递归的逻辑
不能直接改为以下的min!如果这么求的话,没有左孩子的分支会算为最短深度。
int leftDepth = getDepth(node->left);
int rightDepth = getDepth(node->right);
int result = 1 + min(leftDepth, rightDepth);
return result;
正确操作:
如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。
最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
if (node->left == NULL && node->right != NULL) {
return 1 + rightDepth;
}
// 当一个右子树为空,左不为空,这时并不是最低点
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
//左右孩子都不为空;或者都为空(叶子节点)
int result = 1 + min(leftDepth, rightDepth);
return result;
遍历的顺序为后序(左右中),可以看出:求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
class Solution {
/**
* 递归法,相比求MaxDepth要复杂点
* 因为最小深度是从根节点到最近**叶子节点**的最短路径上的节点数量
*/
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = minDepth(root.left);
int rightDepth = minDepth(root.right);
if (root.left == null) {
return rightDepth + 1;
}
if (root.right == null) {
return leftDepth + 1;
}
// 左右结点都不为null
return Math.min(leftDepth, rightDepth) + 1;
}
}
递归法二
class Solution {
/**
* 递归法(思路来自二叉树最大深度的递归法)
* 该题求最小深度,最小深度为根节点到叶子节点的深度,所以在迭代到每个叶子节点时更新最小值。
*/
int depth = 0;
// 定义最小深度,初始化最大值
int minDepth = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
dep(root);
return minDepth == Integer.MAX_VALUE ? 0 : minDepth;
}
void dep(TreeNode root){
if(root == null) return ;
// 递归开始,深度增加
depth++;
dep(root.left);
dep(root.right);
// 该位置表示递归到叶子节点了,需要更新最小深度minDepth
if(root.left == null && root.right == null)
minDepth = Math.min(minDepth , depth);
// 递归结束,深度减小
depth--;
}
}
迭代法(层序遍历)
个人感觉该题目层序遍历最好理解
class Solution {
/**
* 迭代法,层序遍历
*/
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
int depth = 0;
while (!deque.isEmpty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode poll = deque.poll();
if (poll.left == null && poll.right == null) {
// 是叶子结点,直接返回depth,因为从上往下遍历,所以该值就是最小值
return depth;
}
if (poll.left != null) {
deque.offer(poll.left);
}
if (poll.right != null) {
deque.offer(poll.right);
}
}
}
return depth;
}
}
第十四天的总算是结束了,直冲Day15!
最近因为科研等等事情刷题进度有所下降,下周需要备战别的东西,待10月开启新篇章,猛猛刷,继续加油!