目录
- 参考了很多大佬的题解,仅作为自己学习笔记用。
- 144. 二叉树的前序遍历
- 145. 二叉树的后续遍历
- 094. 二叉树的中序遍历
- 637. 二叉树的层平均值
- 513. 找树左下角的值
- 104. 二叉树的深度
- 110. 平衡二叉树
- 543 二叉树路径最大值
- 226. 翻转二叉树(镜像)
- 617. 合并二叉树
- 112. 判断路径和是否等于一个数
- 437. 统计路径和等于一个数的路径数量
- 572. 子树判断
- 101. 对称二叉树
- 111. 二叉树最小深度
- 404. 左叶子之和
- 687. 最长同值路径
- 337. 打家劫舍-3
- 671. 二叉树中第二小节点
- 235. 二叉搜索树最近祖先
- 236. 二叉树的最近祖先
- 108. 有序数组转换二叉搜索树
- 109. 有序链表转换二叉搜索树
- 653. BST中是否存在两数之和为给定值
- 530. 二叉搜索树最小绝对值差
- 501. 二叉搜索树的众数
- 230. 二叉搜索树中第 k 小元素
- 538. 把二叉搜索树转换累加树
- 669. 修剪二叉搜索树
- 参考资料
参考了很多大佬的题解,仅作为自己学习笔记用。
144. 二叉树的前序遍历
题意:
给你二叉树的根节点 root ,返回它节点值的前序遍历。
题解1:
递归。dfs 方法就是前序遍历的模板。
class Solution {
List<Integer> list = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
dfs(root);
return list;
}
void dfs(TreeNode root){
if(root == null) return;
list.add(root.val);
dfs(root.left);
dfs(root.right);
}
}
题解2:
迭代。递归的时候是隐式的用到了栈,所以迭代的时候需要显式声明一个栈。
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null)
return list;
Deque<TreeNode> stack = new LinkedList<>(); // 声明一个栈,先把头节点入栈
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop(); // 弹出栈顶,加入list
list.add(node.val);
if(node.right != null) // 注意是先右后左,这样入栈,左就在上面
stack.push(node.right);
if(node.left != null)
stack.push(node.left);
}
return list;
}
145. 二叉树的后续遍历
题意:
给定一个二叉树,返回它的后序遍历。
题解1:
递归。
class Solution {
List<Integer> list = new ArrayList<>();
public List<Integer> postorderTraversal(TreeNode root) {
dfs(root);
return list;
}
void dfs(TreeNode root){
if(root == null) return;
dfs(root.left);
dfs(root.right);
list.add(root.val);
}
}
题解2:
迭代。前序遍历是根先入栈,再弹出,再先右后左入栈,最后出来是根左右,如果先左后右入栈,出来的话就是根右左,然后再把这个反转一下,就是左右根,那就是后续了,而这里的反转,可以用 Collections.reverse()
,也可以用另外一个栈来反转顺序。这里我们用另外一个栈来解。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null)
return list;
Deque<TreeNode> stack1 = new LinkedList<>(); // 遍历存放的
Deque<TreeNode> stack2 = new LinkedList<>(); // 将上面倒序再弹出才是最后结果的顺序
stack1.push(root);
while(!stack1.isEmpty()){
TreeNode node = stack1.pop();
stack2.push(node);
if(node.left != null)
stack1.push(node.left);
if(node.right != null)
stack1.push(node.right);
}
while(!stack2.isEmpty())
list.add(stack2.pop().val); // 将stack2弹出才是正确的顺序
return list;
}
094. 二叉树的中序遍历
题意:
给定一个二叉树的根节点 root ,返回它的中序遍历。
题解1:
递归。
class Solution {
List<Integer> list = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
dfs(root);
return list;
}
void dfs(TreeNode root){
if(root == null) return;
dfs(root.left);
list.add(root.val);
dfs(root.right);
}
}
题解2:
迭代。尽可能的将这个节点的左子树压入栈,此时栈顶的元素是最左侧的元素。弹出访问,访问结束会访问父节点,因为栈中记录了来源。具体的流程可以看官方题解中的图。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null)
return list;
Deque<TreeNode> stack = new LinkedList<>(); // 遍历存放的
while(!stack.isEmpty() || root != null){
while(root != null){ // 一直往左走,把左节点都入栈,栈顶应该是最左侧节点
stack.push(root);
root = root.left;
}
TreeNode node = stack.pop(); // 先弹出最左侧节点,然后访问
list.add(node.val);
root = node.right; // cur 变成弹出节点的右节点
}
return list;
}
637. 二叉树的层平均值
题意:
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
题解:
public List<Double> averageOfLevels(TreeNode root) {
List<Double> list = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if(root != null)
queue.add(root);
while(!queue.isEmpty()){
int size = queue.size();
int i = size;
double sum = 0;
while(i-- > 0){
TreeNode node = queue.poll();
sum += node.val;
if(node.left != null)
queue.add(node.left);
if(node.right != null)
queue.add(node.right);
}
list.add(sum/size);
}
return list;
}
513. 找树左下角的值
题意:
给定一个二叉树,在树的最后一行找到最左边的值。
题解:
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
root = queue.poll();
if (root.right != null)
queue.add(root.right);
if (root.left != null)
queue.add(root.left);
}
return root.val;
}
104. 二叉树的深度
题意:
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
题解:
public int maxDepth(TreeNode root) {
return root == null ? 0 : Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
110. 平衡二叉树
题意:
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
题解:
class Solution {
private boolean res = true;
public boolean isBalanced(TreeNode root) {
maxDepth(root);
return res;
}
int maxDepth(TreeNode root){
if(root == null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
if(Math.abs(left - right) > 1) res = false;
return 1 + Math.max(left, right);
}
}
543 二叉树路径最大值
题意:
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
题解:
最长的路径不一定经过根节点。用一个变量存储左右深度之和,每次更新为最大。
class Solution {
int max;
public int diameterOfBinaryTree(TreeNode root) {
max = 0;
maxDepth(root);
return max;
}
int maxDepth(TreeNode root){
if(root == null) return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
max = Math.max(max, left + right);
return 1 + Math.max(left, right);
}
}
226. 翻转二叉树(镜像)
题意:
翻转一棵二叉树。
题解:
public TreeNode invertTree(TreeNode root) {
if(root == null) return null;
TreeNode left = root.left;
root.left = invertTree(root.right);
root.right = invertTree(left);
return root;
}
617. 合并二叉树
题意:
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
题解:
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null) return root2;
if(root2 == null) return root1;
TreeNode node = new TreeNode(root1.val + root2.val);
node.left = mergeTrees(root1.left, root2.left);
node.right = mergeTrees(root1.right, root2.right);
return node;
}
112. 判断路径和是否等于一个数
题意:
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 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);
}
437. 统计路径和等于一个数的路径数量
题意:
给定一个二叉树,它的每个结点都存放着一个整数值。找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
题解:
class Solution {
public int pathSum(TreeNode root, int sum) {
Map<Integer, Integer> prefixSum = new HashMap<>();
prefixSum.put(0, 1);
return recur(root, sum, prefixSum, 0);
}
// 树节点 要找的路径和 前缀和map 当前节点路径和
int recur(TreeNode root, int target, Map<Integer, Integer> prefixSum, int curSum){
if(root == null) return 0;
// 核心代码
curSum += root.val;
int tmp = 0;
tmp += prefixSum.getOrDefault(curSum - target, 0);
prefixSum.put(curSum, prefixSum.getOrDefault(curSum, 0) + 1);
// 下一层
tmp += recur(root.left, target, prefixSum, curSum);
tmp += recur(root.right, target, prefixSum, curSum);
// 状态恢复代码的作用就是: 在遍历完一个节点的所有子节点后,将其从map中除去。
prefixSum.put(curSum, prefixSum.get(curSum) - 1);
return tmp;
}
}
572. 子树判断
题意:
给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
题解:
class Solution {
public boolean isSubtree(TreeNode s, TreeNode t) {
if(s == null) return false;
return recur(s, t) || isSubtree(s.left, t) || isSubtree(s.right, t);
}
boolean recur(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 recur(s.left, t.left) && recur(s.right, t.right);
}
}
101. 对称二叉树
题意:
给定一个二叉树,检查它是否是镜像对称的。
题解:
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return recur(root.left, root.right);
}
boolean recur(TreeNode l, TreeNode r){
if(l == null && r == null) return true;
if(l == null || r == null) return false;
if(l.val != r.val) return false;
return recur(l.left, r.right) && recur(l.right, r.left);
}
}
111. 二叉树最小深度
题意:
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
题解:
class Solution {
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 1 + Math.min(left, right);
}
}
404. 左叶子之和
题意:
计算给定二叉树的所有左叶子之和。
题解:
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
if(root == null) return 0;
if(isLeaf(root.left))
return root.left.val + sumOfLeftLeaves(root.right);
return sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right);
}
boolean isLeaf(TreeNode node){
if(node == null) return false;
return node.left == null && node.right == null;
}
}
687. 最长同值路径
题意:
给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。
注意:两个节点之间的路径长度由它们之间的边数表示。
题解:
class Solution {
int res;
public int longestUnivaluePath(TreeNode root) {
res = 0;
dfs(root);
return res;
}
int dfs(TreeNode root){
if(root == null) return 0;
int left = dfs(root.left);
int right = dfs(root.right);
int arrowLeft = 0, arrowRight = 0;
if(root.left != null && root.left.val == root.val)
arrowLeft = left + 1;
if(root.right != null && root.right.val == root.val)
arrowRight = right + 1;
res = Math.max(res, arrowLeft + arrowRight);
return Math.max(arrowLeft, arrowRight);
}
}
337. 打家劫舍-3
题意:
在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
题解:
动态规划。
class Solution {
public int rob(TreeNode root) {
int[] dp = dfs(root);
return Math.max(dp[0], dp[1]);
}
public int[] dfs(TreeNode node) {
if (node == null)
return new int[]{0, 0};
int[] left = dfs(node.left);
int[] right = dfs(node.right);
// dp[0]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点不偷
// dp[1]:以当前 node 为根结点的子树能够偷取的最大价值,规定 node 结点偷
int[] dp = new int[2];
dp[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
dp[1] = node.val + left[0] + right[0];
return dp;
}
}
671. 二叉树中第二小节点
题意:
给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
更正式地说,root.val = min(root.left.val, root.right.val) 总成立。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
题解:
class Solution {
long ans = Long.MAX_VALUE;
public int findSecondMinimumValue(TreeNode root) {
if(root==null) return -1;
dfs(root, root.val);
if(ans == Long.MAX_VALUE) return -1;
return (int)ans;
}
private void dfs(TreeNode root, int min) {
if(root == null) return;
if(root.val > min && root.val < ans)
ans = root.val;
dfs(root.left, min);
dfs(root.right, min);
}
}
235. 二叉搜索树最近祖先
题意:
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
题解:
因为是二叉搜索树,所以数的排列是有顺序可循的。
如果 root 比p、q 都大,那么 p、q在 root 的左子树上;
如果 root 比p、q 都小,那么 p、q在 root 的右子树上;
否则,root 就是祖先。
①递归
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null)
return root;
if(root.val > p.val && root.val > q.val) // root 比 p q都大,往左子树走
return lowestCommonAncestor(root.left, p, q);
if(root.val < p.val && root.val < q.val) // root 比 p q都大,往右子树走
return lowestCommonAncestor(root.right, p, q);
return root;
}
②迭代
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null){
if(root.val > p.val && root.val > q.val)
root = root.left;
else if(root.val < p.val && root.val < q.val)
root = root.right;
else
break;
}
return root;
}
如果可以保证 p < q,那么就可以减少判断条件,优化如下:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(p.val > q.val) { // 保证 p.val < q.val
TreeNode tmp = p;
p = q;
q = tmp;
}
while(root != null) {
if(root.val < p.val) // p,q 都在 root 的右子树中
root = root.right; // 遍历至右子节点
else if(root.val > q.val) // p,q 都在 root 的左子树中
root = root.left; // 遍历至左子节点
else break;
}
return root;
}
236. 二叉树的最近祖先
题意:
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
和上题一个意思,就是由二叉搜索树,变成了二叉树。
题解:
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null;
if(root == q || root == p) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null){
return right;
}else if(right == null){
return left;
}else{
return root;
}
}
108. 有序数组转换二叉搜索树
题意:
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足 “每个节点的左右两个子树的高度差的绝对值不超过 1 ” 的二叉树。
题解:
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
public TreeNode helper(int[] nums, int left, int right) {
if (left > right) return null;
int mid = (left + right) / 2; // 总是选择中间位置左边的数字作为根节点
TreeNode root = new TreeNode(nums[mid]);
root.left = helper(nums, left, mid - 1); // 递归构建root 的左子树右子树
root.right = helper(nums, mid + 1, right);
return root;
}
}
109. 有序链表转换二叉搜索树
题意:
给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。
本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
题解1:
和上题类似,可以将链表转为有序数组,在和上题一样的解法,但是需要额外的空间。
此处我们直接用快慢指针先找到链表的中位数,下面再进行递归构建。
class Solution {
public TreeNode sortedListToBST(ListNode head) {
return helper(head, null);
}
TreeNode helper(ListNode left, ListNode right){
if(left == right) return null;
ListNode mid = getMedian(left, right);
TreeNode root = new TreeNode(mid.val);
root.left = helper(left, mid);
root.right = helper(mid.next, right);
return root;
}
public ListNode getMedian(ListNode left, ListNode right) {
ListNode fast = left, slow = left;
while (fast != right && fast.next != right) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}
题解2:
中序遍历可以带来优化。直接按链表顺序构建好,先构建左节点,再构建根节点,再构建右节点。
class Solution {
ListNode listHead; // 指向链表的头节点
public TreeNode sortedListToBST(ListNode head) {
listHead = head;
int length = getLength(head);
return buildTree(0, length - 1);
}
public int getLength(ListNode head) { // 获取链表的长度
int ret = 0;
while (head != null) {
++ret;
head = head.next;
}
return ret;
}
public TreeNode buildTree(int left, int right) {
if (left > right) return null;
int mid = (left + right) / 2;
TreeNode root = new TreeNode();
root.left = buildTree(left, mid - 1);
root.val = listHead.val;
listHead = listHead.next;
root.right = buildTree(mid + 1, right);
return root;
}
}
653. BST中是否存在两数之和为给定值
题意:
给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。
题解:
中序遍历转换为有序数组,然后双指针(对撞指针)进行查找。
class Solution {
List<Integer> list = new ArrayList<>();
public boolean findTarget(TreeNode root, int k) {
inOrder(root);
int i = 0, j = list.size() - 1;
while(i < j){
if(list.get(i) + list.get(j) == k){
return true;
}else if(list.get(i) + list.get(j) > k){
j--;
}else{
i++;
}
}
return false;
}
void inOrder(TreeNode root){
if(root == null) return ;
inOrder(root.left);
list.add(root.val);
inOrder(root.right);
}
}
530. 二叉搜索树最小绝对值差
题意:
给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
题解:
可以中序遍历放入list,然后比较相邻元素。但是利用中序遍历是有序的性质更好,直接在遍历的时候操作。
class Solution {
private int min = Integer.MAX_VALUE;
TreeNode pre = null;
public int getMinimumDifference(TreeNode root) {
inOrder(root);
return min;
}
void inOrder(TreeNode root){
if(root == null) return ;
inOrder(root.left);
if(pre != null)
min = Math.min(min, Math.abs(pre.val - root.val));
pre = root;
inOrder(root.right);
}
}
501. 二叉搜索树的众数
题意:
给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。
题解:
class Solution {
List<Integer> list = new ArrayList<>();
int cur, cnt, maxCnt;
public int[] findMode(TreeNode root) {
inOrder(root);
int[] res = new int[list.size()];
for(int i = 0; i < list.size(); i++){
res[i] = list.get(i);
}
return res;
}
void inOrder(TreeNode root){
if(root == null) return ;
inOrder(root.left);
update(root.val);
inOrder(root.right);
}
void update(int x){
if(x == cur){
cnt++;
}else{
cnt = 1;
cur = x;
}
if(cnt == maxCnt){
list.add(cur);
}
if(cnt > maxCnt){
maxCnt = cnt;
list.clear();
list.add(cur);
}
}
}
230. 二叉搜索树中第 k 小元素
题意:
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)。
题解:
用数组存放中序遍历,然后找。
class Solution {
public int kthSmallest(TreeNode root, int k) {
ArrayList<Integer> list = inorder(root, new ArrayList<Integer>());
return list.get(k - 1);
}
public ArrayList<Integer> inorder(TreeNode root, ArrayList<Integer> list) {
if (root == null) return list;
inorder(root.left, list);
list.add(root.val);
inorder(root.right, list);
return list;
}
}
538. 把二叉搜索树转换累加树
题意:
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
题解:
反序中序遍历。
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if (root != null) {
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}
}
669. 修剪二叉搜索树
题意:
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。
题解:
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) return null;
if (root.val < low) {
//因为是二叉搜索树,节点.left < 节点 < 节点.right
//节点数字比low小,就把左节点全部裁掉.
root = root.right;
//裁掉之后,继续看右节点的剪裁情况.剪裁后重新赋值给root.
root = trimBST(root, low, high);
} else if (root.val > high) {
//如果数字比high大,就把右节点全部裁掉.
root = root.left;
//裁掉之后,继续看左节点的剪裁情况
root = trimBST(root, low, high);
} else {
//如果数字在区间内,就去裁剪左右子节点.
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
}
return root;
}