DAY 14 二叉树的遍历
二叉树中,大部分问题都可用递归解决
递归:
- 确定递归函数的参数和返回值:可以边写边添加
- 确定终止条件
- 明确函数的目的,确定单层的逻辑
144 二叉树的前序遍历
给你二叉树的根节点 root
,返回它节点值的 前序 遍历。
递归
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
preRecursive(res, root);
return res;
}
// 输入一个根节点和一个数组,就能获得一个存有二叉树前序遍历顺序的数组
public void preRecursive(List<Integer> res, TreeNode root){
if(root==null){
return;
}
res.add(root.val);
preRecursive(res,root.left);
preRecursive(res, root.right);
}
}
94 二叉树的中序遍历
给定一个二叉树的根节点 root
,返回 它的 中序 遍历 。
递归
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
if(root==null){
return res;
}
inorderTraversal(root.left);
res.add(root.val);
inorderTraversal(root.right);
return res;
}
}
145 二叉树的后序遍历
给你一棵二叉树的根节点 root
,返回其节点值的 后序遍历 。
递归
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postRecursive(res,root);
return res;
}
public void postRecursive(List<Integer> res, TreeNode root){
if(root==null){
return;
}
postRecursive(res,root.left);
postRecursive(res,root.right);
res.add(root.val);
}
}
DAY 15
102 二叉树的层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null){
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
List<Integer> curList = new ArrayList<>();
for(int i = queue.size();i>0;i--){
root = queue.poll();
curList.add(root.val);
if(root.left!=null) queue.offer(root.left);
if(root.right!=null) queue.offer(root.right);
}
res.add(curList);
}
return res;
}
拓展:分层
双重循环实现
int curLevelSize = 0;
while(!queue.isEmpty()){
curLevelSize = queue.size();
while(curLevelSize>0){
... ...
}
}
226 翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
// 输入一个根节点,就可以获得一棵翻转好的二叉树的根节点
public TreeNode invertTree(TreeNode root) {
if(root==null){
return null;
}
TreeNode tempLeft = root.left; // 暂存左孩子
root.left = invertTree(root.right); // 左孩子为翻转好的右子树根节点
root.right = invertTree(tempLeft); // 右孩子为翻转好的左子树根节点
return root;
}
101 对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
class Solution {
public boolean isSymmetric(TreeNode root) {
return isSymmetric(root.left,root.right);
}
public boolean isSymmetric(TreeNode leftRoot, TreeNode rightRoot){
if(leftRoot==null && rightRoot==null){
return true;
}else if(leftRoot == null || rightRoot == null){
return false;
}
return leftRoot.val==rightRoot.val && isSymmetric(leftRoot.left,rightRoot.right) && isSymmetric(leftRoot.right,rightRoot.left);
}
}
DAY 16
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数后者节点数(取决于高度从0开始还是从1开始)
104 二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
public int maxDepth(TreeNode root) {
if(root==null){
return 0;
}
return 1 + Math.max(maxDepth(root.left),maxDepth(root.right));
}
559 N 叉树的最大深度
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。
public int maxDepth(Node root) {
if(root == null){
return 0;
}
int res = 1;
for(int i=0;i<root.children.size();i++){
int childDeepth = maxDepth(root.children.get(i));
res = Math.max(res, 1+childDeepth);
}
return res;
}
111 二叉树的最小深度
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
解题思路:
题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点,左右孩子都为空的节点才是叶子节点!!!仅左或右孩子为空的不是叶子节点,不是递归停止的条件
![image-20221123114055740](img/image-20221123114055740.png)
归结束条件:
- 当 root 节点左右孩子都为空时(叶子节点),返回 1
- 当 root 节点左右孩子有一个为空时,返回不为空的孩子节点的深度
- 当 root 节点左右孩子都不为空时,返回左右孩子较小深度的节点值
public int minDepth(TreeNode root) {
if(root == null){
return 0;
}else if(root.left==null && root.right==null){ // 找到叶子结点啦
return 1;
}
// left 或 right 有一个不为空
int leftMin = minDepth(root.left);
int rightMin = minDepth(root.right);
// return root.left==null? 1+rightMin : (root.right==null? 1+leftMin : 1+Math.min(leftMin,rightMin));
// 简化 : 当左或右孩子为空时,leftMin和RightMin 一个为0,一个不为0,任何数加0值变,因此此时的结果为 1+leftMin+rightMin
return root.left==null || root.right==null ? 1+leftMin+rightMin : 1+Math.min(leftMin,rightMin);
}
222 完全二叉树的节点个数
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h
层,则该层包含 1~ 2h
个节点。
方法一:递归统计
public int countNodes(TreeNode root) {
if(root==null){
return 0;
}
return 1+countNodes(root.left)+countNodes(root.right);
}
方法二:
如果去判断一个左子树或者右子树是不是满二叉树呢?
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树
DAY 17
110 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
public boolean isBalanced(TreeNode root) {
if(root==null){
return true;
}
int hightL = height(root.left);
int heightR = height(root.right);
return Math.abs(hightL-heightR)<=1 && isBalanced(root.left) && isBalanced(root.right);
}
public int height(TreeNode root){
if(root==null){
return 0;
}
return 1+Math.max(height(root.left),height(root.right));
}
257 二叉树的所有路径
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
回溯
List<String> res = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
treePath(root, new ArrayList<>());
return res;
}
public void treePath(TreeNode root, List<String> list){
// 1. root为空:直接return
if(root==null){
return;
}
// 2. root左右孩子均空:找到叶子结点,将这条路径加入
if(root.left==null && root.right==null){
list.add(String.valueOf(root.val));
StringBuilder sb = new StringBuilder();
for(int i=0;i<list.size();i++){
sb.append(list.get(i));
}
res.add(sb.toString());
list.remove(list.size()-1);
}
// 3. root左或者右有一个不空:加入不空的
list.add(root.val+"->");
treePath(root.left,list);
treePath(root.right,list);
list.remove(list.size()-1);
}
404. 左叶子之和
给定二叉树的根节点 root
,返回所有左叶子之和。
public int sumOfLeftLeaves(TreeNode root) {
if(root==null){
return 0;
}
int sum = 0;
// 左孩子
TreeNode leftRoot = root.left;
if(leftRoot!=null){
// 1) 左孩子是叶子结点
if(leftRoot.right==null && leftRoot.left==null){
sum += leftRoot.val;
}else{
// 2) 左孩子是棵树,返回左子树所有左叶子之和
sum += sumOfLeftLeaves(leftRoot);
}
}
// 右孩子
TreeNode rightRoot = root.right;
if(rightRoot!=null){
// 1) 右孩子是叶子结点
if(rightRoot.right==null && rightRoot.left==null){
sum += 0;
}else{
// 2) 右孩子不是叶子结点,是棵树,返回右子树所有左叶子之和
sum += sumOfLeftLeaves(rightRoot);
}
}
// 当前结点: 是叶子节点返回0; 不是叶子结点,返回以当前节点为根的树的所有左叶子之和
return leftRoot==null && rightRoot==null ? 0 : sum;
}
DAY 18
513 找树左下角的值
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
迭代法
过于麻烦了
public int findBottomLeftValue(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode nextLevel = root; // 不断移动,用于找下一层中最左边结点
TreeNode leftNode = root; // 记录找到的最左边结点
while(!queue.isEmpty()){
TreeNode curNode = queue.poll();
if(curNode == nextLevel){
if(curNode.left!=null){
nextLevel = curNode.left;
leftNode = curNode.left;
}else if(curNode.right!=null){
nextLevel = curNode.right;
leftNode = curNode.right;
} else nextLevel = queue.peek();
}
if(curNode.left!=null) queue.offer(curNode.left);
if(curNode.right!=null) queue.offer(curNode.right);
}
return leftNode.val;
}
层序遍历分层
public int findBottomLeftValue(TreeNode root) {
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
TreeNode leftNode = root;
int curLevelSize = 0;
while(!queue.isEmpty()){
curLevelSize = queue.size();
leftNode = queue.peek();
while(curLevelSize>0){
TreeNode curNode = queue.poll();
if(curNode.left!=null) queue.offer(curNode.left);
if(curNode.right!=null) queue.offer(curNode.right);
curLevelSize--;
}
}
return leftNode.val;
}
递归法
public int findBottomLeftValue(TreeNode root) {
// 错误: dfs(root,0); // 由于level默认为0,若树从0开始算起,则仅一个根节点的情况无法包含进去
dfs(root,1);
return leftVal;
}
int level; // 已遍历树中最左下角结点所在层数
// TreeNode node = new TreeNode(); // 已遍历树中最左下角结点
// 优化
int leftVal; // 已遍历树中最左下角结点的值
// 输入一个节点就可以找到 以当前结点为根的树中最左下角结点,并与已经找过的子树最左下角结点作比较,保留深度更大的
public void dfs(TreeNode root, int curLevel){
if(root==null){
return;
}
// 当前节点是叶子节点,且当前结点所在层数大于node结点所在层数 (不可取等,因为前序遍历,同一层中当前节点左边的结点已经判断过了)
if(root.left==null && root.right==null && curLevel>level){
leftVal = root.val;
level = curLevel;
}
dfs(root.left,curLevel+1);
dfs(root.right,curLevel+1);
}
112 路径总和
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null){
return false;
}
targetSum -= root.val;
if(root.left==null && root.right==null){
return targetSum==0; // wow
}
return hasPathSum(root.left, targetSum) || hasPathSum(root.right,targetSum);
}
递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。
113 路径总和 II
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
pathFind(root,targetSum);
return res;
}
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public void pathFind(TreeNode root, int targetSum){
if(root==null){
return;
}
path.add(root.val);
targetSum -= root.val;
if(targetSum==0 && root.left==null && root.right==null){
res.add(new ArrayList<Integer>(path));
return;
}
if(root.left!=null){
pathFind(root.left,targetSum);
path.remove(path.size()-1);
}
if(root.right!=null){
pathFind(root.right,targetSum);
path.remove(path.size()-1);
}
}