文章目录
二叉树作为一种可以高效查找的数据结构,在计算机科学领域有着广泛的应用,同时他也是考察面试者算法能力的重要题型,笔者将自己的二叉树刷题路径进行汇总,以便更好地复习。
本文参考:
基础知识
二叉树在做题方法上,通常有迭代和递归两种解法,出现的二叉树有经典二叉树、完全二叉树、平衡二叉树等等,在这些的基础上产生很多题型,比如二叉树的遍历、属性相关的题目、二叉树的修改与构造等等。
笔者在二叉树刷题的过程中,建议大家先分清楚本题目的遍历顺序,可以参考我在遍历中总结的“二叉树递归遍历与处理结果总结”,然后再去思考更细节的东西。
思考方式
灵神对二叉树中的递归做法进行了解读,参考视频,本质上都是分解和原问题相似的子问题,从而可以复用代码,同时要将目光尽可能放在当层中,完全信任别的层数的递归函数,解题有两种思路:
- 找到边界情况,从下往上返回递归函数的结果,跟节点就是最终的结果
- 定义全局变量,从上往下进行局部变量的传递,更新全局变量
二叉树的层次遍历
就是广度优先遍历,层次遍历与搜索层次有关的广度优先搜索模板一致,参考我的另一篇文章算法学习-广度优先搜索,其中que.offer(root)
默认root
存在且不为null
,因此当根结点为null的时候,需要先进行if(root==null) return
特判。
Deque<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int size=que.size();
for(int i=0;i<size;i++){
TreeNode top=que.poll();
if(top.left!=null) que.offer(top.left);
if(top.right!=null) que.offer(top.right);
}
}
二叉树的修改与构造
构造树一般采用的是递归版的前序遍历,因为先构造中间节点,然后递归构造左子树和右子树,递归base case为该区间没法构造节点。
模板如654.最大二叉树:
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return buildHelper(nums,0,nums.length-1);
}
public TreeNode buildHelper(int[]nums,int left,int right){
//该区间无法构造节点
if(left>right){
return null;
}
int maxV=-1;
int index=0;
for(int i=left;i<=right;i++){
if(nums[i]>maxV){
maxV=nums[i];
index=i;
}
}
//构造中间节点
TreeNode root=new TreeNode(maxV);
//递归构造左右子树
root.left=buildHelper(nums,left,index-1);
root.right=buildHelper(nums,index+1,right);
return root;
}
}
相关题目
二叉树的遍历
二叉树的遍历不仅仅是输出遍历次序那么简单,许多题目其实就是在遍历的基础上做出来的,因此很有必要把遍历掌握清楚了。
笔者也曾在遍历的定义区分上有所纠结,由于我们在递归的过程中,压入递归栈的时候,不可避免地都是将程序从上往下执行的,但至于处理最后的结果,却不一定按照递归遍历的顺序进行,否则不全是前序遍历了,因此,我更愿意将结果处理与左右递归的相对位置,作为区分遍历的标准,可以参考我下面的总结->二叉树递归遍历与结果处理总结。
前序遍历
144.二叉树的前序遍历
递归做法很好写
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<Integer> ans=new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
preDfs(root);
return ans;
}
public void preDfs(TreeNode root){
if(root==null) return;
ans.add(root.val);
preDfs(root.left);
preDfs(root.right);
}
}
非递归做法采用栈的形式,在弹栈的时候记录结果,因此压栈的时候需要反向压栈,从上向下遍历的过程中,根节点可以直接放入结果集,然后按照“右左”的顺序压栈,这里的左其实也是下一层的根。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
Stack<TreeNode> st=new Stack<>();
if(root==null) return res;
st.push(root);
while(!st.empty()){
TreeNode top=st.pop();
res.add(top.val);
if(top.right!=null) st.push(top.right);
if(top.left!=null) st.push(top.left);
}
return res;
}
}
上面非递归的做法是传统的思路,还有一种标记法,是将「访问的元素」全部压入栈中,但是对「未加入结果集的元素」后面再放一个NULL,用于标记其未访问。弹栈的话只要栈非空每次都要弹栈,如果弹出元素非空,看情况继续将“右左中”入栈,不过这一步也是能从上往下继续访问下去的必要操作,如果弹出元素为空,就开始记录下一个元素为结果。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
Stack<TreeNode> st=new Stack<>();
if(root==null) return res;
st.push(root);
while(!st.isEmpty()){
TreeNode top=st.pop();
if(top!=null){
if(top.right!=null) st.push(top.right);
if(top.left!=null) st.push(top.left);
st.push(top);
st.push(null);
}else{
TreeNode top2=st.pop();
res.add(top2.val);
}
}
return res;
}
}
655.输出二叉树
这题采用了两次递归版dfs,第一次求出树的高度,构造出答案list以后,第二次递归地前序遍历在对应位置插入数值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<List<String>> ans;
int height;
public List<List<String>> printTree(TreeNode root) {
height=dfs1(root)-1;
ans=new ArrayList<>();
int m=height+1;
int n=(1<<(height+1))-1;
for(int i=0;i<m;i++){
List<String> temp=new ArrayList<>();
for(int j=0;j<n;j++){
temp.add("");
}
ans.add(temp);
}
dfs2(root,0,(n-1)/2);
return ans;
}
//统计高度,根节点为1
public int dfs1(TreeNode root){
if(root==null) return 0;
return 1+Math.max(dfs1(root.left),dfs1(root.right));
}
//填入数字
public void dfs2(TreeNode root,int r,int c){
if(root==null) return;
ans.get(r).set(c,String.valueOf(root.val));
dfs2(root.left,r+1,c-(1<<(height-r-1)));
dfs2(root.right,r+1,c+(1<<(height-r-1)));
}
}
前序遍历DFS+全局哈希表map记录,遍历过程中用map.putIfAbsent存储每一层的最左边第一个节点值,也即该层的最小值,并在遍历树的过程中当成完全二叉树不断标号、不断更新当前节点与当前最小值的差值。其处理结果在左右递归以前。参考爪哇缪斯的图解。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res=0;
HashMap<Integer,Integer> map=new HashMap<>();
public int widthOfBinaryTree(TreeNode root) {
dfs(root,1,0);
return res;
}
//递归函数表示当前节点root,其编号为index,所在的层数
public void dfs(TreeNode root,int index,int level){
if(root==null) return;
// 先序遍历,处理结果
map.putIfAbsent(level,index);
res=Math.max(res,index-map.get(level)+1);
dfs(root.left,2*index,level+1);
dfs(root.right,2*index+1,level+1);
}
}
6182.反转二叉树的奇数层
轴对称相关递归。从上到下先序遍历,交换奇数层节点的值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode reverseOddLevels(TreeNode root) {
if(root==null) return null;
dfs(root.left,root.right,2);
return root;
}
public void dfs(TreeNode left,TreeNode right,int level){
if(left==null) return ;
if(level%2==0){
int temp=right.val;
right.val=left.val;
left.val=temp;
}
dfs(left.left,right.right,level+1);
dfs(left.right,right.left,level+1);
}
}
轴对称相关递归,后序遍历,等左右递归返回结果,才一并返回本层的结果。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return dfs(root.left,root.right);
}
public boolean dfs(TreeNode left,TreeNode right){
if(left==null&&right==null) return true;
if(left==null||right==null) return false;
if(left.val!=right.val) return false;
return dfs(left.left,right.right)&&dfs(left.right,right.left);
}
}
逻辑类似于
class Solution {
public boolean isSymmetric(TreeNode root) {
return dfs(root.left,root.right);
}
public boolean dfs(TreeNode left,TreeNode right){
if(left==null&&right==null) return true;
if(left==null||right==null) return false;
if(left.val!=right.val) return false;
boolean l=dfs(left.left,right.right);
boolean r=dfs(left.right,right.left);
boolean res=l&&r;
return res;
}
}
中序遍历
94.二叉树的中序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<Integer> ans=new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
midDfs(root);
return ans;
}
public void midDfs(TreeNode root){
if(root==null) return;
midDfs(root.left);
ans.add(root.val);
midDfs(root.right);
}
}
非递归做法1:
从上向下遍历的过程中,用栈处理元素,用指针帮助访问节点,因为树只能从上往下遍历下来,然而又是中序遍历,并不能直接输出,需要先用栈记录下来,到时再等cur==null
的时候进行输出,用指针跳转。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
// 在弹栈的时候记录结果
List<Integer> res=new ArrayList<>();
Stack<TreeNode> st=new Stack<>();
if(root==null) return res;
TreeNode cur=root;
while(!st.isEmpty()||cur!=null){
// 并不需要每次都弹栈,在cur==null的时候才开始弹栈
// 一直从上往下遍历到最左侧节点
if (cur!=null){
st.push(cur);
cur=cur.left;
}else{
// 开始弹栈记录结果
TreeNode top=st.pop();
res.add(top.val);
cur=top.right;
}
}
return res;
}
}
非递归做法2: 仍然是标记法
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res=new ArrayList<>();
if(root==null) return res;
Stack<TreeNode>st=new Stack<>();
st.push(root);
while(!st.isEmpty()){
// 从上向下遍历的必要套路
TreeNode top=st.pop();
if(top!=null){
if(top.right!=null) st.push(top.right);
st.push(top);
st.push(null);
if(top.left!=null) st.push(top.left);
}else{
TreeNode top1=st.pop();
res.add(top1.val);
}
}
return res;
}
}
36.二叉搜索树与双向链表
关键点在于中序遍历的过程中,每次处理的都是root,且是按递增顺序在递归中被访问。直到最底层才开始能够取到root节点处理。
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
// 用于记录每个节点的前驱
Node pre=null;
// 用于记录循环链表的表头
Node head=null;
public Node treeToDoublyList(Node root) {
if(root==null) return null;
dfs(root);
// 循环链表的头尾连接
head.left=pre;
pre.right=head;
return head;
}
// 中序遍历
public void dfs(Node root){
if(root==null) return;
dfs(root.left);
// 进行节点的转移
if(pre==null){
head=root;
}else{
// 节点连接
pre.right=root;
root.left=pre;
}
pre=root;
dfs(root.right);
}
}
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val,Node _left,Node _right) {
val = _val;
left = _left;
right = _right;
}
};
*/
class Solution {
Node pre=null;
Node head=null;
public Node treeToDoublyList(Node root) {
if(root==null) return null;
dfs(root);
head.left=pre;
pre.right=head;
return head;
}
public void dfs(Node root){
if(root==null) return;
dfs(root.left);
if(pre==null){
head=root;
}else{
pre.right=root;
root.left=pre;
}
pre=root;
dfs(root.right);
}
}
后序遍历
145.二叉树的后序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<Integer> ans=new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
postDfs(root);
return ans;
}
public void postDfs(TreeNode root){
if(root==null) return;
postDfs(root.left);
postDfs(root.right);
ans.add(root.val);
}
}
非递归写法:标记法
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
Stack<TreeNode> st=new Stack<>();
ArrayList<Integer> ans=new ArrayList<>();
if(root==null) return ans;
st.push(root);
while(!st.isEmpty()){
TreeNode top=st.pop(); // 检查弹出来元素是否为null
if(top!=null){ // 继续遍历
st.push(top); // 将它再次放入栈从而可以往下走
st.push(null);
if(top.right!=null) st.push(top.right);
if(top.left!=null) st.push(top.left); // 总是左边的先弹栈从而可以往下遍历
}else{ // 只有先弹出null,才说明到了栈能够输出顶点的时候
TreeNode top2=st.pop();
ans.add(top2.val);
}
}
return ans;
}
}
47.二叉树剪枝
后序遍历,从底向上进行二叉树剪枝。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode pruneTree(TreeNode root) {
if(root==null) return null;
root.left=pruneTree(root.left);
root.right=pruneTree(root.right);
if(root.val==0&&root.left==null&&root.right==null) return null;
return root;
}
}
美团20220903T3.字母树
给定一棵有n个节点的树,节点用1,2,…n编号。1号节点为树的根节点,每个节点上有一个用大写字母表示的标记。求每个
节点的子树中出现的字母标记种类数。
注:子树的定义:设T是有根树,a是T中的一个顶点,由a以及a的所有后裔(后代)导出的子图称为有根树T的子树。
输入描述
第一行输入一个正整数n,表示树的节点数量。
第二行输入n-1个正整数,第i个整数表示第i+1号节点的父亲节点。
第三行输入长度为n的由大写字母组成的字符串s1s2s3...sn,第i个字符si表示第i号节点的标记。
3≤n≤50000.
数据保证形成一棵合法的树,字符串由大写字母组成。
输出描述
输出n个整数,相邻两个数之间用空格隔开,第i个整数表示第i号节点的子树中出现不同的字母种类数。
input:
6
1 2 2 1 4
ABCCAD
output:
4 3 1 2 1 1
后序遍历DFS+全局数组ans记录,要计算每个节点及其子树出现的字母种类数,无关种类数量,其实就可以用位表示来记录每个节点出现的字母种类,假设每个节点有一个26位的整数,某一位为0表示某个字母没出现过,某一位为1表明某字母出现过。比如10000000…0,表示只有一个A。
首先要将输入转换为图的形式存储起来(一个节点及其后续节点的对应关系),答案通过全局变量记录每个节点的状态,采用后序遍历的方式,用v先保存根的字母种类,然后将子树的状态更新到根节点root上面,处理结果在左右递归以后,最后判断root节点用位表示的状态,记录到全局变量中,同时递归函数返回该节点的字母种类状态位表示。这里我们可以在构造的图中,将合法的点都遍历完直接退出,没有null这样的base情况需要判断。
n = int(input())
nodelist = list(map(int,input().split()))
s = input()
ans=[0]*n
# 存树,节点序号从0开始,存储每个节点的子节点
g =[[]for _ in range(n)]
for i,v in enumerate(nodelist,start=1):
# 给的序号从1开始,第i个整数值表示第i+1号节点(从0开始为i号)的父亲节点
g[v-1].append(i)
# 以当前node节点为根的子树的字母种类数
def dfs(node):
# 后序遍历,遍历下来先用到了v
v = (1<<(ord(s[node])-ord('A')))
for nxt in g[node]:
v |= dfs(nxt)
# 递归后处理结果
ans[node] = bin(v)[2:].count('1')
return v
dfs(0)
print(*ans)
后序遍历DFS+全局哈希表map记录序列化,从上到下遍历到底,然后从下往上将子树的序列化更新到根结点上,后序遍历处理结果在左右递归以后。
在整个遍历的过程中,通过HashMap判断该序列化的子树是否出现过,当且仅当前面出现次数为1时加入到res中,避免重复记录。
序列化方式需要特别注意能够唯一区别一棵树的结构(空为"_“)和值(”,"分割)。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
Map<String,Integer> mp;
List<TreeNode> res;
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
mp=new HashMap<>();
res=new ArrayList<>();
dfs(root);
return res;
}
public String dfs(TreeNode root){
if(root==null) return "_";
// 后序遍历
String l=dfs(root.left);
String r=dfs(root.right);
// 序列化方式需要注意,既不能中序构建,也需要加","
String cur=l+","+r+","+root.val;
// 仅记录一次
if(mp.containsKey(cur)&&mp.get(cur)==1){
res.add(root);
}
mp.put(cur,mp.getOrDefault(cur,0)+1);
return cur;
}
}
1519.子树中标签相同的节点数
后序遍历DFS(虽然在递归以前标记了vis[node])+全局数组ans记录答案,从上到下递归遍历下来,处理结果是在递归弹栈将子树标签节点返回给根结点以后,自底向上处理的。
这里题目有歧义,树是有向无环图,而题干说“连通的无环无向图”,样例中给出的4
[[0,2],[0,3],[1,2]] “aeed” 都不是一棵树,返回结果[1,1,2,1],但是题目又说一定是把0当树根,感性理解来看是从0(a)开始往下走,2(e)->1(e),到1(e)开始从叶子反弹上来,和题目中的[1,2]1->2是矛盾的。于是我们要把边当成无向的输入,但是为了从0开始,一直dfs触底反弹,不陷入1(e)又开始访问2(e)的死循环,又得从上向下进行访问标记vis[node]=true
。
Java建图解法如下:
class Solution {
String labels;
ArrayList<Integer>[] g;
int n;
int[] ans;
boolean[] vis;
public int[] countSubTrees(int _n, int[][] edges, String _labels) {
n=_n;
labels=_labels;
ans=new int[n];
g=new ArrayList[n];
vis=new boolean[n];
Arrays.setAll(g,e->new ArrayList<>());
for(int[]e:edges){
g[e[0]].add(e[1]);
g[e[1]].add(e[0]);
}
dfs(0);
return ans;
}
//返回以node为根的树,每种字母出现的次数
public int[] dfs(int node){
//进入dfs的时候一定没有访问过,因此不需要base情况直接返回
int[]res=new int[26];
vis[node]=true;
for(int son:g[node]){
if(!vis[son]){
//一定没访问过的son
int[] nums= dfs(son);
for(int k=0;k<26;k++){
res[k]+=nums[k];
}
}
}
//增加该字母出现的次数
char c=labels.charAt(node);
//自底向上更新答案,记录每棵子树出现的字母次数
ans[node]=++res[c-'a'];
return res;
}
}
Python建图解法如下:
class Solution:
def countSubTrees(self, n: int, edges: List[List[int]], labels: str) -> List[int]:
g= defaultdict(list)
for i,j in edges:
g[i].append(j)
g[j].append(i)
ans=[0]*n
vis=[0]*n
def dfs(node):
vis[node]=1
res=[0]*26
for nxt in g[node]:
if not vis[nxt]:
for i,j in enumerate(dfs(nxt)):
res[i]+=j
res[ord(labels[node])-ord('a')]+=1
ans[node]=res[ord(labels[node])-ord('a')]
return res
dfs(0)
return ans
236.二叉树的最近公共祖先
以下两题都是参考灵神的讲解,刷新对于递归的理解,关键是递归函数在本层怎么巧妙地利用别的层数的结果,所以是后序遍历的模板。
其中返回值的意义在碰到一个节点p或q返回当前节点,以及左右子树都有时返回当前节点看起来意义不同,其实它们都是返回从当前节点出发「可能」是最近公共祖先的节点,最终第一层递归返回了就是结果。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return dfs(root,p,q);
}
// 后序遍历,返回从当前节点出发「可能」是最近公共祖先的节点,然后进行处理
public TreeNode dfs(TreeNode root, TreeNode p, TreeNode q){
if(root==null||root==p||root==q) return root; // 本层直接返回
// 递归左右子树
TreeNode l=dfs(root.left,p,q);
TreeNode r=dfs(root.right,p,q);
// 左右子树返回结果都不为null
if(l!=null&&r!=null) return root;
// 左不为null,返回左边
else if(l!=null) return l;
// 左为null,右不为null或者左右同时为null,则返回右边
else return r;
}
}
235.二叉搜索树的最近公共祖先
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
return dfs(root,p,q);
}
public TreeNode dfs(TreeNode root, TreeNode p, TreeNode q){
int x=root.val;
if(p.val<x&&q.val<x) return dfs(root.left,p,q);
if(p.val>x&&q.val>x) return dfs(root.right,p,q);
return root;
}
}
层次遍历
662.二叉树最大的宽度
层次遍历,当成虚拟的完全二叉树的编号来做,每层只需要算出最右侧编号-最左侧编号+1
。参考爪哇缪斯的图解。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int widthOfBinaryTree(TreeNode root) {
ArrayDeque<TreeNode> que=new ArrayDeque<>();
int ans=0;
que.offer(new TreeNode(1,root.left,root.right));
while(!que.isEmpty()){
int size=que.size();
int start=-1,end=-1;
for(int i=0;i<size;i++){
TreeNode top=que.poll();
end=top.val;
if(start==-1) start=top.val;
if(top.left!=null) que.offer(new TreeNode(2*top.val,top.left.left,top.left.right));
if(top.right!=null) que.offer(new TreeNode(2*top.val+1,top.right.left,top.right.right));
}
ans=Math.max(ans,end-start+1);
}
return ans;
}
}
2415.反转二叉树的奇数层
和层数有关的广度优先遍历。当时觉得层序遍历是逐个节点加入队列的,没法去处理完整一层的情况,其实只需要在加入以后的下一层去处理就行了。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode reverseOddLevels(TreeNode root) {
int level=1;
if(root==null) return null;
Deque<TreeNode> que=new ArrayDeque<>();
que.offer(root);
while(!que.isEmpty()){
int size=que.size();
int[]arr=que.stream().mapToInt(x->x.val).toArray();
for(int i=0;i<size;i++){
TreeNode top=que.poll();
//偶数层进行首尾交换
if(level%2==0){
top.val=arr[size-1-i];
}
if(top.left!=null) que.offer(top.left);
if(top.right!=null) que.offer(top.right);
}
level++;
}
return root;
}
}
二叉树递归遍历与结果处理总结
为了分清楚不同的递归遍历,就需要分清楚递归函数单步逻辑中的遍历和处理结果,我们将处理结果在左右递归以前的称为“前序遍历”,将处理结果在左右递归以后的称为“后序遍历”。遍历都是先序的,因为给一棵树,计算机只能从根开始不断往下走,关键是看我们要得到的结果的处理位置在哪里。
如20220903字母树、652.寻找重复的子树是后序遍历,从上到下遍历下来但是并未处理结果,虽然字母树在往下遍历的过程中先用到了根结点v的字母种类,652先用到了根结点的值,但是处理结果都是在先序遍历的左右节点递归以后,遍历顺序如箭头所示,处理结果次序如圆圈框处序号所示,从上往下入栈,递归触底弹栈,也即从下往上完成了子树的结果处理。定义后序遍历的递归函数通常都有返回值。
662.二叉树最大的宽度是先序遍历,从上向下遍历并处理结果下来,处理结果在左右递归以前。通常定义的递归函数无返回值。
还有一种和对称性有关的问题,常常需要将一棵树按照轴对称比较,这里的遍历也可以看作后序遍历,需要左右两边的递归函数返回结果,本层才能返回结果。并且递归函数的书写参数需要有左右两部分。原先对称的两个节点,其递归下去也需要控制对称。如101.对称二叉树
二叉树的修改与构造
654.最大二叉树
构造模板题
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return buildHelper(nums,0,nums.length-1);
}
public TreeNode buildHelper(int[]nums,int left,int right){
if(left>right){
return null;
}
int maxV=-1;
int index=0;
for(int i=left;i<=right;i++){
if(nums[i]>maxV){
maxV=nums[i];
index=i;
}
}
TreeNode root=new TreeNode(maxV);
root.left=buildHelper(nums,left,index-1);
root.right=buildHelper(nums,index+1,right);
return root;
}
}
998.最大二叉树II
为了让插入值val在序列最后,就是要将val尽可能放在右子树或者根节点上,即如果val比当前值大,就让当前的树作为val的左孩子;否则继续向右子树搜索插入位置。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode insertIntoMaxTree(TreeNode root, int val) {
if(root==null) return new TreeNode(val);
if(root.val<val){
TreeNode newNode=new TreeNode(val);
newNode.left=root;
return newNode;
}else{
root.right=insertIntoMaxTree(root.right,val);
}
return root;
}
}
43.往完全二叉树添加节点
采用和搜索层数无关的广度优先搜索,找到某个节点的左节点或者右节点为空,则将新节点插入到其左节点或者右节点上。后面的查询可以从该点开始查,不需要每次重新入队,将前面已经出队列的点再入队一次,因此可以将该点放入队头。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class CBTInserter {
TreeNode root;
ArrayDeque<TreeNode> que;
public CBTInserter(TreeNode _root) {
root=_root;
que=new ArrayDeque<>();
}
public int insert(int v) {
TreeNode newNode=new TreeNode(v);
que.offer(root);
while(!que.isEmpty()){
TreeNode top=que.poll();
if(top.left==null){
top.left=newNode;
que.offerFirst(top);
}else if(top.right==null){
top.right=newNode;
que.offerFirst(top);
}else{
que.offer(top.left);
que.offer(top.right);
continue;
}
return top.val;
}
return -1;
}
public TreeNode get_root() {
return root;
}
}
/**
* Your CBTInserter object will be instantiated and called as such:
* CBTInserter obj = new CBTInserter(root);
* int param_1 = obj.insert(v);
* TreeNode param_2 = obj.get_root();
*/
47.二叉树剪枝
后序遍历,从底向上进行二叉树剪枝。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode pruneTree(TreeNode root) {
if(root==null) return null;
root.left=pruneTree(root.left);
root.right=pruneTree(root.right);
if(root.val==0&&root.left==null&&root.right==null) return null;
return root;
}
}
二叉树的属性
44.二叉树每层的最大值
经典层序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> largestValues(TreeNode root) {
ArrayList<Integer> ans=new ArrayList<>();
if(root==null) return ans;
Deque<TreeNode> que=new LinkedList<>();
que.offer(root);
while(!que.isEmpty()){
int size=que.size();
int maxV=Integer.MIN_VALUE;
for(int i=0;i<size;i++){
TreeNode top=que.poll();
maxV=Math.max(maxV,top.val);
if(top.left!=null) que.offer(top.left);
if(top.right!=null) que.offer(top.right);
}
ans.add(maxV);
}
return ans;
}
}
45.二叉树最底层最左边的值
同样是层次遍历,记录最后一层最左边的数字,只需要用一个局部变量ans每次都更新为最左边的数字,最后就是最后一层最左边的数字。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int findBottomLeftValue(TreeNode root) {
Deque<TreeNode> que=new ArrayDeque<>();
que.offer(root);
int ans=-1;
while(!que.isEmpty()){
int size=que.size();
for(int i=0;i<size;i++){
TreeNode top=que.poll();
if(i==0) ans=top.val;
if(top.left!=null) que.offer(top.left);
if(top.right!=null) que.offer(top.right);
}
}
return ans;
}
}
46.二叉树的右侧视图
经典层次遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> rightSideView(TreeNode root) {
Deque<TreeNode> que=new ArrayDeque<>();
ArrayList<Integer> ans=new ArrayList<>();
if(root==null) return ans;
que.offer(root);
while(!que.isEmpty()){
int size=que.size();
for(int i=0;i<size;i++){
TreeNode top=que.poll();
if(i==size-1) ans.add(top.val);
if(top.left!=null) que.offer(top.left);
if(top.right!=null) que.offer(top.right);
}
}
return ans;
}
}
二叉树的路径问题
「二叉树的路径问题」是一类重要的题型,参考一篇文章解决所有二叉树路径问题(问题分析+分类模板+题目剖析),我们也进行二叉树路径问题的总结。
二叉树路径问题其分为了两类:
- (前序遍历)自顶向下:从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束。继续细分的话还可以分成一般路径与给定和的路径。
- (后序遍历)非自顶向下:从树中任意节点到任意节点的路径,不需要自顶向下,找的结果比较“曲折”,类似树中找直径。
一、前序遍历(自顶向下)模板
一般就采用DFS回溯,单层逻辑上采用先序遍历,查找所有节点。如果是找路径和等于给定target的路径的,只需要在递归函数中增加一个变量sum,当其符合目标的时候记录结果就可以。
其中会碰到不是从根结点出发,也不要求叶子节点结束的题目,就需要运用双重递归,在从上而下遍历递归搜索二叉树的过程中,用另一个递归函数进行给定和路径的处理,整体时间复杂度为O(N^2)
,N
为总节点数,如437.路径总和3。
一般路径:
显式的回溯做法:
class Solution {
LinkedList<Integer> path=new LinkedList<>();
List<String> res=new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
dfs(root);
return res;
}
//从root节点开始往下找路径(还未包括root)
public void dfs(TreeNode root){
//前序遍历
//上来就压入路径,非空判断做在下面
path.add(root.val);
//进行结果的返回
if(root.left==null&&root.right==null){
StringBuilder sb=new StringBuilder();
for(int i=0;i<path.size();i++){
if(i!=path.size()-1) sb.append(path.get(i)+"->");
else sb.append(path.get(i));
}
res.add(sb.toString());
}
//非空判断,继续递归,非空更好做回溯
if(root.left!=null){
dfs(root.left);
path.removeLast();
}
//非空判断,继续递归,非空更好做回溯
if(root.right!=null){
dfs(root.right);
path.removeLast();
}
}
}
将回溯隐藏在函数递归调用中,传入形参,每次递归函数返回就复原了
class Solution {
//存储结果的全局变量
List<String> res;
public List<String> binaryTreePaths(TreeNode root) {
res=new ArrayList<>();
dfs(root,"");
return res;
}
public void dfs(TreeNode root,String path){
if(root==null) return;
//收集结果
if(root.left==null&&root.right==null){
res.add(path+root.val);
return;
}
//前序遍历
path+=root.val;
//继续递归
//本质上也是一种回溯了,下一层弹出来还是path不变,如果把path当成全局变量就是经典回溯
dfs(root.left,path+"->");
dfs(root.right,path+"->");
}
}
给定和路径,常用回溯做法来做,path作为全局变量,最终需要回溯
class Solution {
List<List<Integer>> res;
LinkedList<Integer> path;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
res=new ArrayList<>();
path=new LinkedList<>();
if(root==null) return res;
dfs(root,targetSum);
return res;
}
//从root节点开始往下找和为leftsum的路径(还未包括root.val)
public void dfs(TreeNode root, int leftsum){
//前序遍历
//上来就压入路径,更新总和,非空判断做在下面
leftsum-=root.val;
path.add(root.val);
//进行结果的返回,注意要将path进行深复制
if(root.left==null&&root.right==null&&leftsum==0){
res.add(new ArrayList<>(path));
return;
}
//非空判断,继续递归,非空更好做回溯
if(root.left!=null){
dfs(root.left,leftsum);
path.removeLast();
}
//非空判断,继续递归,非空更好做回溯
if(root.right!=null){
dfs(root.right,leftsum);
path.removeLast();
}
}
}
其中需要斟酌的是递归的返回条件和进入递归的条件:
// 空节点可以进入递归
void dfs(TreeNode root){
if (root==null) return;
dfs(root.left);
dfs(root.right);
}
// 非空节点进入递归
void dfs(TreeNode root, int target){
target=target-root.val;
// 在某种情况下返回
if (root.left==null&&root.right==null){
return;
}
if(root.left!=null) dfs(root.left,target);
if(root.right!=null) dfs(root.right,target);
}
相关题目:
257.二叉树的所有路径
显式回溯,每次递归后path弹出
class Solution {
LinkedList<Integer> path=new LinkedList<>();
List<String> res=new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
dfs(root);
return res;
}
public void dfs(TreeNode root){
//前序遍历
//上来就压入路径,非空判断做在下面
path.add(root.val);
//进行结果的返回
if(root.left==null&&root.right==null){
StringBuilder sb=new StringBuilder();
for(int i=0;i<path.size();i++){
if(i!=path.size()-1) sb.append(path.get(i)+"->");
else sb.append(path.get(i));
}
res.add(sb.toString());
}
//非空判断,继续递归回溯
if(root.left!=null){
dfs(root.left);
path.removeLast();
}
//非空判断,继续递归回溯
if(root.right!=null){
dfs(root.right);
path.removeLast();
}
}
}
或者将回溯隐藏在递归调用中,
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<String> ans;
public List<String> binaryTreePaths(TreeNode root) {
ans=new ArrayList<>();
dfs(root,"");
return ans;
}
public void dfs(TreeNode root,String cur){
if(root==null) return;
if(root.left==null&&root.right==null){
ans.add(cur+root.val);
return;
}
dfs(root.left,cur+root.val+"->"); // 自己管自己这一层
dfs(root.right,cur+root.val+"->");
}
}
988.从叶结点开始的最小字符串
找到二叉树中所有路径的字符串,该字符串需要从底向上构造,对字符串排序选出最小的字符串即可。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
StringBuilder sb=new StringBuilder();
String ans=null;
public String smallestFromLeaf(TreeNode root) {
dfs(root);
return ans;
}
public void dfs(TreeNode root){
if(root==null) return;
if(root.left==null&&root.right==null){
sb.append((char)('a'+root.val));
String s=sb.reverse().toString();
if(ans==null){
ans=s;
}else if(ans.compareTo(s)>0){
ans=s;
}
sb.reverse();
sb.deleteCharAt(sb.length()-1);
return;
}
sb.append((char)('a'+root.val));
dfs(root.left);
sb.deleteCharAt(sb.length()-1);
sb.append((char)('a'+root.val));
dfs(root.right);
sb.deleteCharAt(sb.length()-1);
}
}
112.路径总和
这里不需要记录所有路径,只需要判断是否存在路径,因此直接运用提供的递归函数进行递归就可以,并且由于是targetSum基本数据类型,可以采用递归函数中带targetSum参数的回溯。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
//没找到满足条件的叶子才会进入这一步
if(root==null) return false;
//如果是满足条件的叶子节点早就返回了
if(root.left==null&&root.right==null&&targetSum==root.val) return true;
//否则继续向下搜索
int leftsum=targetSum-root.val;
return hasPathSum(root.left,leftsum)||hasPathSum(root.right,leftsum);
}
}
或者只是利用递归遍历,条件是否满足当作全局变量
class Solution {
boolean flag=false;
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root==null) return false;
dfs(root,targetSum,root.val);
return flag;
}
public void dfs(TreeNode root,int targetSum,int curSum){
if(root.left==null&&root.right==null){
if(curSum==targetSum) flag=true;
return;
}
if(root.left!=null)dfs(root.left,targetSum,curSum+root.left.val);
if(root.right!=null)dfs(root.right,targetSum,curSum+root.right.val);
}
}
113.路径总和2
这题查找从根结点开始到叶子节点符合和的路径。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
List<List<Integer>> res;
LinkedList<Integer> path;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
res=new ArrayList<>();
path=new LinkedList<>();
if(root==null) return res;
dfs(root,targetSum);
return res;
}
//从root节点开始往下找和为leftsum的路径(还未包括root.val)
public void dfs(TreeNode root, int leftsum){
//前序遍历
//上来就压入路径,更新总和
leftsum-=root.val;
path.add(root.val);
//进行结果的返回,注意要将path进行深复制
if(root.left==null&&root.right==null&&leftsum==0){
res.add(new ArrayList<>(path));
//和后面的双重递归区分
return;
}
//进入前判空
if(root.left!=null){
dfs(root.left,leftsum);
path.removeLast();
}
//进入前判空
if(root.right!=null){
dfs(root.right,leftsum);
path.removeLast();
}
}
}
437.路径总和3
这题不需要从根结点开始,也不需要在叶子节点结束,但是也不同于后面的后序遍历,本题的路径方向需要从上往下。采用双重递归,外面一层是先序遍历二叉树,里面一层是从该节点开始,从上往下先序遍历查找给定和的路径。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int count=0;
public int pathSum(TreeNode root, int targetSum) {
dfs(root,targetSum);
return count;
}
//查找从root开始(不包括root),和为targetSum的路径
public void dfs(TreeNode root,int targetSum){
if(root==null) return;
//双重递归,始终找的是和为targetSum的路径
dfs2(root,targetSum);
dfs(root.left,targetSum);
dfs(root.right,targetSum);
}
//查找从root开始(不包括root),和为sum的路径
public void dfs2(TreeNode root, long sum){
sum -= root.val;
//不能return,因为不要求到叶子节点,因此在某个点下面可能继续找到和为sum的
if(sum==0){
count++;
}
if(root.left!=null)dfs2(root.left,sum);
if(root.right!=null)dfs2(root.right,sum);
}
}
二、后序遍历(非自顶向下)模板
需要设置一个全局变量res
,在任何调用的递归函数dfs中都可以更新,以此达到「以任意节点为根的结果」的效果。单层逻辑上一般考虑后序遍历,在递归出left
、right
的结果后,才结合当前根节点进行结果处理,同时更新全局变量res
,最后递归函数返回的却是只考虑单侧和根结点的结果。
class Solution {
//全局变量的定义,边数问题通常为0,和问题通常为大的负数
int res=0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return res;
}
//定义递归函数,以root为根节点,单侧的最大路径长度或者和
public int dfs(TreeNode root){
//后序遍历
if(root==null) return 0;
int l=dfs(root.left);
int r=dfs(root.right);
//和边数有关的时候,单层逻辑上尤其需要注意left或者right为空的情况,直接为0
if(root.left!=null) l++;
else l=0;
if(root.right!=null) r++;
else r=0;
//update res
res=Math.max(res,l+r);
//返回单侧的结果
return Math.max(l,r);
}
}
相关题目:
687.最长同值路径
这里采用全局变量res
记录任意节点为根的同值路径。同时采用后序遍历,在单步逻辑中看的是「当前节点root
和其左右节点」的相等关系,根据左右节点和根节点的数量关系,更新的是res
的最大值,但是递归函数返回的却是以该节点为起点向左或者向右走的单侧的最长路径。后序遍历的板子能保证看到所有结果。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res=0;
public int longestUnivaluePath(TreeNode root) {
if(root==null) return 0;
dfs(root);
return res;
}
//以root为根节点,单侧的最长路径长度
public int dfs(TreeNode root){
if(root==null) return 0;
int l=dfs(root.left);
int r=dfs(root.right);
//逻辑处理,root和左右节点的数量关系,对res以及return结果的贡献
//左右节点为空或者同时为空的情况也要考虑
if(root.left!=null&&root.val==root.left.val)l++;
else l=0;
if(root.right!=null&&root.val==root.right.val) r++;
else r=0;
//同时不相等、同时相等、一边相等的逻辑最长路径长度都可以化为l+r
res=Math.max(res,l+r);
return Math.max(l,r);
}
}
124.二叉树中的最大路径和
非自顶向下的模板,从底向上计算每个节点的贡献,在处理左右递归时,需要进行负数忽略的处理(这样子就不一定从叶子节点开始了,因为叶子节点贡献为负的话,就被忽略了),然后更新res,return结果也就是左右递归的最大值再加上root.val。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int res=-0x3f3f3f3f;
public int maxPathSum(TreeNode root) {
dfs(root);
return res;
}
//以root为根节点,单侧的最大路径和
public int dfs(TreeNode root){
if(root==null) return 0;
int l=dfs(root.left);
int r=dfs(root.right);
//单层逻辑处理,root和左右节点的数量关系,对res以及return结果的贡献
//如果是负数,则不要贡献给root,直接为0
l=Math.max(l,0);
r=Math.max(r,0);
//update res
res=Math.max(res,l+r+root.val);
return Math.max(l+root.val,r+root.val);
}
}
543.二叉树的直径
非自顶向下模板,在处理边数问题上,单层逻辑需要考虑左右节点为空或者同时为空的情况,因为至少存在两个点才能构成一条边。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 记录直径
int res=0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
// 最后递归返回过程中记录的答案
return res;
}
public int dfs(TreeNode root){
if (root==null) return 0;
int l=dfs(root.left);
int r=dfs(root.right);
//和边数有关的时候,单层逻辑上尤其需要注意left或者right为空的情况,直接为0
if(root.left!=null) l=l+1;
if(root.right!=null) r=r+1;
// 记录直径
res=Math.max(l+r,res);
// 严格返回左右两侧的最长距离
return Math.max(l,r);
}
}
二叉搜索树的修改与构造
669.修剪二叉搜索树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//聚焦于当前节点的值,返回以当前节点修剪能够得到的树
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root==null) return null;
//根结点不在[low,high],平衡树直接剪去一半,希望另一半有符合答案的部分
if(root.val<low) return trimBST(root.right,low,high);
if(root.val>high) return trimBST(root.left,low,high);
//根结点在[low,high]
root.left=trimBST(root.left,low,high);
root.right=trimBST(root.right,low,high);
return root;
}
}
二叉搜索树的属性
52.展平二叉搜索树
中序遍历,通过dummyhead构建树免去了头节点的特殊判断
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
TreeNode temp;
public TreeNode increasingBST(TreeNode root) {
// dummyHead一直不改变,让temp一直向下改变指向
TreeNode dummyHead=new TreeNode(-1);
temp=dummyHead;
dfs(root);
return dummyHead.right;
}
public void dfs(TreeNode root){
if(root==null){
return;
}
dfs(root.left);
// 这里开辟了新空间,所以下面left=null可以删掉,如果直接=root
temp.right=new TreeNode(root.val);
//System.out.println(temp.left);
//temp.left=null; // =root的话这里需要清理root.left
temp=temp.right;
dfs(root.right);
}
}
53.二叉搜索树中的中序后继
- 从根节点 root 开始,比较当前 cur 节点的值和节点 p 的值的大小关系
- 如果当前 cur 节点的值小于或等于节点 p 的值,那么比节点 p 的值大的节点应该在它的右子树
- 如果当前 cur 节点的值大于节点 p 的值,那么当前节点有可能是 p 的下一个节点,还需要判断当前 cur 节点的左节点是否满足以上条件
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
TreeNode res=null;
TreeNode cur=root;
while(cur!=null){
System.out.println(cur.val);
if (cur.val<=p.val){
cur=cur.right;
}else{ // cur.val>p.val cur就有可能是要找的「下一个节点」
res=cur;
cur=cur.left; // 继续往下看是否有满足条件的更小值
}
}
return res;
}
}
54.所有大于等于节点的值之和
巧妙地利用了二叉搜索树的性质,对之前的中序遍历次序进行了调换,先dfs(right)再dfs(left)相当于降序输出二叉搜索树的节点,只需要用一个sum累加结果就可以。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int sum;
public TreeNode convertBST(TreeNode root) {
dfs(root);
return root;
}
public void dfs(TreeNode root){
if (root==null) return;
dfs(root.right);
sum+=root.val;
root.val=sum;
dfs(root.left);
}
}
98.验证二叉搜索树
中序遍历,记录前一个访问的节点,整体访问序列需要为升序,递归做法:
class Solution {
TreeNode pre=null;
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
boolean left=isValidBST(root.left);
if(pre!=null&&root.val<=pre.val){
return false;
}
pre=root;
boolean right=isValidBST(root.right);
return left&&right;
}
}
上面的做法在左子树不满足的时候还要搜寻右子树,下面是左子树不满足直接返回:
class Solution {
TreeNode pre=null;
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
// 直接把递归写在条件里了
if(!isValidBST(root.left)) return false;
//System.out.println(root.val);
if(pre!=null&&root.val<=pre.val){
return false;
}
pre=root;
return isValidBST(root.right);
}
}
非递归做法:
在中序遍历的过程中记录pre节点,如果出现不满足条件的直接return false
class Solution {
TreeNode pre;
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> st=new Stack<>();
TreeNode cur=root;
while(!st.isEmpty()||cur!=null){
while(cur!=null){
st.push(cur);
cur=cur.left;
}
TreeNode top=st.pop();
if(pre!=null&&top.val<=pre.val){
return false;
}
pre=top;
cur=top.right;
}
return true;
}
}
剑指Offer54.二叉搜索树的第k大节点
中序遍历变形+用全局变量idx记录访问到的节点序号
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int res=0;
int idx=0;
public int kthLargest(TreeNode root, int k) {
dfs(root,k);
return res;
}
public void dfs(TreeNode root,int k){
if(root==null) return;
// 找大节点,因此需要先遍历右分支
dfs(root.right,k);
idx++;
if(idx==k) res=root.val;
dfs(root.left,k);
}
}