树专题
- 二叉搜索树第k小的结点
- 剑指 Offer 54. 二叉搜索树的第k大节点
- 树的高度
- 二叉树中值为某一和的路径
- 层次遍历
- Z字形打印二叉树(层次遍历)
- 199. 二叉树的右视图
- 剑指 Offer 33. 二叉搜索树的后序遍历序列
- JZ38 二叉树的深度
- 543. 二叉树的直径
- 剑指 Offer 55 - II. 平衡二叉树
- 剑指 Offer 26. 树的子结构
- 剑指 Offer 07. 重建二叉树
- 剑指 Offer 27. 二叉树的镜像 226. 翻转二叉树
- 101. 对称二叉树 剑指 Offer 28. 对称的二叉树
- Leetcode 114. 二叉树展开为链表
- JZ57 二叉树的下一个结点
- 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
- 剑指 Offer 68 - II. 二叉树的最近公共祖先
- 617. 合并二叉树
- 951. 翻转等价二叉树
- 96. 不同的二叉搜索树
- 222. 完全二叉树的节点个数
- 98. 验证二叉搜索树
- 257. 二叉树的所有路径
- 124. 二叉树中的最大路径和
- 814. 二叉树剪枝
- 1026. 节点与其祖先之间的最大差值
二叉搜索树第k小的结点
思路:二叉搜索树中序遍历(有递归写法和非递归写法)是递增的。注意边界条件,比如 k <= 0 || k > 树的结点个数
import java.util.ArrayList;
public class Solution {
ArrayList<TreeNode> res = new ArrayList<>();
TreeNode KthNode(TreeNode pRoot, int k) {
if(pRoot == null || k <= 0) return null;
inorder(pRoot);
int len = res.size();
if(len < k) return null;
return res.get(k-1);
}
void inorder(TreeNode node){
if(node == null) return;
inorder(node.left);
res.add(node);
inorder(node.right);
}
}
迭代写法
import java.util.ArrayList;
import java.util.ArrayDeque;
public class Solution {
TreeNode KthNode(TreeNode pRoot, int k) {
if(pRoot == null || k <= 0) return null;
ArrayDeque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = pRoot;
int count = 0;
while(!stack.isEmpty() || cur != null){
if(cur != null){
stack.push(cur);
cur = cur.left; // 左
}else{
cur = stack.pop();
count++; // 根
if(count == k){
return cur;
}
cur = cur.right; // 右
}
}
return null;
}
}
剑指 Offer 54. 二叉搜索树的第k大节点
思路:中序遍历后求倒数第k个结点,或者中序遍历倒序的正数第k个结点
中序遍历方式
class Solution {
ArrayList<Integer> list = new ArrayList<>();
public int kthLargest(TreeNode root, int k) {
if(root == null || k <= 0) return 0;
inOrder(root);
return list.get(list.size() - k);
}
public void inOrder(TreeNode root){
if(root == null) return;
inOrder(root.left);
list.add(root.val);
inOrder(root.right);
}
}
中序遍历倒序方式:中序遍历倒序的正数第k个结点:右–左--根
class Solution {
int count, res;
public int kthLargest(TreeNode root, int k) {
if(root == null || k <= 0) return 0;
count = k;
inOrder(root);
return res;
}
public void inOrder(TreeNode root){
if(root == null || count == 0) return;
inOrder(root.right);
// 提前退出
if(--count == 0){
res = root.val;
return;
}
inOrder(root.left);
}
}
树的高度
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
} else {
// 左子树高度
int leftHeight = maxDepth(root.left);
// 右子树高度
int rightHeight = maxDepth(root.right);
// 返回高度较高的子树+1
return Math.max(leftHeight, rightHeight) + 1;
}
}
}
public class Solution {
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
return Math.max(TreeDepth(root.left),TreeDepth(root.right)) + 1;
}
}
二叉树中值为某一和的路径
递归+回溯
终止条件:结点为空 if(node == null) return;
回溯:删除当前访问过的结点 path.remove(path.size() - 1);
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
dfs(root, target);
return res;
}
void dfs(TreeNode node, int target){
if(node == null) return;
path.add(node.val);
target -= node.val;
if(target == 0 && node.left == null && node.right == null){
res.add(new ArrayList<>(path));
}
dfs(node.left, target);
dfs(node.right, target);
path.remove(path.size() - 1);
}
}
层次遍历
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
if(pRoot == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(pRoot);
while(!queue.isEmpty()){
int size = queue.size();
ArrayList<Integer> temp = new ArrayList<>();
for(int i = 0; i < size; i++){
TreeNode cur = queue.poll();
temp.add(cur.val);
if(cur.left != null) queue.offer(cur.left);
if(cur.right != null) queue.offer(cur.right);
}
res.add(new ArrayList<>(temp));
}
return res;
}
}
Z字形打印二叉树(层次遍历)
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
if(pRoot == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(pRoot);
int count = 0;
while(!queue.isEmpty()){
ArrayList<Integer> temp = new ArrayList<>();
int size = queue.size();
for(int i = 0; i < size; i++){
TreeNode cur = queue.poll();
if(count % 2 == 0){
temp.add(cur.val);
}else{
temp.add(0, cur.val);
}
if(cur.left != null) queue.offer(cur.left);
if(cur.right != null) queue.offer(cur.right);
}
count++;
res.add(new ArrayList<>(temp));
}
return res;
}
}
199. 二叉树的右视图
思路:实际上就是层次遍历,返回每一层最右边那个结点,也就是每层最后那个结点(i == size - 1)
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> list = new ArrayList<>();
if(root == null) return list;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i = 0; i < size; i++){
TreeNode node = queue.poll();
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
if(i == size - 1) list.add(node.val);
}
}
return list;
}
}
剑指 Offer 33. 二叉搜索树的后序遍历序列
性质:左结点都小于根,右结点都大于根,所以应该用递归。
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence.length == 0 || sequence == null) return false;
return recur(sequence, 0, sequence.length - 1);
}
public boolean recur(int[] postOrder, int left, int right){
if(left >= right) return true;
int rootVal = postOrder[right];
// 结点索引
int curIndex = left;
// 找左子树
while(postOrder[curIndex] < rootVal){
curIndex++;
}
// 保存左子树最后一个位置的索引
int temp = curIndex;
// 找右子树
while(postOrder[curIndex] > rootVal){
curIndex++;
}
// 索引一直走到结尾 && 左子树范围[left, temp - 1] &&右子树范围[temp, right - 1]
return curIndex == right && recur(postOrder, left, temp - 1) && recur(postOrder, temp, right - 1);
}
}
JZ38 二叉树的深度
递归和BFS
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
int lHeight = TreeDepth(root.left);
int rHeight = TreeDepth(root.right);
return Math.max(lHeight, rHeight) + 1;
}
BFS
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
int depth = 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size = queue.size();
for(int i = 0; i < size; i++){
TreeNode node = queue.poll();
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
}
//遍历完一层,深度+1
depth++;
}
return depth;
}
543. 二叉树的直径
class Solution {
int maxd=0;
public int diameterOfBinaryTree(TreeNode root) {
depth(root);
return maxd;
}
public int depth(TreeNode node){
if(node==null){
return 0;
}
int Left = depth(node.left);
int Right = depth(node.right);
maxd=Math.max(Left+Right,maxd);//将每个节点最大直径(左子树深度+右子树深度)当前最大值比较并取大者
return Math.max(Left,Right)+1;//返回节点深度
}
}
剑指 Offer 55 - II. 平衡二叉树
空树也算平衡树,左右子树高度差不超过1
abs(self.depth(root.left) - self.depth(root.right)) <= 1 //判断 当前子树 是否是平衡树;
isBalanced(root.left) //判断 当前子树的左子树 是否是平衡树;
isBalanced(root.right) //判断 当前子树的右子树 是否是平衡树;
class Solution {
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
return Math.abs(getDepth(root.left)-getDepth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
int getDepth(TreeNode root){
if(root == null) return 0;
int l = getDepth(root.left);
int r = getDepth(root.right);
return Math.max(l, r) + 1;
}
}
剑指 Offer 26. 树的子结构
isSub(root1, root2) || HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
isSub(root1, root2) //root2是不是roo1的子结构
HasSubtree(root1.left, root2) //root2是不是roo1的左子树的子结构
HasSubtree(root1.right, root2) //root2是不是roo1的右子树的子结构
public class Solution {
public boolean HasSubtree(TreeNode A,TreeNode B) {
if(A == null || B == null) return false;
// 两棵树一模一样 || B是A左子树的子结构 || B是A右子树的子结构
return isSub(A, B) || HasSubtree(A.left, B) || HasSubtree(A.right, B);
}
// 逐个判断 实质上是先序遍历
boolean isSub(TreeNode A, TreeNode B){
// 当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true
if(B == null) return true;
if(A == null || A.val != B.val) return false;
// 逐个判断 A 和 B 的左右节点是否相等
return isSub(A.left, B.left) && isSub(A.right, B.right);
}
}
剑指 Offer 07. 重建二叉树
左子树在前序遍历[preL + 1, preL + LeftNodeNum] 在中序遍历[inL, index - 1]
右子树在前序遍历[preL + LeftNodeNum + 1, preR] 在中序遍历[index + 1, inR]
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre == null || in == null || pre.length == 0|| in.length == 0 || pre.length != in.length){
return null;
}
return recur(pre, in, 0, pre.length - 1, 0, in.length - 1);
}
TreeNode recur(int [] pre,int [] in, int preL, int preR, int inL, int inR){
if(inL > inR || preL > preR) return null;
int index = 0;
TreeNode root = new TreeNode(pre[preL]);
for(index = inL; index <= inR; index++){
if(in[index] == root.val){
break;
}
}
// 左子树结点个数
// 左子树在前序遍历[preL + 1, preL + LeftNodeNum] 在中序遍历[inL, index - 1]
// 右子树在前序遍历[preL + LeftNodeNum + 1, preR] 在中序遍历[index + 1, inR]
int LeftNodeNum = index - inL;
root.left = recur(pre, in, preL + 1, preL + LeftNodeNum, inL, index - 1);
root.right = recur(pre, in, preL + LeftNodeNum + 1, preR, index + 1, inR);
return root;
}
}
注意:copyOfRange(int[] arr, int left, int right)是复制 arr[left] ~ arr[right - 1]
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0 || preorder.length != inorder.length) return null;
int rootVal = preorder[0];
int rootIndex = 0;
// 找中序根的索引
for(int i = 0; i < inorder.length; i++){
if(rootVal == inorder[i]){
rootIndex = i;
break;
}
}
TreeNode root = new TreeNode(rootVal);
// 注意边界
root.left = buildTree(
Arrays.copyOfRange(preorder, 1, 1 + rootIndex),
Arrays.copyOfRange(inorder, 0, rootIndex)
);
root.right = buildTree(
Arrays.copyOfRange(preorder, 1 + rootIndex, preorder.length),
Arrays.copyOfRange(inorder, rootIndex + 1, inorder.length)
);
return root;
}
}
剑指 Offer 27. 二叉树的镜像 226. 翻转二叉树
递归的思路:mirrorTree的作用是输出二叉树的镜像(翻转左右子树)
那么新的左子树TreeNode newLeft = mirrorTree(root.right);
新的左子树TreeNode newRight = mirrorTree(root.left);
最后把翻转后的左右子树连上就可以root.left = newLeft; root.right = newRight;
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return root;
TreeNode newRight = mirrorTree(root.left);
TreeNode newLeft = mirrorTree(root.right);
root.left = newLeft;
root.right = newRight;
return root;
}
迭代做法:使用栈,每次交换栈顶的左右节点。
public TreeNode mirrorTree(TreeNode root) {
if(root == null) return root;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
// 交换栈顶的左右节点
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
if(node.left != null) stack.push(node.left);
if(node.right != null) stack.push(node.right);
}
return root;
}
101. 对称二叉树 剑指 Offer 28. 对称的二叉树
递归或者迭代(栈)
public class Solution {
boolean isSymmetrical(TreeNode pRoot) {
if(pRoot == null) return true;
return recur(pRoot.left, pRoot.right);
}
// 此函数比较二叉树中位置对称的两个节点
boolean recur(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 recur(left.left,right.right) && recur(left.right, right.left);
}
}
// 迭代写法
class Solution {
public boolean isSymmetric(TreeNode root) {
LinkedList<TreeNode> queue = new LinkedList<>();
if (root == null) return true;
queue.offer(root.left);
queue.offer(root.right);
while (!queue.isEmpty()){
TreeNode leftNode = queue.poll();
TreeNode rightNode = queue.poll();
if (leftNode == null && rightNode == null) continue;
if (leftNode == null ||
rightNode == null || leftNode.val != rightNode.val)
return false;
queue.offer(leftNode.left);
queue.offer(rightNode.right);
queue.offer(leftNode.right);
queue.offer(rightNode.left);
}
return true;
}
}
Leetcode 114. 二叉树展开为链表
class Solution {
public void flatten(TreeNode root) {
List<TreeNode> list = new ArrayList<TreeNode>();
Deque<TreeNode> stack = new LinkedList<TreeNode>();
TreeNode node = root;
while (node != null || !stack.isEmpty()) {
while (node != null) {
list.add(node);
stack.push(node);
node = node.left;
}
node = stack.pop();
node = node.right;
}
int size = list.size();
for (int i = 1; i < size; i++) {
TreeNode prev = list.get(i - 1), curr = list.get(i);
prev.left = null;
prev.right = curr;
}
}
}
class Solution {
List<TreeNode> list = new ArrayList<>();
public void flatten(TreeNode root) {
preOrder(root);
int size = list.size();
for(int i = 1; i < size; i++){
TreeNode pre = list.get(i - 1), cur = list.get(i);
pre.left = null;
pre.right = cur;
}
}
void preOrder(TreeNode root){
if(root == null) return;
list.add(root);
preOrder(root.left);
preOrder(root.right);
}
}
JZ57 二叉树的下一个结点
题目:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:首先这道题给出的是中序遍历这个二叉树,那么就是左根右。我们在求一个结点的下一个结点,那么这个时候我们需要分情况讨论:
1、如果该结点有右子树,则该结点的下一个结点为该结点的右子树的最左结点。(中序遍历性质)
2、如果该结点没有右子树,则又分两种情况讨论:
情况一:如果该结点为该结点的父结点的左孩子,则该结点的父结点pNode.next则为下一个结点。(左的下一个是根)
情况二:如果该结点为该结点的父结点的右孩子,则该结点的父结点的父结点的父结点,直到其中的一个父结点是这个父结点的左孩子,则该父结点的父结点为下一个结点。
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode) {
if(pNode == null){
return null;
}
if(pNode.right != null){
// 如果有右子树
pNode = pNode.right;
// 找右子树的最左结点
while(pNode.left != null){
pNode = pNode.left;
}
// 找到了就返回
return pNode;
// 没有右子树,此时应该去找父亲节点
}else{
// 存在父亲节点
while(pNode.next != null){
// 该节点是父亲节点的左孩子,直接返回父亲节点
if(pNode.next.left == pNode){
return pNode.next;
}
// 该节点是父亲节点的右孩子,一直找父亲节点,直到父亲节点是某个祖先节点的左孩子。
pNode = pNode.next;
}
}
return null;
}
}
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
思路:1.二叉搜索树的遍历路线是确定的。记录根到p和q的路径path1和path2,path1和path2最后一个相等的结点就是最近公共祖先
2.迭代
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
List<TreeNode> path_p = getPath(root, p);
List<TreeNode> path_q = getPath(root, q);
TreeNode ancestor = null;
for (int i = 0; i < path_p.size() && i < path_q.size(); ++i) {
if (path_p.get(i) == path_q.get(i)) {
ancestor = path_p.get(i);
} else {
break;
}
}
return ancestor;
}
public List<TreeNode> getPath(TreeNode root, TreeNode target) {
List<TreeNode> path = new ArrayList<TreeNode>();
TreeNode node = root;
while (node != target) {
path.add(node);
if (target.val < node.val) {
node = node.left;
} else {
node = node.right;
}
}
path.add(node);
return path;
}
}
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while(root != null) {
if(root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
root = root.right; // 遍历至右子节点
else if(root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
root = root.left; // 遍历至左子节点
else break;
}
return root;
}
}
剑指 Offer 68 - II. 二叉树的最近公共祖先
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null) return null; // 如果树为空,直接返回null
if(root == p || root == q) return root; // 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
TreeNode left = lowestCommonAncestor(root.left, p, q); // 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
TreeNode right = lowestCommonAncestor(root.right, p, q); // 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁
if(left == null) return right; // 如果在左子树中 p和 q都找不到,则 p和 q一定都在右子树中,右子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
else if(right == null) return left; // 否则,如果 left不为空,在左子树中有找到节点(p或q),这时候要再判断一下右子树中的情况,如果在右子树中,p和q都找不到,则 p和q一定都在左子树中,左子树中先遍历到的那个就是最近公共祖先(一个节点也可以是它自己的祖先)
else return root; //否则,当 left和 right均不为空时,说明 p、q节点分别在 root异侧, 最近公共祖先即为 root
}
}
617. 合并二叉树
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
return merge(root1, root2);
}
public TreeNode merge(TreeNode root1, TreeNode root2){
if(root1 == null || root2 == null){
return root1 == null ? root2 : root1;
}
int val1 = root1 == null ? 0 : root1.val;
int val2 = root2 == null ? 0 : root2.val;
// 先合并根节点
TreeNode newRoot = new TreeNode(val1 + val2);
// 再递归合并左右子树
newRoot.left = merge(root1.left, root2.left);
newRoot.right = merge(root1.right, root2.right);
return newRoot;
}
}
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if (t1 == null) {
return t2;
}
if (t2 == null) {
return t1;
}
// 先合并根节点
TreeNode merged = new TreeNode(t1.val + t2.val);
// 再递归合并左右子树
merged.left = mergeTrees(t1.left, t2.left);
merged.right = mergeTrees(t1.right, t2.right);
return merged;
}
}
使用队列来做
class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if(root1 == null || root2 == null){
return root1 == null ? root2 : root1;
}
Queue<TreeNode> queue = new LinkedLst<>();
queue.offer(root1);
queue.offer(root2);
while(!queue.isEmpty()){
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
node1.val += node2.val;
if(node1.left != null && node2.left != null){
queue.offer(node1.left);
queue.offer(node2.left);
}
if(node1.left == null){
node1.left = node2.left;
}
if(node1.right != null && node2.right != null){
queue.offer(node1.right);
queue.offer(node2.right);
}
if(node1.right == null){
node1.right = node2.right;
}
}
return root1;
}
}
951. 翻转等价二叉树
class Solution {
public boolean flipEquiv(TreeNode root1, TreeNode root2) {
if(root1 == null && root2 == null) return true;
// if(root1 == root2) return true;
if(root1 == null || root2 == null || root1.val != root2.val) return false;
// 不翻转
boolean b1 = flipEquiv(root1.left, root2.left) && flipEquiv(root1.right, root2.right);
// 翻转
boolean b2 = flipEquiv(root1.left, root2.right) && flipEquiv(root1.right, root2.left);
return b1 || b2;
}
}
96. 不同的二叉搜索树
假设n个节点存在二叉排序树的个数是G(n),令f(i)为以i为根的二叉搜索树的个数
即有:G(n) = f(1) + f(2) + f(3) + f(4) + … + f(n)
n为根节点,当i为根节点时,其左子树节点个数为[1,2,3,…,i-1],右子树节点个数为[i+1,i+2,…n],所以当i为根节点时,其左子树节点个数为i-1个,右子树节点为n-i,即f(i) = G(i-1)*G(n-i),
上面两式可得:G(n) = G(0)G(n-1)+G(1)(n-2)+…+G(n-1)*G(0)
class Solution {
public int numTrees(int n) {
if(n <= 2) return n;
int[] dp = new int[n +1];
dp[0] = 1;
dp[1] = 1;
//首先一定是遍历节点数,依然节点数为i的状态是依靠 i之前节点数的状态。
// 然后就是遍历i里面每一个数作为头结点的状态了,用j来遍历。
//一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
for(int i = 2; i <= n; i++){
for(int j = 1; j <= i; j++){
dp[i] += (dp[j-1] * dp[i-j]);
}
}
return dp[n];
}
}
222. 完全二叉树的节点个数
countNodes()表示完全二叉树的结点个数,总的结点个数=左子树结点个数+右子树结点个数+1
class Solution {
public int countNodes(TreeNode root) {
if(root == null) return 0;
return countNodes(root.left) + countNodes(root.right) + 1;
}
}
完全二叉树的性质简化遍历次数
性质:除最后一层外,其余层全部铺满;且最后一层向左停靠
如果根节点的左子树深度等于右子树深度,则说明左子树为满二叉树
如果根节点的左子树深度大于右子树深度,则说明右子树为满二叉树
如果知道子树是满二叉树,那么就可以轻松得到该子树的节点数目:(1<<depth) - 1;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int countNodes(TreeNode root) {
if(root == null){
return 0;
}
// 左子树高度
int left = countLevel(root.left);
// 右子树高度
int right = countLevel(root.right);
// 如果左子树高度等于右子树高度,说明左子树是满二叉树。
if(left == right){
// 左子树结点个数实际上是(1<<left) - 1, 这里加上根节点就把1抵消了
// 递归求右子树结点数量+左子树结点数量 + 根节点数量
return countNodes(root.right) + (1<<left);
}else{
return countNodes(root.left) + (1<<right);
}
}
// 一直往左走,统计树的深度
private int countLevel(TreeNode root){
int level = 0;
while(root != null){
level++;
root = root.left;
}
return level;
}
}
98. 验证二叉搜索树
函数表示考虑以 root 为根的子树,判断子树中所有节点的值是否都在 (l,r)的范围内(注意是开区间),在的话继续向下搜索,不在说明不是二叉搜索树
注意:空树也是二叉搜索树
class Solution {
public boolean isValidBST(TreeNode root) {
return validBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean validBST(TreeNode root, long min, long max){
// 空树也是二叉搜索树
if(root == null) return true;
// 子树中所有节点的值不在范围内
if(root.val >= max || root.val <= min){
return false;
}
// 子树中所有节点的值在范围内,继续递归左右子树
// 左子树范围 (min, root.val) 右子树范围 (root.val, max)
return validBST(root.left, min, root.val) && validBST(root.right, root.val, max);
}
}
257. 二叉树的所有路径
class Solution {
List<String> res = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
if(root == null) return res;
dfs(root, new StringBuilder());
return res;
}
public void dfs(TreeNode node, StringBuilder path){
if(node == null){
return;
}
path.append(node.val);
if(node.left == null && node.right == null){
res.add(path.toString());
return;
}else{
dfs(node.left, new StringBuilder(path).append("->"));
dfs(node.right, new StringBuilder(path).append("->"));
}
}
}
124. 二叉树中的最大路径和
class Solution {
int maxSum = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return maxSum;
}
public int maxGain(TreeNode node) {
if (node == null) {
return 0;
}
// 递归计算左右子节点的最大贡献值
// 只有在最大贡献值大于 0 时,才会选取对应子节点
int leftGain = Math.max(maxGain(node.left), 0);
int rightGain = Math.max(maxGain(node.right), 0);
// 节点的最大路径和取决于该节点的值与该节点的左右子节点的最大贡献值
int priceNewpath = node.val + leftGain + rightGain;
// 更新答案
maxSum = Math.max(maxSum, priceNewpath);
// 非空节点的最大贡献值等于节点值与其子节点中的最大贡献值之和
// 返回节点的最大贡献值
return node.val + Math.max(leftGain, rightGain);
}
}
814. 二叉树剪枝
// 将root这棵树剪枝,返回剪枝后的树
public TreeNode pruneTree(TreeNode root) {
if (root == null) return null;
root.left = pruneTree(root.left); // 左子树剪枝,得到剪枝后左子树
root.right = pruneTree(root.right); // 右子树剪枝,得到剪枝后右子树
// 判断决定root结点是否需要剪掉:
if (root.left == null && root.right == null && root.val == 0) return null;
// 返回root这棵树剪枝后的结果
return root;
}
class Solution {
public TreeNode pruneTree(TreeNode root) {
// root为根的树包含1嘛? 包含就返回root,不包含的话自己本身也要剪枝
return containsOne(root) ? root : null;
}
// 判断以node为根的树中是否包含1
public boolean containsOne(TreeNode node){
if(node == null) return false;
// 递归判断node的左右子树是否包含1
boolean left = containsOne(node.left);
boolean right = containsOne(node.right);
// node左子树不包含1,剪枝
if(!left) node.left = null;
if(!right) node.right = null;
// 左子树或者右子树包含1,或者node的值为1,此时返回true
return left || right || node.val == 1;
}
}
1026. 节点与其祖先之间的最大差值
使用全局变量保存最大差值,进行dfs,每次递归查找当前路径的最大值和最小值,到达叶子节点时使用当前路径的最大值和最小值更新全局变量。
class Solution {
int res = Integer.MIN_VALUE;
public int maxAncestorDiff(TreeNode root) {
if (root == null) return 0;
//如果当前节点没有子节点,则直接返回
helper(root, root.val, root.val);
return res;
}
/**
* 每条从根节点到叶子节点的路径中的最大值和最小值,并求出差值更新全局变量
*/
private void helper(TreeNode node, int max, int min) {
if (node == null) return;
max = Math.max(node.val, max);
min = Math.min(node.val, min);
//到达叶子节点,求最大差值
if (node.left == null && node.right == null) {
res = Math.max(res, Math.abs(max - min));
}
helper(node.left, max, min);
helper(node.right, max, min);
}
}
class Solution {
public int maxAncestorDiff(TreeNode root) {
int left = dfs(root.left, root.val, root.val);
int right = dfs(root.right, root.val, root.val);
return Math.max(left, right);
}
public int dfs(TreeNode root, int max, int min) {
if (root == null)
return 0;
max = Math.max(root.val, max);
min = Math.min(root.val, min);
if (root.left == null && root.right == null)
return max - min;
int left = dfs(root.left, max, min);
int right = dfs(root.right, max, min);
return Math.max(left, right);
}
}