【代码随想录】二叉树刷题

理论基础

满二叉树:一棵二叉树只有度为 0 的结点和度为 2 的结点,并且度为 0 的结点在同一层

完全二叉树:对节点从上至下、左至右开始编号,其所有编号都能与 相同高度的满二叉树中的编号对应

二叉搜索树:它存储的元素必须具备 可比较性

  • 任意一个节点的值都 大于子树 所有节点的值
  • 任意一个节点的值都 小于子树 所有节点的值
  • 它的左右子树也是一棵 二叉搜索树

平衡二叉搜索树 (AVL 树):它是一棵空树,或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树

二叉树的存储方式

链式存储:

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;
    }
}

顺序存储:

a
------
|    |
b    c
--------
| |  | |
d e  f g

下标 i 从 0 开始
- 左子树: 2i + 1
- 右子树: 2i + 2
-------------------
0  1  2  3  4  5  6
a, b, c, d, e, f, g
-------------------

下标 i 从 1 开始
- 左子树: 2i
- 右子树: 2i + 1
----------------------
0  1  2  3  4  5  6  7
-  a, b, c, d, e, f, g
----------------------

二叉树的遍历

前中后序遍历

递归的本质:

  • 每一次递归调用都会把函数的 局部变量参数值返回地址 等压入调用栈中
  • 递归返回的时候,从栈顶弹出上一次递归的各项参数
  • 这就是递归为什么可以返回上一层位置的原因

先序遍历144. 二叉树的前序遍历

// 递归实现
class Solution {
    
    
    public List<Integer> preorderTraversal(TreeNode root) {
    
    
        List<Integer> list = new ArrayList<>(); 
        dfs(root, list);
        return list;
    }

    void dfs(TreeNode node, List<Integer> list) {
    
    
        if (node == null) return;
        list.add(node.val); // 根
        dfs(node.left, list); // 左
        dfs(node.right, list); // 右
    }
}
// 迭代实现
class Solution {
    
    
    public List<Integer> preorderTraversal(TreeNode root) {
    
    
        List<Integer> res = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        if (root != null) stack.push(root);
        while (!stack.isEmpty()) {
    
    
            TreeNode node = stack.pop();
            res.add(node.val);
            if (node.right != null) stack.push(node.right); // 右
            if (node.left != null) stack.push(node.left); // 左
        }
        return res;
    }
}

后序遍历145. 二叉树的后序遍历

// 递归实现
class Solution {
    
    
    public List<Integer> postorderTraversal(TreeNode root) {
    
    
        List<Integer> list = new ArrayList<>();
        dfs(root, list);
        return list;
    }

    void dfs(TreeNode node, List<Integer> list) {
    
    
        if (node == null) return;
        dfs(node.left, list); // 左
        dfs(node.right, list); // 右
        list.add(node.val); // 根
    }
}
// 迭代实现
public List<Integer> postorderTraversal(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	if (root == null) return res;

	Stack<TreeNode> stack = new Stack<>();
	stack.add(root);

	while (!stack.isEmpty()) {
    
    
		TreeNode node = stack.pop();
		res.add(node.val);
		if (node.left != null) stack.add(node.left); // 左
		if (node.right != null) stack.add(node.right); // 右
	}

	Collections.reverse(res); // 反转
	return res;
}

中序遍历94. 二叉树的中序遍历

// 递归实现
class Solution {
    
    
    List<Integer> res = new ArrayList<>();

    public List<Integer> inorderTraversal(TreeNode root) {
    
    
        dfs(root);
        return res;
    }

    void dfs(TreeNode node) {
    
    
        if (node == null) return;
        dfs(node.left); // 左
        res.add(node.val); // 根
        dfs(node.right); // 右
    }
}

中序遍历 的 迭代实现不太容易记,建议记后面的 迭代写法统一

// 迭代实现1
public List<Integer> inorderTraversal(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	Stack<TreeNode> st = new Stack<>();
	TreeNode cur = root;
	while (cur != null || !st.isEmpty()) {
    
    
		if (cur != null) {
    
     // *
			st.push(cur);
			cur = cur.left;
		} else {
    
     // *
			cur = st.pop();
			res.add(cur.val);
			cur = cur.right;
		}
	}
	return res;
}

// 迭代实现2
public List<Integer> inorderTraversal(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	Stack<TreeNode> st = new Stack<>();
	TreeNode cur = root;
	while (cur != null || !st.isEmpty()) {
    
    
		while (cur != null) {
    
     // *
			st.push(cur);
			cur = cur.left;
		} 
		cur = st.pop();
		res.add(cur.val);
		cur = cur.right;
	}
	return res;
}

迭代写法统一*

这种写法下,三种遍历只需要调整代码顺序即可实现:

// 中序遍历
public List<Integer> inorderTraversal(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	if (root == null) return res;
	Stack<Object> st = new Stack<>();
	st.push(root);
	while (!st.empty()) {
    
    
		Object o = st.pop();
		if (o instanceof TreeNode) {
    
    
			TreeNode node = (TreeNode) o;
			if (node.right != null) st.push(node.right); // *
			st.push(node.val); // *
			if (node.left != null) st.push(node.left); // *
		} else {
    
    
			res.add((Integer) o);
		}
	}
	return res;
}

// 先序遍历
public List<Integer> preorderTraversal(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	if (root == null) return res;
	Stack<Object> st = new Stack<>();
	st.push(root);
	while (!st.empty()) {
    
    
		Object o = st.pop();
		if (o instanceof TreeNode) {
    
    
			TreeNode node = (TreeNode) o; // *
			if (node.right != null) st.push(node.right); // *
			if (node.left != null) st.push(node.left); // *
			st.push(node.val);
		} else {
    
    
			res.add((Integer) o);
		}
	}
	return res;
}

// 后序遍历
public List<Integer> postorderTraversal(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	if (root == null) return res;
	TreeNode cur = root;
	Stack<Object> st = new Stack<>();
	st.push(cur);
	while (!st.isEmpty()) {
    
    
		Object o = st.pop();
		if (o instanceof TreeNode) {
    
    
			TreeNode node = (TreeNode) o;
			st.push(node.val); // *
			if (node.right != null) st.push(node.right); // *
			if (node.left != null) st.push(node.left); // *
		} else {
    
    
			res.add((Integer)o);
		}
	}
	return res;
}

层序遍历

题目:102. 二叉树的层序遍历 - 力扣(LeetCode)

public List<List<Integer>> levelOrder(TreeNode root) {
    
    
	List<List<Integer>> res = new ArrayList<>();
	if (root == null) return res;
	Queue<TreeNode> q = new LinkedList<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		List<Integer> tmpList = new ArrayList<>();
		for (int i = q.size(); i > 0; i--) {
    
    
			TreeNode node = q.poll();
			tmpList.add(node.val);
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
		res.add(tmpList);
	}
	return res;
}

层序遍历相关题目

二叉树的层序遍历 II

题目:107. 二叉树的层序遍历 II - 力扣(LeetCode)

给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)

public List<List<Integer>> levelOrderBottom(TreeNode root) {
    
    
	List<List<Integer>> res = new ArrayList<>();
	if (root == null) return res;
	Deque<TreeNode> q = new ArrayDeque<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		List<Integer> tmpList = new ArrayList<>();
		for (int i = q.size(); i > 0; i--) {
    
    
			TreeNode node = q.poll();
			tmpList.add(node.val);
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
		res.add(0, tmpList); // 往头部放
	}
	return res;
}

二叉树的右视图

题目:199. 二叉树的右视图 - 力扣(LeetCode)

给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。

每次将层次遍历的最后一个值添加到结果中:

public List<Integer> rightSideView(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	if (root == null) return res;
	Queue<TreeNode> q = new LinkedList<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		for (int i = q.size(); i > 0; i--) {
    
    
			TreeNode node = q.poll();
			if (i == 1) res.add(node.val);
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
	}
	return res;
}

dfs 将第一次访问该深度的右边元素添加到结果中:

class Solution {
    
    
    List<Integer> res = new ArrayList<>();

    public List<Integer> rightSideView(TreeNode root) {
    
    
        dfs(root, 0);
        return res;
    }

    void dfs(TreeNode node, int depth) {
    
    
        if (node == null) return;
        // 第一次访问这个深度
        if (depth == res.size()) res.add(node.val);
        dfs(node.right, depth + 1);
        dfs(node.left, depth + 1);
    }
}

二叉树的层平均值

题目:637. 二叉树的层平均值 - 力扣(LeetCode)

给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10^-5 以内的答案可以被接受。

public List<Double> averageOfLevels(TreeNode root) {
    
    
	List<Double> res = new ArrayList<>();
	if (root == null) return res;
	Queue<TreeNode> q = new LinkedList<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		double sum = 0;
		int n = q.size();
		for (int i = n; i > 0; i--) {
    
    
			TreeNode node = q.poll();
			sum += node.val;
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
		res.add(sum / n);
	}
	return res;
}

N 叉树的层序遍历

题目:429. N 叉树的层序遍历 - 力扣(LeetCode)

public List<List<Integer>> levelOrder(Node root) {
    
    
	List<List<Integer>> res = new ArrayList<>();
	if (root == null) return res;
	Queue<Node> q = new LinkedList<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		List<Integer> tmpList = new ArrayList<>();
		for (int i = q.size(); i > 0; i--) {
    
    
			Node node = q.poll();
			tmpList.add(node.val);
			for (Node n : node.children) q.add(n);
		}
		res.add(tmpList);
	}
	return res;
}

在每个树行中找最大值

题目:515. 在每个树行中找最大值 - 力扣(LeetCode)

给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。

public List<Integer> largestValues(TreeNode root) {
    
    
	List<Integer> res = new ArrayList<>();
	if (root == null) return res;
	Queue<TreeNode> q = new LinkedList<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		int max = Integer.MIN_VALUE;
		for (int i = q.size(); i > 0; i--) {
    
    
			TreeNode node = q.poll();
			max = Math.max(max, node.val);
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
		res.add(max);
	}
	return res;
}

填充每个节点的下一个右侧节点指针

题目:116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)

层序遍历:

public Node connect(Node root) {
    
    
	if (root == null) return root;
	Queue<Node> q = new LinkedList<>();
	q.add(root);
	while (!q.isEmpty()) {
    
    
		for (int i = q.size(); i > 0; i--) {
    
    
			Node node = q.poll();
			if (i > 1) node.next = q.peek(); // 除最后一个元素,都指向下一元素
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
	}
	return root;
}

DFS:

public Node connect(Node root) {
    
    
	if (root == null) return root;
	if (root.left != null) {
    
    
		root.left.next = root.right; // 亲兄弟建立连接
		if (root.next != null) // 堂兄弟建立连接
			root.right.next = root.next.left;
	}
	connect(root.left);
	connect(root.right);
	return root;
}

填充每个节点的下一个右侧节点指针 II

题目:117. 填充每个节点的下一个右侧节点指针 II - 力扣(LeetCode)

这题用层序遍历的解法的话,和 116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode) 一模一样。

DFS 的做法:

class Solution {
    
    
    public Node connect(Node root) {
    
    
        if (root == null) return root;
        // 左右子树都不为 null, 直接连接
        if (root.left != null && root.right != null)
            root.left.next = root.right;
        // 左不为 null, 右为 null, 则右子树的 next 由根的 next 得出
        if (root.left != null && root.right == null)
            root.left.next = getNext(root.next);
        // 右不为 null
        if (root.right != null)
            root.right.next = getNext(root.next);
        connect(root.right); // 必须先 right 再 left
        connect(root.left);
        return root;
    }
    // 找到 node 最左的第一个节点
    Node getNext(Node node) {
    
    
        if (node == null) return null;
        // 先尝试找左节点
        if (node.left != null) return node.left;
        // 再尝试找右节点
        if (node.right != null) return node.right;
        // 左右节点都不存在,尝试对 node.next 进行相同操作
        if (node.next != null) return getNext(node.next);
        return null;
    }
}

二叉树的最大深度

题目:104. 二叉树的最大深度 - 力扣(LeetCode)

给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

递归:

public int maxDepth(TreeNode root) {
    
    
	if (root == null) return 0;
	return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}

DFS:

class Solution {
    
    
    int maxLevel = 0;

    public int maxDepth(TreeNode root) {
    
    
        dfs(root, 1);
        return maxLevel;
    }

    void dfs(TreeNode node, int level) {
    
    
        if (node == null) return;
        if (level > maxLevel) maxLevel = level;
        dfs(node.left, level + 1);
        dfs(node.right, level + 1);
    }
}

BFS:

public int maxDepth(TreeNode root) {
    
    
	if (root == null) return 0;
	Deque<TreeNode> q = new ArrayDeque<>();
	q.add(root);
	int cnt = 0;
	while (!q.isEmpty()) {
    
    
		for (int i = q.size(); i > 0; i--) {
    
    
			TreeNode node = q.poll();
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
		cnt ++ ;
	}
	return cnt;
}

二叉树的最小深度

题目:111. 二叉树的最小深度 - 力扣(LeetCode)

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

层序遍历:最快

public int minDepth(TreeNode root) {
    
    
	if (root == null) return 0;
	Queue<TreeNode> q = new LinkedList<>();
	q.add(root);
	int level = 1;
	while (!q.isEmpty()) {
    
    
		for (int i = q.size(); i > 0; i--) {
    
    
			TreeNode node = q.poll();
			// 层序遍历时, 遇到第一个叶子节点,返回当前树的高度
			if (node.left == null && node.right == null)
				return level;
			if (node.left != null) q.add(node.left);
			if (node.right != null) q.add(node.right);
		}
		level++;
	}
	return level;
}

DFS:

class Solution {
    
    
    int minLevel = Integer.MAX_VALUE;

    public int minDepth(TreeNode root) {
    
    
        if (root == null) return 0;
        dfs(root, 1);
        return minLevel;
    }

    void dfs(TreeNode node, int level) {
    
    
        if (node == null) return;
        if (node.left == null && node.right == null)
            minLevel = Math.min(minLevel, level);
        dfs(node.left, level + 1);
        dfs(node.right, level + 1);
    }
}

递归:

public int minDepth(TreeNode root) {
    
    
	if (root == null) return 0;
	// 处理只有一个子节点的情况
	if (root.left == null && root.right != null)
		return minDepth(root.right) + 1;
	if (root.right == null && root.left != null)
		return minDepth(root.left) + 1;
	return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}

二叉树题目

翻转二叉树

题目:226. 翻转二叉树 - 力扣(LeetCode)

给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

递归:

public TreeNode invertTree(TreeNode root) {
    
    
	if (root == null) return null;

	// 交换左右子节点
	TreeNode tmpNode = root.left;
	root.left = root.right;
	root.right = tmpNode;

	invertTree(root.left);
	invertTree(root.right);
	return root;
}

BFS:

public TreeNode invertTree(TreeNode root) {
    
    
	if (root == null) return null;
	Queue<TreeNode> queue = new LinkedList<>();
	queue.offer(root);
	while (!queue.isEmpty()) {
    
    
		TreeNode node = queue.poll();
		// 交换子树
		TreeNode t = node.right;
		node.right = node.left;
		node.left = t;
		if (node.left != null) queue.offer(node.left);
		if (node.right != null) queue.offer(node.right);
	}
	return root;
}

DFS:

public TreeNode invertTree(TreeNode root) {
    
    
	if (root == null) return null;
	// 交换子树
	TreeNode t = invertTree(root.left);
	root.left = invertTree(root.right);
	root.right = t;
	return root;
}

对称二叉树

题目:101. 对称二叉树 - 力扣(LeetCode)

给你一个二叉树的根节点 root , 检查它是否轴对称。

递归:

class Solution {
    
    
    public boolean isSymmetric(TreeNode root) {
    
    
        if (root == null) return true;
        return helper(root.left, root.right);
    }

    boolean helper(TreeNode n1, TreeNode n2) {
    
    
        if (n1 == null && n2 == null) return true;
        if (n1 == null || n2 == null || n1.val != n2.val) return false;
        return helper(n1.left, n2.right) && helper(n1.right, n2.left); 
    }
}

迭代:层序遍历

public boolean isSymmetric(TreeNode root) {
    
    
	Queue<TreeNode> q = new LinkedList<>();
	q.offer(root.left);
	q.offer(root.right);
	while (!q.isEmpty()) {
    
    
		TreeNode n1 = q.poll();
		TreeNode n2 = q.poll();
		if (n1 == null && n2 == null) continue;
		if (n1 == null || n2 == null || n1.val != n2.val)
			return false;
		q.offer(n1.left);
		q.offer(n2.right);
		q.offer(n1.right);
		q.offer(n2.left);
	}
	return true;
}

完全二叉树的节点个数*

题目:222. 完全二叉树的节点个数 - 力扣(LeetCode)

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

利用完全二叉树性质的做法:

class Solution {
    
    
    public int countNodes(TreeNode root) {
    
    
        if (root == null) return 0;
        int l = getHeight(root.left); // 左子树的高度
        int r = getHeight(root.right); // 右子树的高度
        if (l == r) {
    
    
            // 左右子树高度相等,表示左子树一定是满二叉树
            // 用公式计算左子树的节点个数:2^l - 1
            // 递归计算右子树的节点个数 countNodes(root.right)
            // 再 + 1 (当前节点)
            return countNodes(root.right) + (1 << l); // 1 << l == 2^l
        } else {
    
    
            // 最后一层不满,但是倒数第二层是满的,右子树一定是满二叉树
            // 公式计算右子树的节点个数: 2^r - 1
            // 递归计算左子树的节点个数 countNodes(root.left)
            // 再 + 1 (当前节点)
            return countNodes(root.left) + (1 << r); // 1 << r == 2^r
        }
    }

    // 完全二叉树计算高度
    int getHeight(TreeNode node) {
    
    
        int height = 0;
        while (node != null) {
    
    
            node = node.left;
            height++;
        }
        return height;
    }
}

递归:

public int countNodes(TreeNode root) {
    
    
	if (root == null) return 0;
	return countNodes(root.left) + countNodes(root.right) + 1;
}

DFS:

class Solution {
    
    
    int num = 0;

    public int countNodes(TreeNode root) {
    
    
        dfs(root);
        return num;
    }

    void dfs(TreeNode node) {
    
    
        if (node == null) return;
        num++;
        dfs(node.left);
        dfs(node.right);
    }
}

层序遍历:

public int countNodes(TreeNode root) {
    
    
	if (root == null) return 0;
	Queue<TreeNode> q = new LinkedList<>();
	q.offer(root);
	int count = 0;
	while (!q.isEmpty()) {
    
    
		TreeNode node = q.poll();
		if (node.left != null) q.offer(node.left);
		if (node.right != null) q.offer(node.right);
		count++;
	}
	return count;
}

平衡二叉树*

题目:110. 平衡二叉树 - 力扣(LeetCode)

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:一个二叉树 每个节点 的左右两个子树的高度差的绝对值不超过 1 。

递归:

class Solution {
    
    
    public boolean isBalanced(TreeNode root) {
    
    
        if (root == null) return true;
        if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1) return false;
        return isBalanced(root.left) && isBalanced(root.right); 
    }
    int getHeight(TreeNode node) {
    
    
        if (node == null) return 0;
        int leftH = getHeight(node.left);
        int rightH = getHeight(node.right);
        return Math.max(leftH, rightH) + 1;
    }
}

带剪枝的递归:

class Solution {
    
    
    public boolean isBalanced(TreeNode root) {
    
    
        return getHeight(root) != -1;
    }

    int getHeight(TreeNode node) {
    
    
        if (node == null) return 0;
        int leftH = getHeight(node.left); // 左子树高度
        int rightH = getHeight(node.right); // 右子树高度
        // 剪枝:左子树 或 右子树 不满足条件 或 当前节点不满足条件,直接返回 -1
        if (leftH == -1 || rightH == -1 || Math.abs(leftH - rightH) > 1) return -1;
        // 左右子树都满足条件,返回当前节点的高度
        return Math.max(leftH, rightH) + 1;
    }
}

二叉树的所有路径

题目:257. 二叉树的所有路径 - 力扣(LeetCode)

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

DFS + StringBuilder:

class Solution {
    
    
    List<String> res = new ArrayList<>();
    
    public List<String> binaryTreePaths(TreeNode root) {
    
    
        dfs(new StringBuilder(), root); 
        return res;
    }

    void dfs(StringBuilder path, TreeNode node) {
    
    
        if (node == null) return;
        path.append(node.val); 
        if (node.left == null && node.right == null) {
    
    
            res.add(path.toString());
            return;
        }
        dfs(new StringBuilder(path).append("->"), node.left);
        dfs(new StringBuilder(path).append("->"), node.right);
    }
}

另一棵树的子树

题目:572. 另一棵树的子树 - 力扣(LeetCode)

给你两棵二叉树 rootsubRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

class Solution {
    
    
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
    
    
        if (root == null) return false; 
        if (isSameTree(root, subRoot)) return true;
        return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
    }
    // 判断 n1 和 n2 是否是相同的树
    boolean isSameTree(TreeNode n1, TreeNode n2) {
    
    
        if (n1 == null && n2 == null) return true;
        if (n1 == null || n2 == null || n1.val != n2.val) return false;
        return isSameTree(n1.left, n2.left) && isSameTree(n1.right, n2.right);
    }
}

左叶子之和

题目:404. 左叶子之和 - 力扣(LeetCode)

给定二叉树的根节点 root ,返回所有左叶子之和。

DFS:

class Solution {
    
    
    int res = 0;
    public int sumOfLeftLeaves(TreeNode root) {
    
    
        if (root.left == null && root.right == null) return 0;
        dfs(root, null);
        return res;
    }
    // DFS 时传入 parent 节点用来判断是否是左叶子
    void dfs(TreeNode node, TreeNode parent) {
    
    
        if (node == null) return;
        // 判断是左叶子
        if (node.left == null && node.right == null && parent.left == node)
            res += node.val;
        dfs(node.left, node);
        dfs(node.right, node);
    }
}

递归:

public int sumOfLeftLeaves(TreeNode root) {
    
    
	if (root == null) return 0;
	int res = 0;
	if (root.left != null && root.left.left == null && root.left.right == null)
		res += root.left.val;
	return  sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right) + res;
}

找树左下角的值

题目:513. 找树左下角的值 - 力扣(LeetCode)

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

层序遍历:每一层更新 res 为当前层第一个节点的值

public int findBottomLeftValue(TreeNode root) {
    
    
	Queue<TreeNode> q = new LinkedList<>();
	q.offer(root);
	int res = root.val;
	while (!q.isEmpty()) {
    
    
		int size = q.size();
		for (int i = size; i > 0; i--) {
    
    
			TreeNode node = q.poll();
			if (node.left != null) q.offer(node.left);
			if (node.right != null) q.offer(node.right);
			if (i == size) res = node.val; // 每次更新为当前层第一个节点的值
		}
	} 
	return res;
}

DFS:遍历同时求出树的深度

// DFS:遍历同时求出树的深度
class Solution {
    
    
    int maxLevel = Integer.MIN_VALUE;
    int res;

    public int findBottomLeftValue(TreeNode root) {
    
    
        dfs(root, 0);     
        return res;
    }
    
    void dfs(TreeNode node, int level) {
    
    
        if (node == null) return;
        if (node.left == null && node.right == null)
            if (level > maxLevel) {
    
    
                maxLevel = level;
                res = node.val;
            }
        dfs(node.left, level + 1);
        dfs(node.right, level + 1); 
    }
}

路径总和

题目:112. 路径总和 - 力扣(LeetCode)

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

DFS 模板:

class Solution {
    
    
    public boolean hasPathSum(TreeNode root, int targetSum) {
    
    
        return dfs(root, 0, targetSum);
    }

    boolean dfs(TreeNode node, int sum, int targetSum) {
    
    
        if (node == null) return false;
        if (node.left == null && node.right == null && sum + node.val == targetSum)
            return true;
        return dfs(node.left, node.val + sum, targetSum)
                || dfs(node.right, node.val + sum, targetSum);
    }
}

使用 - 操作优化掉额外参数:

public boolean hasPathSum(TreeNode root, int targetSum) {
    
    
	if (root == null) return false;
	if (root.left == null && root.right == null && root.val == targetSum)
		return true;
	return hasPathSum(root.left, targetSum - root.val)
			|| hasPathSum(root.right, targetSum - root.val);
}

从中序与后序遍历序列构造二叉树*

题目:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

参考题解:图解构造二叉树之中序+后序

递归:

public TreeNode buildTree(int[] inorder, int[] postorder) {
    
    
	if (inorder.length == 0) return null;
	if (inorder.length == 1) return new TreeNode(inorder[0]);
	// 初始化根节点
	TreeNode root = new TreeNode(postorder[postorder.length - 1]);
	// 在 inorder[] 中找到根的位置
	for (int i = 0; i < inorder.length; i++) {
    
    
		if (inorder[i] == root.val) {
    
    
			root.left = buildTree(
				Arrays.copyOfRange(inorder, 0, i),
				Arrays.copyOfRange(postorder, 0, i));
			root.right = buildTree(
				Arrays.copyOfRange(inorder, i + 1, inorder.length),
				Arrays.copyOfRange(postorder, i, postorder.length - 1));
			break;
		}
	}
	return root;
}

递归优化:传入索引进行优化 + 数组模拟哈希

class Solution {
    
    
    int[] inorder_map = new int[6001]; // 数组模拟哈希
    public TreeNode buildTree(int[] inorder, int[] postorder) {
    
    
        if (inorder.length == 1) return new TreeNode(inorder[0]);
        // 下标映射
        for (int i = 0; i < inorder.length; i++) 
            inorder_map[inorder[i] + 3000] = i; // 映射防越界
        return help(inorder, 0, inorder.length - 1, 
                      postorder, 0, postorder.length - 1);
    }

    // inorder [s1, e1] postorder [s2, e2]
    TreeNode help(int[] inorder, int s1, int e1, int[] postorder, int s2, int e2) {
    
    
        if (s1 > e1 || s2 > e2) return null;
        int rootVal = postorder[e2]; // 根节点的值
        TreeNode root = new TreeNode(rootVal); // 初始化根节点
        int rootValIdx = inorder_map[rootVal + 3000]; // 中序遍历中根节点值的下标
        // 左子树长度 = rootValIx - s1 -1
        // s2 + rootValIdx - s1 - 1 表示计算 后序数组 的结束位置,
        root.left = help(inorder, s1, rootValIdx - 1,
                         postorder, s2, s2 + rootValIdx - s1 - 1);
        root.right = help(inorder, rootValIdx + 1, e1,
                          postorder, s2 + rootValIdx - s1, e2 - 1);
        return root;
    }
}

从前序与中序遍历序列构造二叉树

题目:105. 从前序与中序遍历序列构造二叉树 - 力扣(LeetCode)

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

public TreeNode buildTree(int[] preorder, int[] inorder) {
    
    
	// preorder = [3,9,20,15,7] -> [3] [9] [20, 15, 7] 根左右
	// inorder = [9,3,15,20,7] -> [9] [3] [15, 20, 7] 左根右
	if (preorder.length == 0) return null;
	TreeNode root = new TreeNode(preorder[0]);
	for (int i = 0; i < inorder.length; i++) {
    
    
		if (inorder[i] == root.val) {
    
    
			root.left = buildTree(
				Arrays.copyOfRange(preorder, 1, i + 1),
				Arrays.copyOfRange(inorder, 0, i)
			);
			root.right = buildTree(
				Arrays.copyOfRange(preorder, i + 1, preorder.length),
				Arrays.copyOfRange(inorder, i + 1, inorder.length)
			);
			break;
		}
	}
	return root;
}

最大二叉树

题目:654. 最大二叉树 - 力扣(LeetCode)

给定一个不重复的整数数组 nums最大二叉树 可以用下面的算法从 nums 递归地构建:

  1. 创建一个根节点,其值为 nums 中的最大值。
  2. 递归地在最大值 左边子数组前缀上 构建左子树。
  3. 递归地在最大值 右边子数组后缀上 构建右子树。

返回 nums 构建的 最大二叉树

递归:

public TreeNode constructMaximumBinaryTree(int[] nums) {
    
    
	if (nums.length == 0) return null;
	int maxIdx = 0;
	for (int i = 0; i < nums.length; i++) 
		if (nums[i] > nums[maxIdx]) maxIdx = i;
	TreeNode root = new TreeNode(nums[maxIdx]);
	root.left = constructMaximumBinaryTree(Arrays.copyOfRange(nums, 0, maxIdx));
	root.right = constructMaximumBinaryTree(Arrays.copyOfRange(nums, maxIdx + 1, nums.length));
	return root;
}

利用索引优化数组切片操作:

class Solution {
    
    
    public TreeNode constructMaximumBinaryTree(int[] nums) {
    
    
        return help(nums, 0, nums.length - 1);
    }
    
    TreeNode help(int[] nums, int l, int r) {
    
    
        if (l > r) return null;
        int maxIdx = l;
        for (int i = l; i <= r; i++)
            if (nums[i] > nums[maxIdx]) maxIdx = i;
        TreeNode root = new TreeNode(nums[maxIdx]);
        root.left = help(nums, l, maxIdx - 1);
        root.right = help(nums, maxIdx + 1, r);
        return root;
    }
}

合并二叉树

题目:617. 合并二叉树 - 力扣(LeetCode)

给你两棵二叉树: root1root2
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
注意:合并过程必须从两个树的根节点开始。

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
    
    
	if (root1 == null) return root2;
	if (root2 == null) return root1;
	TreeNode root = new TreeNode(root1.val + root2.val);
	root.left = mergeTrees(root1.left, root2.left);
	root.right = mergeTrees(root1.right, root2.right);
	return root;
}

二叉搜索树题目

验证二叉搜索树

题目:98. 验证二叉搜索树 - 力扣(LeetCode)

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:

  • 节点的左子树只包含 小于 当前节点的数。
  • 节点的右子树只包含 大于 当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

中序 DFS:

class Solution {
    
    
    long preVal = Long.MIN_VALUE;

    public boolean isValidBST(TreeNode root) {
    
    
        if (root == null) return true;
        // 左
        if (!isValidBST(root.left)) return false;
        // 中
        if (root.val <= preVal) return false; 
        preVal = root.val; // 保存上一次遍历时的值
        // 右
        return isValidBST(root.right);
    }
}

二叉搜索树的最小绝对差

题目:530. 二叉搜索树的最小绝对差 - 力扣(LeetCode)

给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值
差值是一个正数,其数值等于两值之差的绝对值。

中序 DFS:

class Solution {
    
    
    int res = Integer.MAX_VALUE;
    int prev = -100010; // 记录上一个节点的值
    
    public int getMinimumDifference(TreeNode root) {
    
    
        dfs(root);
        return res;
    }

    void dfs(TreeNode node) {
    
    
        if (node == null) return;
		// 左
        dfs(node.left);
		// 中
        res = Math.min(res, node.val - prev);
        prev = node.val; // 记录上一个节点的值
		// 右
        dfs(node.right);
    }
}

二叉搜索树中的众数

题目:501. 二叉搜索树中的众数 - 力扣(LeetCode)

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:

  • 结点左子树中所含节点的值 小于等于 当前节点的值
  • 结点右子树中所含节点的值 大于等于 当前节点的值
  • 左子树和右子树都是二叉搜索树

对通用的二叉树的处理:

class Solution {
    
    
    final int N = 100010;
    int[] map = new int[N + N]; // 某个数字的出现次数
    int maxCount = 0; // 某个数字出现最多的次数
    public int[] findMode(TreeNode root) {
    
    
        dfs(root);
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < map.length; i++)
            if (map[i] == maxCount)  list.add(i - N);
        int[] res = new int[list.size()];
        int k = 0;
        for (Integer i : list) res[k++] = i;
        return res;
    }
    void dfs(TreeNode root) {
    
    
        if (root == null) return;
        dfs(root.left);
        map[root.val + N]++; // 映射为正值
        maxCount = Math.max(maxCount, map[root.val + N]);
        dfs(root.right);
    }
}

针对二叉搜索树进行优化:

class Solution {
    
    
    List<Integer> list = new ArrayList<>();
    int preVal = 0; // 上一个值
    int curTimes = 0; // 当前值的出现次数
    int  maxTimes = 0; // 最大的出现次数

    public int[] findMode(TreeNode root) {
    
    
        dfs(root);
        int[] res = new int[list.size()];
        int k = 0;
        for (Integer i : list) res[k++] = i;
        return res;
    }

    void dfs(TreeNode node) {
    
    
        if (node == null) return;
        dfs(node.left);
        // 判断 当前值 与 上一个值 的关系
        if (preVal == node.val) curTimes++;
        else {
    
    
            preVal = node.val;
            curTimes = 1;
        }
        // 判断 当前数量 和 最大数量 的关系
        if (curTimes == maxTimes) list.add(node.val);
        else if (curTimes > maxTimes) {
    
    
            list.clear();
            list.add(node.val);
            maxTimes = curTimes;
        }
        dfs(node.right);
    }
}

二叉树的最近公共祖先**

题目:236. 二叉树的最近公共祖先 - 力扣(LeetCode)

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    
    
	if (root == null || root == p || root == q) return root;
	TreeNode left = lowestCommonAncestor(root.left, p, q);
	TreeNode right = lowestCommonAncestor(root.right, p, q);
	// root 左右两边都能找到,分别为 p, q,则 root 是最近公共祖先
	if (left != null && right != null) return root;
	// 只有 root 左边 能找到
	if (left != null) return left;
	// 只有 root 右边 能找到
	if (right != null) return right;
	// 左右都找不到
	return null;
}

二叉搜索树的最近公共祖先

题目:235. 二叉搜索树的最近公共祖先 - 力扣(LeetCode)

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    
    
	// 如果 p, q 值都小于 root, 说明 p, q 在 root 的左子树中
	if (root.val > p.val && root.val > q.val)
		return lowestCommonAncestor(root.left, p, q);
	// 如果 p, q 值都大于 root, 说明 p, q 在 root 的右子树中
	if (root.val < p.val && root.val < q.val)
		return lowestCommonAncestor(root.right, p, q);
	// p, q 分布在 root 左右,则 root 为最近公共祖先
	return root;
}

二叉搜索树中的插入操作

题目:701. 二叉搜索树中的插入操作 - 力扣(LeetCode)

给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。

递归:

public TreeNode insertIntoBST(TreeNode root, int val) {
    
    
	if (root == null) return new TreeNode(val);
	if (root.val > val) root.left = insertIntoBST(root.left, val);
	else root.right = insertIntoBST(root.right, val);
	return root;
}

迭代:

public TreeNode insertIntoBST(TreeNode root, int val) {
    
    
	if (root == null) return new TreeNode(val);
	TreeNode node = root, parent = root; // 记录上一个节点
	while (node != null) {
    
    
		parent = node;
		if (val > node.val) node = node.right;
		else node = node.left;
	}
	if (val > parent.val) parent.right = new TreeNode(val); 
	else parent.left = new TreeNode(val);
	return root;
}

删除二叉搜索树中的节点**

题目:450. 删除二叉搜索树中的节点 - 力扣(LeetCode)

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。

删除的三种情况:

  1. 度为 0 的节点(叶子节点),直接删除
  2. 度为 1 的节点:用子节点替代该节点
  3. 度为 2 的节点:用 前驱 / 后继 覆盖它,再删除 前驱 / 后继
public TreeNode deleteNode(TreeNode root, int key) {
    
    
	if (root == null) return null;
	// 目标值 < 当前节点值, 往左找
	if (root.val > key) root.left = deleteNode(root.left, key);
	// 目标值 > 当前节点值, 往右找
	else if (root.val < key) root.right = deleteNode(root.right, key);
	// 目标值 == 当前节点值, 进行删除操作
	else {
    
    
		// 情况 1: 度为 0 的节点 (叶子节点): 直接删除
		if (root.left == null && root.right == null) return null;
		// 情况 2: 度为 1 的节点: 用子节点替代该节点
		else if (root.right == null) return root.left;
		else if (root.left == null) return root.right;
		// 情况 3: 度为 2 的节点: 用 前驱/后继 覆盖它, 再删除 前驱/后继
		else {
    
    
			// 前驱: 左节点的最右节点
			TreeNode pre = root.left;
			while (pre.right != null) pre = pre.right; // 寻找前驱
			root.val = pre.val; // 用前驱覆盖当前节点
			root.left = deleteNode(root.left, pre.val); // 删除前驱
			
			// 后继: 右节点的最左节点
			// TreeNode suc = root.right;
			// while (suc.left != null) suc = suc.left; // 寻找后继
			// root.val = suc.val; // 用后继覆盖当前节点
			// root.right = deleteNode(root.right, suc.val); // 删除后继
		}
	}
	return root;
}

修剪二叉搜索树

题目:669. 修剪二叉搜索树 - 力扣(LeetCode)

给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案

public TreeNode trimBST(TreeNode root, int low, int high) {
    
    
	if (root == null) return null;
	// root.val < low 则 root.left 下面的节点值都 < low
	if (root.val < low) return trimBST(root.right, low, high);
	// root.val > high 则 root.right 下面的值都 > high
	if (root.val > high) return trimBST(root.left, low, high);
	// 递归调用左右子树
	root.left = trimBST(root.left, low, high);
	root.right = trimBST(root.right, low, high);
	return root;
}

将有序数组转换为二叉搜索树

题目:108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

好理解的递归:

public TreeNode sortedArrayToBST(int[] nums) {
    
    
	if (nums.length == 0) return null;
	int mid = nums.length >> 1;
	TreeNode root = new TreeNode(nums[mid]);
	root.left = sortedArrayToBST(Arrays.copyOfRange(nums, 0, mid));
	root.right = sortedArrayToBST(Arrays.copyOfRange(nums, mid + 1, nums.length));
	return root;
}

利用索引优化上面递归中的数组拷贝:

class Solution {
    
    
    public TreeNode sortedArrayToBST(int[] nums) {
    
    
        return helper(nums, 0, nums.length - 1);
    }

    TreeNode helper(int[] nums, int l, int r) {
    
    
        if (l > r ) return null;
        int mid = (l + r) >> 1;
        TreeNode root = new TreeNode(nums[mid]);
        root.left = helper(nums, l, mid - 1);
        root.right = helper(nums, mid + 1, r);
        return root;
    }
}

Go 语言记录

Go 的先序递归实现:

func preorderTraversal(root *TreeNode) []int {
    
    
	res := make([]int, 0)
	if root == nil {
    
    
		return res
	}

	st := list.New()
	st.PushBack(root)

	for st.Len() > 0 {
    
    
		node := st.Remove(st.Back()).(*TreeNode)

		res = append(res, node.Val)
		if node.Right != nil {
    
    
			st.PushBack(node.Right)
		}
		if node.Left != nil {
    
    
			st.PushBack(node.Left)
		}
	}
	return res
}

Go 的中序递归实现:

// go - 全局变量
var arr []int

func inorderTraversal(root *TreeNode) []int {
    
    
    arr = make([]int, 0)
    dfs(root)
    return arr
}

func dfs(root *TreeNode) {
    
    
    if root != nil {
    
    
        dfs(root.Left)
        arr = append(arr, root.Val)
        dfs(root.Right)
    }
}

// go - 闭包
func inorderTraversal(root *TreeNode) []int {
    
    
    arr := make([]int, 0)
	
    var dfs func(*TreeNode)
    dfs = func(root *TreeNode) {
    
    
        if root == nil {
    
    
            return
        }
        dfs(root.Left)
        arr = append(arr, root.Val)
        dfs(root.Right)
    }
    
    dfs(root)
    return arr
}

// go - 切片当参数传递
func inorderTraversal(root *TreeNode) []int {
    
    
	arr := make([]int, 0)
	dfs(root, &arr)
	return arr
}

func dfs(root *TreeNode, arr *[]int) {
    
    
	if root == nil {
    
    
        return
	}
	dfs(root.Left, arr)
	*arr = append(*arr, root.Val)
	dfs(root.Right, arr)
}

猜你喜欢

转载自blog.csdn.net/weixin_43734095/article/details/126697337