1 递归
树的高度深度⭐⭐⭐
104最大深度
//dfs
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
111最小路径——最浅深度
方法一:递归DFS
public int minDepth(TreeNode root) {
if (root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
if (left == 0 || right == 0) return left + right + 1;
return Math.min(left, right) + 1;
}
方法二:BFS
int minDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
// root 本身就是一层,depth 初始化为 1
int depth = 1;
while (!q.isEmpty()) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
/* 判断是否到达终点 */
if (cur.left == null && cur.right == null)
return depth;
/* 将 cur 的相邻节点加入队列 */
if (cur.left != null)
q.offer(cur.left);
if (cur.right != null)
q.offer(cur.right);
}
/* 这里增加步数 */
depth++;
}
return depth;
}
110判断平衡树——深度
高度差小于等于一,遍历节点的深度,判断差值
private boolean result = true;
public boolean isBalanced(TreeNode root) {
maxDepth(root);
return result;
}
public int maxDepth(TreeNode root) {
if (root == null) return 0;
int l = maxDepth(root.left);
int r = maxDepth(root.right);
if (Math.abs(l - r) > 1) result = false;
return 1 + Math.max(l, r);
}
543树的路径和——某点的左右子树的深度和
private int max = 0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return max;
}
private int depth(TreeNode root) {
if (root == null) return 0;
int leftDepth = depth(root.left);
int rightDepth = depth(root.right);
max = Math.max(max, leftDepth + rightDepth);
return Math.max(leftDepth, rightDepth) + 1;
}
226翻转树左右子树——自上而下递归
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
TreeNode left = root.left; // 后面的操作会改变 left 指针,因此先保存下来
root.left = invertTree(root.right);
root.right = invertTree(left);
return root;
}
617合并两个树——自上而下构建新结点递归合并
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return null;
if (t1 == null) return t2;
if (t2 == null) return t1;
TreeNode root = new TreeNode(t1.val + t2.val);
root.left = mergeTrees(t1.left, t2.left);
root.right = mergeTrees(t1.right, t2.right);
return root;
}
112 判断路径和==某个数?——从上往下,减去结点值递归往下找
base case
1.未找到:访问到了空结点
2.找到了:.当前结点值正好等于余数,且是叶子结点。
public boolean hasPathSum(TreeNode root, int sum) {
if (root == null) return false;
if (root.left == null && root.right == null && root.val == sum) return true;
return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);
}
437统计路径和==某个数?(不一定以 root 开头)——遍历所有结点的时候都认为是从根结点开始,然后把路径数加到一起
public int pathSum(TreeNode root, int sum) {
if (root == null) return 0;
int ret = pathSumStartWithRoot(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
return ret;
}
private int pathSumStartWithRoot(TreeNode root, int sum) {
if (root == null) return 0;
int ret = 0;
if (root.val == sum) ret++;
ret += pathSumStartWithRoot(root.left, sum - root.val) + pathSumStartWithRoot(root.right, sum - root.val);
return ret;
}
687 相同节点值的最大路径长度——路径上的值判断
class Solution {
private int maxdis =0;
//dfs从底向上寻找
public int longestUnivaluePath(TreeNode root) {
longdistance(root);
return maxdis;
}
// 定义一个函数,从低向上判断相同结点相连的左右结点之和(不包括根节点所以不用减掉一)
//
private int longdistance(TreeNode root){
if(root == null) return 0;
int l = longdistance( root.left);
int r = longdistance(root.right);
int ldep = root.left!=null&&root.left.val==root.val?l+1:0;
int rdep =root.right!=null&&root.right.val==root.val?r+1:0;
//记录最大深度
maxdis=Math.max(maxdis,rdep+ldep);
//返回当前节点的深度,左右两边的最大长度
return Math.max(ldep,rdep);
}
}
337打家劫舍——1.动态规划. 2.间隔遍历
class Solution {
public int rob(TreeNode root) {
//间接dfs 累积之和的最大值
if(root ==null) return 0;
// 记录当前结点加上左右树累积的最大值
int val =root.val;
// 间接左子树累积最大值
if(root.left!=null) val+=rob(root.left.left)+rob(root.left.right);
// 间接右子树累积最大值
if(root.right!=null) val+=rob(root.right.left)+rob(root.right.right);
// 当前结点不累计。记录从下个结点才开始累积的最大值
int val2=rob(root.left)+rob(root.right);
//看看哪个最大,返回当前结点累积的最大值
return Math.max(val,val2);
}
}
671第二小的结点——找左右子树中最小的
public int findSecondMinimumValue(TreeNode root) {
if (root == null) return -1;
if (root.left == null && root.right == null) return -1;
int leftVal = root.left.val;
int rightVal = root.right.val;
if (leftVal == root.val) leftVal = findSecondMinimumValue(root.left);
if (rightVal == root.val) rightVal = findSecondMinimumValue(root.right);
if (leftVal != -1 && rightVal != -1) return Math.min(leftVal, rightVal);
if (leftVal != -1) return leftVal;
return rightVal;
}
同树判断
核心代码
private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
if (t == null && s == null) return true;
if (t == null || s == null) return false;
if (t.val != s.val) return false;
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
变形1:572子树判断——遍历所有结点判断同树
public boolean isSubtree(TreeNode s, TreeNode t) {
if (s == null) return false;
//遍历结点
return isSubtreeWithRoot(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
private boolean isSubtreeWithRoot(TreeNode s, TreeNode t) {
if (t == null && s == null) return true;
if (t == null || s == null) return false;
if (t.val != s.val) return false;
return isSubtreeWithRoot(s.left, t.left) && isSubtreeWithRoot(s.right, t.right);
}
变形2:101 树的对称——递归判断左子树和右子树是否相同
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isSymmetric(root.left, root.right);
}
private boolean isSymmetric(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return true;
if (t1 == null || t2 == null) return false;
if (t1.val != t2.val) return false;
return isSymmetric(t1.left, t2.right) && isSymmetric(t1.right, t2.left);
}
构造二叉树
105前序中序构造二叉树——前序找根,递归划分左右子树
public class ti_105 {
HashMap<Integer,Integer> memo= new HashMap<>();
int[] post;
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<inorder.length;i++){//中序遍历存放位置索引,便于取索引
memo.put(inorder[i],i);
}
post = preorder;
TreeNode root = buildTree(0,inorder.length-1,0,post.length-1);
return root;
}
//递归建树
//前序遍历[preStart,preEnd] 中序遍历[inStart,inEnd]
public TreeNode buildTree(int preStart,int preEnd,int inStart,int inEnd){
if(inStart>inEnd || preStart > preEnd){ //叶子结点
return null;
}
int node =post[preStart]; //前序第一个是根节点
int index = memo.get(node);
TreeNode root =new TreeNode(node);
int leftLen = index-inStart;
root.left = buildTree(preStart+1,preStart+leftLen,inStart,index-1);//前序的去掉前面的
root.right = buildTree(preStart+leftLen+1,preEnd,index+1,inEnd);//
return root;//返回当前结点给上一级的left或者right
}
}
106中序与后序构造二叉树——从后序找根结点,递归划分中序的左右子树
public class ti_106 {
HashMap<Integer,Integer> memo= new HashMap<>();
int[] post;
public TreeNode buildTree(int[] inorder, int[] postorder) {
for(int i=0;i<inorder.length;i++){//中序遍历存放位置索引
memo.put(inorder[i],i);
}
post = postorder;
TreeNode root = buildTree(0,inorder.length-1,0,post.length-1);
return root;
}
//递归建树前序遍历
//中序[inStart,inEnd] 后序[postStart,postEnd]
public TreeNode buildTree(int inStart,int inEnd,int postStart,int postEnd){
if(inStart>inEnd || postStart>postEnd){ //叶子结点
return null;
}
int node = post[postEnd];
int index = memo.get(node);
TreeNode root =new TreeNode(node);
int leftLen = index - inStart;
root.left = buildTree(inStart,index-1,postStart,postStart+leftLen-1);//ri-is-1为左子树长度
root.right = buildTree(index+1,inEnd,postStart+leftLen,postEnd-1);//pe-1去掉最后的根结点
return root;//返回当前结点给上一级的left或者right
}
}
654构造最大二叉树——构建当前结点+递归构建左右结点
public class ti_654 {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums,0,nums.length-1);
}
public TreeNode build(int[] nums,int start,int end){
if(start>end){
return null;
}
int maxValue = Integer.MIN_VALUE;
//可优化,寻找最大值
int index = -1;
for(int i = start; i <= end ;i++){
if(nums[i]>maxValue){
maxValue = nums[i];
index = i;
}
}
TreeNode root = new TreeNode(maxValue);
root.left = build(nums,start,index-1);
root.right = build(nums,index+1,end);
return root;
}
}
2 前中后序遍历
2.1 递归实现
前序遍历框架
public void dfs(TreeNode root) {
visit(root.val);
dfs(root.left);
dfs(root.right);
}
中序遍历框架
public void dfs(TreeNode root) {
dfs(root.left);
visit(root.val);
dfs(root.right);
}
后序遍历框架
public void dfs(TreeNode root) {
dfs(root.left);
dfs(root.right);
visit(root.val);
}
2.2 非递归实现
144前序遍历框架⭐⭐
构建栈,根左右,先弹出根结点,然后将右结点先放,最后弹出。
弹出记得保存。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null) continue;
ret.add(node.val);
stack.push(node.right); // 先右后左,保证左子树先遍历
stack.push(node.left);
}
return ret;
}
145中序遍历框架
左子结点存到底,弹出记得存入右子结点。
弹出时记得保存。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
if (root == null) return ret;
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode node = stack.pop();
ret.add(node.val);
cur = node.right;
}
return ret;
}
94后序遍历框架
通过前序遍历变形得到,后序遍历为左右根,反过来是根右左。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> ret = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
if (node == null) continue;
ret.add(node.val);
stack.push(node.left);
stack.push(node.right);
}
Collections.reverse(ret);
return ret;
}
3 层序遍历
层序遍历队列实现框架⭐⭐⭐
BFS实现,队列实现,先入先出,从左到右的层序遍历。
public void bfs(TreeNode root) {
if (root == null) return;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int cnt = queue.size();
for (int i = 0; i < cnt; i++) {
TreeNode node = queue.poll();
//存储操作
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
}
}
BFS框架
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
Set<Node> visited; // 避免走回头路
q.offer(start); // 将起点加入队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
637每层结点的平均值——层序遍历适当修改
public List<Double> averageOfLevels(TreeNode root) {
List<Double> ret = new ArrayList<>();
if (root == null) return ret;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
int cnt = queue.size();
double sum = 0;
for (int i = 0; i < cnt; i++) {
TreeNode node = queue.poll();
sum += node.val;
if (node.left != null) queue.add(node.left);
if (node.right != null) queue.add(node.right);
}
ret.add(sum / cnt);
}
return ret;
}
4 序列化与反序列化
4.1 前序遍历序列化和反序列化
序列化成数组
->res = [1,2,-1,4,-1,-1,3,-1,-1]
LinkedList<Integer> res;
void traverse(TreeNode root) {
if (root == null) {
// 暂且用数字 -1 代表空指针 null
res.addLast(-1);
return;
}
/****** 前序遍历位置 ******/
res.addLast(root.val);
/***********************/
traverse(root.left);
traverse(root.right);
}
序列化成字符串
->sb=“1,2,#,4,#,#,3,#,#,”
String SEP = ",";
String NULL = "#";
/* 主函数,将二叉树序列化为字符串 */
String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
serialize(root, sb);
return sb.toString();
}
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
/****** 前序遍历位置 ******/
sb.append(root.val).append(SEP);
/***********************/
serialize(root.left, sb);
serialize(root.right, sb);
}
反序列化
- 无空指针信息:至少要得到前、中、后序遍历中的两种才能还原二叉树。
- 包含空指针的信息:node 列表就可以还原二叉树。
反序列化思路:先找根构建当前结点,递归构建左右子树
/* 主函数,将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
// 将字符串转化成列表
LinkedList<String> nodes = new LinkedList<>();
for (String s : data.split(SEP)) {
nodes.addLast(s);
}
return deserialize(nodes);
}
/* 辅助函数,通过 nodes 列表构造二叉树 */
TreeNode deserialize(LinkedList<String> nodes) {
if (nodes.isEmpty()) return null;
/****** 前序遍历位置 ******/
// 列表最左侧就是根节点
String first = nodes.removeFirst();
//回溯时机,返回上一层去构建右子树
if (first.equals(NULL)) return null;
TreeNode root = new TreeNode(Integer.parseInt(first));
/***********************/
root.left = deserialize(nodes);
root.right = deserialize(nodes);
return root;
}
4.2 后序遍历的序列化与反序列化
序列化
String SEP = ",";
String NULL = "#";
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
serialize(root.left, sb);
serialize(root.right, sb);
/****** 后序遍历位置 ******/
sb.append(root.val).append(SEP);
/***********************/
}
反序列化
找根结点,递归构建左右子树:从后往前在 nodes 列表中取元素,一定要先构造 root.right 子树,后构造 root.left 子树。
/* 主函数,将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
LinkedList<String> nodes = new LinkedList<>();
for (String s : data.split(SEP)) {
nodes.addLast(s);
}
return deserialize(nodes);
}
/* 辅助函数,通过 nodes 列表构造二叉树 */
TreeNode deserialize(LinkedList<String> nodes) {
if (nodes.isEmpty()) return null;
// 从后往前取出元素
String last = nodes.removeLast();
if (last.equals(NULL)) return null;
TreeNode root = new TreeNode(Integer.parseInt(last));
// 限构造右子树,后构造左子树
root.right = deserialize(nodes);
root.left = deserialize(nodes);
return root;
}
4.3 中序遍历的序列化与反序列化
序列化
/* 辅助函数,将二叉树存入 StringBuilder */
void serialize(TreeNode root, StringBuilder sb) {
if (root == null) {
sb.append(NULL).append(SEP);
return;
}
serialize(root.left, sb);
/****** 中序遍历位置 ******/
sb.append(root.val).append(SEP);
/***********************/
serialize(root.right, sb);
}
反序列化行不通,确定不了根节点!!
4.4 层序遍历的序列化与反序列化
序列化
String SEP = ",";
String NULL = "#";
/* 将二叉树序列化为字符串 */
String serialize(TreeNode root) {
if (root == null) return "";
StringBuilder sb = new StringBuilder();
// 初始化队列,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode cur = q.poll();
/* 层级遍历代码位置 */
if (cur == null) {
sb.append(NULL).append(SEP);
continue;
}
sb.append(cur.val).append(SEP);
/*****************/
//null也要加入奥
q.offer(cur.left);
q.offer(cur.right);
}
return sb.toString();
}
反序列化
- 拿出时候构建该结点
- 压入左右子结点,null不压入,依次读取
/* 将字符串反序列化为二叉树结构 */
TreeNode deserialize(String data) {
if (data.isEmpty()) return null;
String[] nodes = data.split(SEP);
// 第一个元素就是 root 的值
TreeNode root = new TreeNode(Integer.parseInt(nodes[0]));
// 队列 q 记录父节点,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
for (int i = 1; i < nodes.length; ) {
// 队列中存的都是父节点
TreeNode parent = q.poll();
// 父节点对应的左侧子节点的值
String left = nodes[i++];
if (!left.equals(NULL)) {
parent.left = new TreeNode(Integer.parseInt(left));
q.offer(parent.left);
} else {
parent.left = null;
}
// 父节点对应的右侧子节点的值
String right = nodes[i++];
if (!right.equals(NULL)) {
parent.right = new TreeNode(Integer.parseInt(right));
q.offer(parent.right);
} else {
parent.right = null;
}
}
return root;
}