二叉树
思路【labuladong】
1)是否可以通过遍历一遍二叉树得到答案?如果可以,用一个traverse函数配合外部变量来实现——回溯
2)是否可以定义一个递归函数,通过子问题的答案推导出原问题的答案?如果可以,写出这个递归函数的定义,并充分利用这个函数的返回值——动态规划
*)如果单独抽出一个二叉树节点,它需要做什么事情?需要在什么时候(前/中/后序)做?
1. 二叉树的遍历方式
1)二叉树的前序遍历144简单
// 递归遍历
class Solution {// 定义结果列表List<Integer> result = new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {// 启动递归traverse(root);return result;}public void traverse(TreeNode root) {// 递归结束条件if (root == null) {return;}// 将root的值加入结果列表result.add(root.val);// 递归遍历左子树traverse(root.left);// 递归遍历右子树traverse(root.right);}
}// 迭代:使用栈
class Solution {public List<Integer> preorderTraversal(TreeNode root) {// 保存结果列表List<Integer> res = new ArrayList<Integer>();// 借助栈实现迭代Deque<TreeNode> stk = new LinkedList<TreeNode>();while (root != null || !stk.isEmpty()) {while (root != null) {stk.push(root);// 前序res.add(root.val);root = root.left;}root = stk.pop();root = root.right;}return res;}
}
2)二叉树的中序遍历94简单
// 递归
// class Solution {
// public List<Integer> inorderTraversal(TreeNode root) {
// // 保存结果
// List<Integer> result = new ArrayList<>();
// // 开始递归
// inorder(root, result);
// return result;
// }// public void inorder(TreeNode root, List<Integer> result) {
// if (root == null) {
// return;
// }
// // 中序
// inorder(root.left, result);
// result.add(root.val);
// inorder(root.right, result);
// }
// }// 迭代
class Solution {public List<Integer> inorderTraversal(TreeNode root) {// 结果列表List<Integer> result = new ArrayList<>();// 栈Deque<TreeNode> stack = new LinkedList<>();// 迭代while (root != null || !stack.isEmpty()) {// 左下进栈while (root != null) {stack.push(root);root = root.left;}// 中序root = stack.pop();result.add(root.val);// 右子树存在入栈,不存在弹出栈中元素继续遍历root = root.right;}return result;}
}
3)二叉树的后序遍历145简单
// 递归
class Solution {List<Integer> res = new ArrayList<Integer>();public List<Integer> postorderTraversal(TreeNode root) {traverse(root);return res;}public void traverse(TreeNode root) {if (root == null) {return;}traverse(root.left);traverse(root.right);res.add(root.val);}
}// 迭代
class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<Integer>();Deque<TreeNode> stk = new LinkedList<TreeNode>();// 用prev记录访问历史,看右子树是否已经被访问过,能否将其弹出TreeNode prev = null;while (root != null || !stk.isEmpty()) {while (root != null) {stk.push(root);root = root.left;}root = stk.pop();// 右子树已经被访问过if (root.right == null || prev == root.right) {res.add(root.val);// 更新历史访问记录,标记该子树已经访问完了prev = root;// 回溯root = null;} else {// 如果右子树没有被访问,将当前节点压栈,访问右子树stk.push(root);root = root.right;}}return res;}
}
4)二叉树的层序遍历
class Solution {public List<List<Integer>> levelOrder(TreeNode root) {// 思路:定义一个队列,在弹出一个节点的同时将其左右子树加入;每层要放在同一个list中,计算长度确定// 定义队列Queue<TreeNode> q = new LinkedList<>();// 定义结果集List<List<Integer>> result = new ArrayList<List<Integer>>();if (root == null) {return result; }q.offer(root);while (!q.isEmpty()) {List<Integer> level = new ArrayList<>();int len = q.size();for (int i = 0; i < len; i++) {TreeNode node = q.poll();level.add(node.val);if (node.left != null) {q.offer(node.left);}if (node.right != null) {q.offer(node.right);}}result.add(level);}return result;}
}
2. 二叉树的属性
1)对称二叉树101简单
// 广度:每一层都是一个回文// 深度:遍历顺序反一下,左子树是左中右,右子树是右中左
class Solution {public boolean isSymmetric(TreeNode root) {return check(root, root);}// 递归判断节点值是否相等public boolean check(TreeNode leftNode, TreeNode rightNode) {if (leftNode == null && rightNode == null) {return true;}if (leftNode == null || rightNode == null) {return false;}return (leftNode.val == rightNode.val) && check(leftNode.left, rightNode.right) && check(leftNode.right, rightNode.left);}
}
2)二叉树的最大深度104简单
// 广度优先
// class Solution {
// public int maxDepth(TreeNode root) {
// // 思路:层次遍历,每一层的时候,深度加1
// if (root == null) {
// return 0;
// }
// int deep = 0;
// Queue<TreeNode> q = new LinkedList<>();
// q.offer(root);
// while (!q.isEmpty()) {
// // deep ++;
// int len = q.size();
// System.out.println(len);
// for (int i = 0; i < len; i++) {
// TreeNode node = q.poll();
// if (node.left != null) {
// q.offer(node.left);
// }
// if (node.right != null) {
// q.offer(node.right);
// }
// }
// deep ++;
// }
// return deep;
// }
// }// // 深度优先
// // 分解的思路
// class Solution {
// public int maxDepth(TreeNode root) {
// // 思路:递归,空节点退出,一个过程是左右子树的最大值+1
// if (root == null) {
// return 0;
// } else {
// int leftH = maxDepth(root.left);
// int rightH = maxDepth(root.right);
// return Math.max(leftH, rightH) + 1;
// }
// }
// }// 遍历的思路
class Solution {// 最大深度int maxDepth = 0;// 当前深度int depth = 0;public int maxDepth(TreeNode root) {// 思路:递归,空节点退出,一个过程是左右子树的最大值+1traverse(root);return maxDepth;}public void traverse(TreeNode root) {if (root == null) {return;}depth++;maxDepth = Math.max(maxDepth, depth);traverse(root.left);traverse(root.right);depth--;}
}
3)二叉树的最小深度111简单
// 递归// 分解
// class Solution {
// // 函数定义:root节点的最小深度
// public int minDepth(TreeNode root) {
// // 退出条件
// if (root == null) {
// return 0;
// }
// if (root.left == null) {
// return minDepth(root.right) + 1;
// }
// if (root.right == null) {
// return minDepth(root.left) + 1;
// }
// return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
// }
// }// 递归
class Solution {// int depth = 0;// int minDepth = Integer.MAX_VALUE;public int minDepth(TreeNode root) {int result = traverse(root);return result;}public int traverse(TreeNode root) {if (root == null) {return 0;}if (root.left == null) {return traverse(root.right) + 1;}if (root.right == null) {return traverse(root.left) + 1;}int l = traverse(root.left);int r = traverse(root.right);return Math.min(l, r) + 1;}
}
4)完全二叉树的节点个数222简单
// // 遍历
// class Solution {
// int result;
// public int countNodes(TreeNode root) {
// traverse(root);
// return result;
// }
// public void traverse(TreeNode root) {
// if (root == null) {
// return;
// }
// // int l = traverse(root.left) + 1;
// // int r = traverse(root.right) + 1;
// traverse(root.left);
// traverse(root.right);
// result ++;
// // return l + r;
// }
// }// 分解
class Solution {// 函数定义:根节点的个数public int countNodes(TreeNode root) {if(root == null) {return 0;}// 左节点的个数int l = countNodes(root.left);int r = countNodes(root.right);return l + r + 1;}
}
5)平衡二叉树110简单
// // 遍历——自顶向下
// class Solution {
// // 左子树是平衡二叉树,右子树是平衡二叉树,左右子树高度差不超过1
// public boolean isBalanced(TreeNode root) {
// if (root == null) {
// return true;
// }
// boolean result = (Math.abs(traverse(root.left) - traverse(root.right)) <= 1);
// return isBalanced(root.left) && isBalanced(root.right) && result;
// }
// // 求深度
// public int traverse(TreeNode root) {
// if (root == null) {
// return 0;
// }
// int l = traverse(root.left);
// int r = traverse(root.right);
// return Math.max(l, r) + 1;
// }
// }// 自底向上——这里用int返回值既解决了二叉树深度的问题,也同时可以判断是否是平衡二叉树
class Solution {public boolean isBalanced(TreeNode root) {return height(root) >= 0;}public int height(TreeNode root) {if (root == null) {return 0;}int leftHeight = height(root.left);int rightHeight = height(root.right);// 判断是否平衡if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {return -1;} else {return Math.max(leftHeight, rightHeight) + 1;}}
}
6)二叉树的所有路径257简单
// 遍历,自顶向下,因为路径是从上到下的顺序
class Solution {List<String> result = new ArrayList<>();// 返回二叉树所有路径public List<String> binaryTreePaths(TreeNode root) {constructPath(root, "");return result;}// 遍历二叉树public void constructPath(TreeNode root, String path) {if (root == null) {return;}StringBuffer pathnow = new StringBuffer(path);pathnow.append(Integer.toString(root.val));if (root.left == null && root.right == null) {result.add(pathnow.toString());} else {pathnow.append("->");constructPath(root.left, pathnow.toString());constructPath(root.right, pathnow.toString());}}
}
7)左叶子之和404简单
// 遍历:遍历到左节点的时候就加
class Solution {// 和int sum = 0;public int sumOfLeftLeaves(TreeNode root) {traverse(root, 0);return sum;}public void traverse(TreeNode root, int flag) {if (root == null) {return;}if (root.left == null && root.right == null && flag == 1) {sum += root.val;}traverse(root.left, 1);traverse(root.right, 0);}
}
8)找树左下角的值513中等
// 遍历
class Solution {int curVal = 0;int curHeight = 0;public int findBottomLeftValue(TreeNode root) {traverse(root, 0);return curVal;}public void traverse(TreeNode root, int height) {if (root == null) {return;}// 高度加1height++;traverse(root.left, height);traverse(root.right, height);// 记录下每层的第一个节点if (height > curHeight) {curHeight = height;curVal = root.val;}}
}
9)路径总和112简单
// class Solution {
// public boolean hasPathSum(TreeNode root, int targetSum) {
// return traverse(root, targetSum, 0);// }
// public boolean traverse (TreeNode root, int targetSum, int sum) {
// if (root == null) {
// return false;
// }
// sum += root.val;
// traverse(root.left, targetSum, sum);
// traverse(root.right, targetSum, sum);
// if (root.left == null && root.right == null && targetSum == sum) {
// return true;
// }// }
// }class Solution {// 函数定义:root节点到叶子节点的路径和是sumpublic boolean hasPathSum(TreeNode root, int sum) {if (root == null) {return false;}if (root.left == null && root.right == null) {return sum == root.val;}return hasPathSum(root.left, sum - root.val) || hasPathSum(root.right, sum - root.val);}
}
3. 二叉树的修改与改造
1)翻转二叉树226简单
// class Solution {
// // 翻转以root为根的二叉树,返回root节点
// public TreeNode invertTree(TreeNode root) {
// if (root == null) {
// return root;
// }
// // change(root);
// // 翻转左右节点
// TreeNode temp = root.left;
// root.left = root.right;
// root.right = temp;
// // 递归左右节点
// invertTree(root.left);
// invertTree(root.right);
// // change(root);
// return root;
// }// public void change(TreeNode root) {
// TreeNode temp = root.left;
// root.left = root.right;
// root.right = temp;
// }
// }class Solution {// 翻转以root为根的二叉树,返回root节点public TreeNode invertTree(TreeNode root) {return traver(root);}public TreeNode traver(TreeNode root) {if (root == null) {return root;}// 交换左右节点TreeNode temp = root.left;root.left = root.right;root.right = temp;// 继续遍历traver(root.left);traver(root.right);return root;}
}
2)构造二叉树——需要画图搞清楚下标
①从前序与中序遍历序列构造二叉树105中等
// 前序的第一个节点是根节点,从中序中找到左右子树的分界点
class Solution {// 由中序遍历构建一个HashMap,存放节点值与索引的对应HashMap<Integer, Integer> valToIndex = new HashMap<>();public TreeNode buildTree(int[] preorder, int[] inorder) {// 填充mapfor (int i = 0; i < inorder.length; i++) {valToIndex.put(inorder[i], i);}// 递归构建二叉树return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);}public TreeNode build(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {// 结束条件if (preStart > preEnd) {return null;}int rootVal = preorder[preStart];int index = valToIndex.get(rootVal);TreeNode root = new TreeNode(rootVal);root.left = build(preorder, preStart + 1, preStart + (index - inStart), inorder, inStart, index - 1);root.right = build(preorder, (preStart +index - inStart) + 1, preEnd, inorder, index + 1, inEnd);return root;}
}
②从中序与后序遍历序列构造二叉树106中等
class Solution {// 定义一个map存放中序遍历中值到索引的映射,方便后续找左右节点分界HashMap<Integer, Integer> valToIndex = new HashMap<>();public TreeNode buildTree(int[] inorder, int[] postorder) {// 遍历中序数组for (int i = 0; i < inorder.length; i++) {valToIndex.put(inorder[i], i);}// 递归构建二叉树return build(inorder, 0, inorder.length - 1, postorder, 0, postorder.length - 1);}public TreeNode build (int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {// 结束条件if (inStart > inEnd) {return null;}// root 节点的值就是后序遍历数组的最后一个元素System.out.println(111);int rootVal = postorder[postEnd];// 找出rootVal在中序遍历中的索引,就可以分开左右子树int index = valToIndex.get(rootVal);TreeNode root = new TreeNode(rootVal);root.left = build(inorder, inStart, index - 1, postorder, postStart, postStart + (index - inStart) - 1);root.right = build(inorder, index + 1, inEnd, postorder, postStart + (index - inStart), postEnd - 1);return root;}
}
③根据前序和后序遍历构造二叉树889中等
class Solution {// 定义一个HashMap,存储postorder中值到索引的映射HashMap<Integer, Integer> valToIndex = new HashMap<>();public TreeNode constructFromPrePost(int[] preorder, int[] postorder) {// 填充HashMapfor (int i = 0; i < postorder.length; i++) {valToIndex.put(postorder[i], i);}// 递归构造二叉树return build(preorder, 0, preorder.length - 1, postorder, 0, postorder.length - 1);}// 递归构造二叉树的算法public TreeNode build(int[] preorder, int preStart, int preEnd, int[] postorder, int postStart, int postEnd) {// 递归结束条件if (preStart > preEnd) {return null;}// 只剩一个根节点if (preStart == preEnd) {return new TreeNode(preorder[preStart]);}// 根节点int rootVal = preorder[preStart];// 左子树的根节点int leftRootVal = preorder[preStart + 1];// 在后续遍历中寻找左子树根节点的位置,划分左右子树int index = valToIndex.get(leftRootVal);// 左子树的元素个数int leftSize = index - postStart + 1;// 构造根节点TreeNode root = new TreeNode(rootVal);root.left = build(preorder, preStart + 1, preStart + leftSize, postorder, postStart, postStart + leftSize);root.right = build(preorder, preStart + leftSize + 1, preEnd, postorder, index + 1, postEnd - 1);return root;}
}
3)最大二叉树654中等
// 思路:计算数组的最大值,作为根节点,分别计算出左右的子数组,递归构建树// 感想:增加参数个数,可以简化中间计算
class Solution {public TreeNode constructMaximumBinaryTree(int[] nums) {// 本次数组的长度int len = nums.length;if (len == 0) {return null;}// 计算本数组的最大值,以及最大值的位置(方便后续分割成两个子数组)int maxVal = -1;int maxIndex = -1;for (int i = 0; i < len; i++) {if (nums[i] > maxVal) {maxVal = nums[i];maxIndex = i;}}// 给根节点赋值TreeNode root = new TreeNode(maxVal);// 左子树构建需要的子数组int[] leftNums = new int[maxIndex];for (int i = 0; i < maxIndex; i++) {leftNums[i] = nums[i];}root.left = constructMaximumBinaryTree(leftNums);// 右子树构建需要的子数组int[] rightNums = new int[len - maxIndex - 1];for (int i = 0; i < len - maxIndex - 1; i++) {rightNums[i] = nums[maxIndex + 1 + i];}root.right = constructMaximumBinaryTree(rightNums);return root;// return build(nums, 0, nums.length - 1);}// public TreeNode build(int[] nums, int lo, int hi) {// // int len = nums.length;// // if (len == 0) {// // return null;// // }// if (lo > hi) {// return null;// }// int maxVal = -1;// int maxIndex = -1;// for (int i = lo; i <= hi; i++) {// if (nums[i] > maxVal) {// maxVal = nums[i];// maxIndex = i;// }// }// TreeNode root = new TreeNode(maxVal);// root.left = build(nums, lo, maxIndex - 1);// root.right = build(nums, maxIndex + 1, hi);// return root;// // int[] leftNums = new int[];// // for (int i = 0; i <= hi - lo + 1; i++) {// // leftNums[i] = nums[i + lo];// // }// // root.left = constructMaximumBinaryTree(leftNums);// // int[] rightNums = new int[len - maxIndex - 1];// // for (int i = 0; i < len - maxIndex - 1; i++) {// // rightNums[i] = nums[len - maxIndex + 1 + i];// // }// // root.right = constructMaximumBinaryTree(rightNums);// }
}
4)合并二叉树617简单
class Solution {// 函数定义:合并两棵树public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if(root1 == null && root2 == null) {return root1;}if (root1 == null && root2 != null) {return root2;}if (root1 != null && root2 == null) {return root1;}root1.val = root1.val + root2.val;root1.left = mergeTrees(root1.left, root2.left);root1.right = mergeTrees(root1.right, root2.right);return root1;}
}
5)二叉树展开为链表114中等
// 思路一:遍历,新构造一个二叉树,不断增加节点——函数返回值为void——>希望在原本二叉树上进行操作// 思路二:分解
class Solution {// 函数定义:将以root节点为根的二叉树拉平public void flatten(TreeNode root) {if (root == null) {return;}// 将左子树拉平flatten(root.left);// 将右子树拉平flatten(root.right);// 取到拉平后的左右子树TreeNode letfNode = root.left;TreeNode rightNode = root.right;// 将左子树连接到root的右子树root.left = null;root.right = letfNode;// 将原右子树拼接到尾部TreeNode p = root;while (p.right != null) {p = p.right;}p.right = rightNode;}
}
4. 二叉搜索树的属性
1)验证二叉搜索树98中等
// 思路:左子树根节点 < 根节点 < 右子树根节点 && 左右子树都是二叉搜索树
class Solution {// 记录上一个节点的值,初始值为long的最小值long pre = Long.MIN_VALUE;public boolean isValidBST(TreeNode root) {return traverse(root);}public boolean traverse(TreeNode root) {if (root == null) {return true;}// 左子树boolean leftT = traverse(root.left);if (root.val <= pre) return false;pre = root.val;// 右子树boolean rightT = traverse(root.right);// 左子树是二叉搜索树,右子树是二叉搜索树,左子树 < 根 < 右子树return leftT && rightT;}
}
2)把二叉搜索树转换为累加树538中等
// 思路:遍历二叉树,利用额外的变量记录二叉树的累计和
class Solution {int sum = 0;public TreeNode convertBST(TreeNode root) {traverse(root);return root;}public void traverse(TreeNode root) {if (root == null) {return;}traverse(root.right);sum += root.val;root. val = sum;traverse(root.left);}
}
3)二叉搜索树中第K小的元素230中等
class Solution {// 记录排名int rank = 0;// 记录结果int result = 0;public int kthSmallest(TreeNode root, int k) {traverse(root, k);return result;}// 中序遍历public void traverse(TreeNode root, int k) {if (root == null) {return;}traverse(root.left, k);rank++;if (rank == k) {result = root.val;return;}traverse(root.right, k);} }
5. 二叉树公共祖先问题
1)二叉树的最近公共祖先236中等
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 递归结束条件if (root == null || root == p || root == q) {return root;}// 后序遍历TreeNode leftT = lowestCommonAncestor(root.left, p, q);TreeNode rightT = lowestCommonAncestor(root.right, p, q);// 如果没有找到节点p或qif (leftT == null && rightT == null) {return null;} else if (leftT == null && rightT != null) {return rightT;} else if (leftT != null && rightT == null) {return leftT;} else {return root;}}
}
2)二叉搜索树的最近公共祖先235中等
// 思路:root应该在pq之间
class Solution {public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {// 如果根节点大于p、q,就往右子树里搜索if (p.val > root.val && q.val > root.val) {return lowestCommonAncestor(root.right, p, q);}// 如果根节点小于p、q,就往左子树里搜索if (p.val < root.val && q.val < root.val) {return lowestCommonAncestor(root.left, p, q);}return root;}
}
6. 二叉搜索树的修改与构造
1)将有序数组转换为二叉搜索树108简单
class Solution {public TreeNode sortedArrayToBST(int[] nums) {TreeNode root = traverse(nums, 0, nums.length - 1);return root;}public TreeNode traverse(int[] nums, int start, int end) {if (start > end) {return null;}int index = (start + end) / 2;TreeNode root = new TreeNode(nums[index]);root.left = traverse(nums, start, index - 1);root.right = traverse(nums, index + 1, end);return root;}
}
2)删除二叉搜索树中的节点450中等
class Solution {public TreeNode deleteNode(TreeNode root, int key) {// 递归结束条件if (root == null) {return null;}// 找到了要删除if (root.val == key) {// 1. 左右子树都为空, 直接删if (root.left == null && root.right == null) {return null;}// 2. 左子树为空,右子树接替;右子树为空,左子树接替if (root.left == null) {return root.right;}if (root.right == null) {return root.left;}// 3. 左右子树都不为空,使用右子树的最小节点接替TreeNode minNode = getMin(root.right);root.right = deleteNode(root.right, minNode.val);minNode.left = root.left;minNode.right = root.right;root = minNode;} else if (root.val > key) {// 要往小了找root.left = deleteNode(root.left, key);} else if (root.val < key) {// 要往大了找root.right = deleteNode(root.right, key);}return root;}// 找二叉搜索树中最小的节点public TreeNode getMin(TreeNode root) {while (root.left != null) {root = root.left;}return root;}
}
[未完待续……]