226.翻转二叉树
题目:力扣
可以很容易发现只需要把二叉树的左右两个子树进行交换就可以翻转。
这里需要注意的是遍历顺序:
遍历的过程中去翻转每一个节点的左右孩子就可以达到整体翻转的效果。
注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!
层序遍历也是可以的。层序遍历中遍历到左右节点的时候进行交换就可以。
只要是连续遍历到左节点和右节点的方法都可以。
递归法:
回顾一下递归三部曲:
- 确定递归函数的参数和返回值
- 确定终止条件 - 当前节点为空的时候,就返回
- 确定单层递归的逻辑 - 先进行交换左右孩子节点,然后反转左子树,反转右子树
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
invertTreeHelper(root);
return root;
}
public void invertTreeHelper(TreeNode root){
if(root == null){
return;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTreeHelper(root.left);
invertTreeHelper(root.right);
}
更加简便的递归写法:
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
采用迭代法解题:<前序遍历法>
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
LinkedList<TreeNode> stack = new LinkedList<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
if(node.right != null) stack.push(node.right);
if(node.left != null) stack.push(node.left);
}
return root;
}
参考资料:代码随想录
101.对称二叉树
题目:力扣
可以发现这道题并不是要比较左右两个节点是否相等。
而是需要判断root的左右两个子树是否互相为翻转二叉树。那么就需要判断
- 左子树的外侧节点和右子树的外侧节点是否相等;
- 左子树的内侧节点和右子树的内侧节点是否相等。
那么二叉树的遍历顺序呢?「遍历顺序非常重要,每道题都需要花时间思考一下」
这里只能选择后序遍历 - 当左子树的左节点和右子树的右节点相等;左子树的右节点和右子树的左节点相等,那么说明这个部分是可以翻转的也就是对称的。可以继续看上一层节点。
如果是前序遍历:先遍历中间节点,但是就算中间节点是对称的,但是并不知道它的左右两个节点是否对称
如果是中序遍历:在遍历到外侧节点判断之后就会返回上一层,这样其实内侧节点还没有被判断
具体实现思路:
递归法:
1. 确定递归函数的参数和返回值 - 因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。
2. 确定终止条件 - 要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚。
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:
- 左右都不为空,比较节点数值,不相同就return false
3.确定单层递归的逻辑 - 此时才进入单层递归的逻辑,单层递归的逻辑就是处理左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
代码实现:
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return isSymmetricHelper(root.left, root.right);
}
public boolean isSymmetricHelper(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;
}
//单层循环逻辑
boolean inside = isSymmetricHelper(left.right, right.left);//内侧子树
boolean outside = isSymmetricHelper(left.left, right.right);//外侧子树
return inside && outside;//判断内侧和外侧是否都对称
}
}
迭代法:
使用迭代法其实可以用前序遍历或者后序遍历的思路进行实现。
把左右子树当作两颗子树,左子树正常遍历顺序(中左右),右子树则是(中右左),每次将放入容器的两个元素取出判断两者值是否相同。
并且在填入新的元素之前,判断两边具有的孩子情况是否对称。
public boolean isSymmetric(TreeNode root) {
//对第一个根节点进行判断
if(root == null) return true;
if(root.left == null && root.right == null) return true;
if(root.left != null && root.right == null || root.left == null && root.right != null) return false;
//初始化栈 - 将左子树起点和右子树起点放入栈
LinkedList<TreeNode> stack = new LinkedList<>();
stack.push(root.left);
stack.push(root.right);
while(!stack.isEmpty()){
//先弹出要对比的两个节点 - 注意弹出顺序(先进后出,左先放,左后出)
TreeNode right = stack.pop();
TreeNode left = stack.pop();
if(right.val != left.val){
return false;
}
//判断外侧节点空指针情况
if(left.left == null && right.right !=null || left.left != null && right.right == null) return false;
//判断内侧节点空指针情况
if(left.right == null && right.left !=null || left.right != null && right.left == null) return false;
//添加左右子树外内侧节点:按照前序遍历-先加右孩子节点「参照左子树遍历顺序」,先放左子树再放右子树
if(left.right != null) stack.push(left.right);
if(right.left != null) stack.push(right.left);
//添加左右子树外侧节点:按照前序遍历-后加左孩子节点「参照左子树遍历顺序」,先放左子树再放右子树
if(left.left != null) stack.push(left.left);
if(right.right!= null) stack.push(right.right);
}
return true;
}
参考资料:代码随想录
104. 二叉树的最大深度
题目:力扣
二叉树的深度和高度的定义:
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
这道题用层序遍历已经解过一次。非常简单的层序遍历模板思路:
public int maxDepth(TreeNode root) {
if(root == null) return 0;
int depth = 0;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
depth++;
int size = queue.size();//计算单层node的个数
for(int i=0; i<size; i++){
TreeNode node = queue.poll();
//储存下一层node
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
}
return depth;
}
这里再用递归的思路进行解题:
用什么遍历方式呢?
如果用前序遍历,那么是求深度(从根节点到该叶子节点的节点数);
如果是用后序遍历,那么是求高度(从该根节点到叶子节点的节点数, 根节点的高度就是二叉树的最大深度)。
用前序遍历是dfs遍历算法 - 深度优先
而用后序遍历是将底端的值求出后然后再往上汇聚 - 从叶子节点往上数。那么一个节点的最大高度由左右两侧的最大高度决定的:
也就是,左右子树中最大的高度然后+1
这样一直往上汇聚一直到根节点,也就求出了根节点的高度,也就是这个二叉树的最大深度。
代码实现:
public int maxDepth(TreeNode root) {
return maxDepthHelper(root, 0);
}
public int maxDepthHelper(TreeNode root, int depth){
//终止条件 - 这里才是开始计数depth
if(root == null){
return 0;
}
//单层循环
int left = maxDepthHelper(root.left, depth);
int right = maxDepthHelper(root.right, depth);
//这个节点的最大深度是 左右节点的最大深度+1
int maxD = Math.max(left,right)+1;
return maxD;
}
其实这个解法中的depth没有用到 - 这里直接用的其实是return值中的一个累加
正确的写法:
public int maxDepth(TreeNode root) {
if(root == null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
int maxD = Math.max(left, right)+1;
return maxD;
}
前序遍历的实现方法:
前序其实是把每条二叉树的路径都走一遍,然后将每条路径的深度和最大深度做对比,最后返回最大深度。---是一个经典的深度优先算法的实现过程。也就是回溯
class Solution {
public int maxDepth(TreeNode root) {
if(root == null) return 0;
maxDepthHelper(root, 1);
return maxD;
}
int maxD = 0;
public void maxDepthHelper(TreeNode root, int depth){
//单层循环 - 判断是否需要更新最大深度
if(depth > maxD) maxD = depth;
//终止条件
if(root == null){
return;
}
//深度遍历
//左边
if(root.left != null){
depth++;
maxDepthHelper(root.left, depth);
depth--; //回溯
}
//右边
if(root.right != null){
depth++;
maxDepthHelper(root.right, depth);
depth--;
}
return;
}
559. n叉树的最大深度
题目:力扣
这道题采用层序遍历也是非常简单,直接用模板就可以。
public int maxDepth(Node root) {
if(root == null) return 0;
LinkedList<Node> queue = new LinkedList<>();
queue.add(root);
int depth = 0; //初始化深度
while(!queue.isEmpty()){
int size = queue.size();//计算单层node的个数
depth++;//深度加一
for(int i=0; i<size; i++){
Node node = queue.poll();
//储存下一层node
List<Node> childrens = node.children;
for(Node children: childrens){
queue.add(children);
}
}
}
return depth;
}
递归法:
如果用后序遍历的话,思路是和二叉树是一样的,只不过这里找节点中所有子树的最大高度:
class Solution {
public int maxDepth(Node root) {
return maxDepthHelper(root, 0);
}
public int maxDepthHelper(Node root, int depth){
//终止条件
if(root == null){
return 0;
}
//单层循环
int maxD = 0; //找到子树中的最大高度
for(Node node : root.children){
maxD = Math.max(maxD, maxDepthHelper(node, depth));
}
maxD = maxD +1;//在所有子树中最大高度基础上加一
return maxD;
}
}
这里depth在递归中没有被用上,正确的写法:
public int maxDepth(Node root) {
if(root == null){
return 0;
}
int maxD = 0; //找到子树中的最大高度
for(Node node : root.children){
maxD = Math.max(maxD, maxDepth(node));
}
maxD = maxD +1;//在所有子树中最大高度基础上加一
return maxD;
}
参考资料:代码随想录
111.二叉树的最小深度
题目:力扣
之前用层序遍历的方法来解题,直接套用模板就可。
public int minDepth(TreeNode root) {
if(root == null) return 0;
int depth = 0;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.add(root);
while(!queue.isEmpty()){
depth++;
int size = queue.size();//计算单层node的个数
for(int i=0; i<size; i++){
TreeNode node = queue.poll();
//判断是否 - 到达最小深度
if(node.left == null && node.right == null) return depth;
//储存下一层node
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
}
return depth;
}
递归法:
采用后序遍历的话,需要注意的是从什么地方算高度,如果是找左右子树最小节点,那么如果是一个单链的极端情况,会出现最小深度为1,但其实这不是最小深度。
因此当一个节点只有左子树时,最小深度就是左子树的高度+1;
当一个节点只有右子树时,最小深度就是右子树的高度+1;
只有当一个节点没有左子树或者没有右子树 或者 左右子树都有的时候,最小深度才是左右子树中最小的高度+1;
在自己实现的时候发现了一个bug,就是如果不在最后答案中+1,会比测试中的正确答案少1;
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
return minDepthHelper(root, 0)+1;
}
public int minDepthHelper(TreeNode root, int Depth){
if(root == null) return 0;
int left = minDepthHelper(root.left, Depth);
int right = minDepthHelper(root.right, Depth);
//注意最小深度是从左右两个节点都为空开始算起
//若有一个不为空,那么这个地方的最小深度就是不为空的高度加一
int minD = 0;
if(root.left != null && root.right == null){
minD = left+1;
}
if(root.right != null && root.left == null){
minD = right+1;
}
if(root.right != null && root.left != null){
minD = Math.min(left, right)+1;
}
return minD;
}
}
发现问题时我在判断了左为空或者右为空,左右同时不为空的情况,没有判断左右同时为空的情况,因此当遍历到叶子节点的时候,并没有+1,叶子节点处的高度为0,因此最后的答案中需要+1;
和之前存在的问题一样,depth其实在这个递归循环中没有发挥作用,正确写法是:
public int minDepth(TreeNode root) {
if(root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
//注意最小深度是从左右两个节点都为空开始算起
//若有一个不为空,那么这个地方的最小深度就是不为空的高度加一
if(root.left != null && root.right == null){
return left+1;
}
if(root.right != null && root.left == null){
return right+1;
}
//左右节点为空和都不为空的情况
return Math.min(left, right)+1;
}
采用前序遍历的方法 - 深度优先算法,直接找所有路径中的最小值。
但是判断条件和之前的找最大值有所不同,不能每一步都去比较最小,因为在最开始的时候一定是最小的。
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
minDepthHelper(root, 1);
return minD;
}
int minD = Integer.MAX_VALUE;
public void minDepthHelper(TreeNode root, int depth){
//终止条件 - 当节点的左右节点都不存在,则判断最小深度是否要替换,并返回
if(root.right == null && root.left == null){
minD = Math.min(depth, minD);
return;
}
//深度遍历
//左边
if(root.left != null){
depth++;
minDepthHelper(root.left, depth);
depth--; //回溯
}
//右边
if(root.right != null){
depth++;
minDepthHelper(root.right, depth);
depth--;
}
return;
}
}
参考资料:代码随想录
222.完全二叉树的节点个数
题目:力扣
在读题的时候第一反应就是其实可以直接对节点进行遍历,那么不管是前中后序遍历还有层序遍历,都可以统计二叉树的节点。
比较简洁的写法是采用后序遍历的方法:
可以这么理解 - 某个节点位置下的节点个数,是等于左子树的节点个数+右子树的节点个数+该节点(1)。
因此可以先求出左子树的节点个数,再求出右子树的节点个数,然后向上汇聚。
代码实现:
public int countNodes(TreeNode root) {
//终止条件
if(root == null){
return 0;
}
//单层循环
//计算左子树
int left = countNodes(root.left);
//计算右子树
int right = countNodes(root.right);
//计算该节点的个数
int count = left + right +1;
//返回值
return count;
}
此时的时间复杂度为O(n),但是这道题有过条件 - 这个二叉树是完全二叉树。因此 ,可以思考使用二叉树的特性让这个时间复杂度降低,也就是不用把所有的节点都遍历到。
- 完全二叉树的特性:深度为n的二叉树中的节点为 2^n-1
因此如果我们确定一个完全二叉树以及这个二叉树的深度,也就可以直接计算得到二叉树的节点。
那么如果判断这个二叉树是一个完全二叉树呢?
一个很巧妙的方法是:
采用两个指针:一个从左孩子出发一直往左走;一个从右孩子出发一直往右走。
那么如果左孩子走的到底的路和右孩子走到底的路所遇到的节点个数是一样的,那么说明者二叉树是一个完全二叉树。
同时也得到了这个二叉树的深度,因此就可以直接计算了。
那么如果左右两边并不相等,那么就会重新从根节点的左侧开始递归查找符合的满二叉树。
这里仍然是采用后序遍历的方法,代码相对更加简洁:
节点的个数 = 左子树的个数+右子树的个数+该节点。
只不过不需要每一个都去遍历地获得左右子树的个数,如果是满二叉树则直接就可以获得节点个数。
那么会不会有这种情况呢?左右节点的遍历个数一样但是并不是满二叉树 。
但是很明显这种情况这个下整个二叉树也不是完全二叉树了,因此是不存在这种情况的。
代码实现:
class Solution {
public int countNodes(TreeNode root) {
//终止条件1
if(root == null){
return 0;
}
TreeNode left = root.left;//标记左孩子
TreeNode right = root.right;//标记右孩子
//初始化深度
int leftdepth = 0;
int rightdepth = 0;
//找到左侧深度
while(left != null){
left = left.left;
leftdepth++;
}
//找到右侧深度
while(right != null){
right = right.right;
rightdepth++;
}
//终止条件2
//如果两侧深度相等说明是满二叉树,那么计算这个二叉树的节点 - 2<<0 = 2的一次 =2
if(leftdepth == rightdepth) return (2<<leftdepth) -1;
//单层循环
//计算左子树
int leftc = countNodes(root.left);
//计算右子树
int rightc = countNodes(root.right);
//计算该节点的个数
int count = leftc + rightc +1;
//返回值
return count;
}
}
参考资料:代码随想录