7. LintCode 分治题目(一)
LintCode 597:具有最大平均数的子树
https://www.lintcode.com/problem/subtree-with-maximum-average
描述
给一棵二叉树,找到有最大平均值的子树。返回子树的根结点。
LintCode会打印出根结点为你返回节点的子树,保证有最大平均数子树只有一棵
样例
输入:{1,-5,11,1,2,4,-2}输出:11说明:这棵树如下所示:1/ \-5 11/ \ / \1 2 4 -211子树的平均值是4.333,为最大的。输入:{1,-5,11}输出:11说明:1/ \-5 111,-5,11 三棵子树的平均值分别是 2.333,-5,11。因此11才是最大的
解题思路
和最小子树思路差不多,对一棵子树而言:最大平均值要么是它本身,要么在左子树中或者右子树中。
AC代码
public class Solution {// 记录答案TreeNode ans;// 初始化一个非常小的值double ave = Integer.MIN_VALUE;public TreeNode findSubtree2(TreeNode root) {if (root == null) { return null; }// 遍历树findTree(root);return ans;}// 计算以root为根的子树的平均值private Info findTree(TreeNode root) {int count = 1;int add = root.val;// 查找左子树if (root.left != null) {Info left = findTree(root.left);count += left.count;add += left.add;}// 查找右子树if (root.right != null) {Info right = findTree(root.right);count += right.count;add += right.add;}// 计算当前子树的平均值,更新答案double x = (double) add / count;if (x > ave) {ans = root;ave = x;}return new Info(count, add);}}// 想要知道返回值,需要两个信息,子树的节点个数和子树节点值的和class Info {int count;int add;Info(int count, int add) {this.add = add;this.count = count;}}
LintCode 174:翻转二叉树
https://www.lintcode.com/problem/invert-binary-tree
描述
样例
输入: {1,3,#}输出: {1,#,3}解释:1 1/ => \3 3输入: {1,2,3,#,#,4}输出: {1,3,2,#,4}解释:1 1/ \ / \2 3 => 3 2/ \4 4
解题思路
对一个节点:
- 交换它的左右子树
- 递归交换左右子树
AC代码
public void invertBinaryTree(TreeNode root) {if (root == null) {return;}// 交换左右子树TreeNode tmp = root.left;root.left = root.right;root.right = tmp;// 递归交换左右子树invertBinaryTree(root.left);invertBinaryTree(root.right);}
LintCode 95:验证二叉搜索树
https://www.lintcode.com/problem/validate-binary-search-tree
描述
给定一个二叉树,判断它是否是合法的二叉查找树(BST)
一棵BST定义为:
- 节点的左子树中的值要严格小于该节点的值。
- 节点的右子树中的值要严格大于该节点的值。
- 左右子树也必须是二叉查找树。
- 一个节点的树也是二叉查找树。
样例
```java 输入:{-1} 输出:true 解释: 二叉树如下(仅有一个节点): -1 这是二叉查找树。
输入:{2,1,4,#,#,3,5} 输出:true 解释: 二叉树如下: 2 / \ 1 4 / \ 3 5 这是二叉查找树。
<a name="I0mkM"></a>#### 解题思路两个思路:- 从下往上:对于一个节点,找出它左子树的最大值,应该小于它;找出它右子树的最小值,应该大于它- 从下往上:对于一棵左子树,它所有的值应该小于父亲,对于一棵右子树,它所有的值应该大于父亲,所以每个节点都有一个取值范围,满足这个范围便是二叉搜索树<a name="FiKwd"></a>#### AC代码```javapublic boolean isValidBST(TreeNode root) {return isValid(root, Long.MIN_VALUE, Long.MAX_VALUE);}public boolean isValid(TreeNode root, long minValue, long maxValue) {if (root == null) {return true;}if (root.val <= minValue || root.val >= maxValue) {return false;}// 左子树的最大值应该小于该节点,右子树的最小值应该大于该节点return isValid(root.left, minValue, root.val) && isValid(root.right, root.val, maxValue);}
LintCode 11:二叉查找树搜索区间
https://www.lintcode.com/problem/search-range-in-binary-search-tree
描述
给定一个二叉查找树和范围[k1, k2]。按照升序返回给定范围内的节点值。
样例
输入:{5},6,10输出:[]5它将被序列化为 {5}没有数字介于6和10之间输入:{20,8,22,4,12},10,22输出:[12,20,22]解释:20/ \8 22/ \4 12它将被序列化为 {20,8,22,4,12}[12,20,22]介于10和22之间
解题思路
AC代码
public class Solution {List<Integer> ans = new ArrayList<>();public List<Integer> searchRange(TreeNode root, int k1, int k2) {search(root, k1, k2);return ans;}private void search(TreeNode root, int k1, int k2) {if (root == null) {return;}// 先搜寻左子树,再搜寻右子树,这样能保持有序search(root.left, k1, k2);if (root.val >= k1 && root.val <= k2) {ans.add(root.val);}search(root.right, k1, k2);}}
LintCode 901:二叉搜索树中最接近的值 II关注问题
https://www.lintcode.com/problem/closest-binary-search-tree-value-ii
描述
给定一棵非空二叉搜索树以及一个target值,找到 BST 中最接近给定值的 k 个数。
- 给出的target值为浮点数
- 你可以假设
k总是合理的,即k ≤ 总节点数 - 我们可以保证给出的 BST 中只有
唯一一个最接近给定值的 k 个值的集合样例
```java 输入: {1} 0.000000 1 输出: [1] 解释: 二叉树 {1},表示如下的树结构: 1
输入: {3,1,4,#,2} 0.275000 2 输出: [1,2] 解释: 二叉树 {3,1,4,#,2},表示如下的树结构: 3 / \ 1 4 \ 2
<a name="kawvt"></a>#### 解题思路1. 二分法篇章中做过一道在排序数组中找最接近的k个数[https://www.lintcode.com/problem/find-k-closest-elements](https://www.lintcode.com/problem/find-k-closest-elements),所以可以先将搜索树中序遍历转换为数组,然后采用该题解法。1. 还是中序遍历,但是不转化成数组了。直接在遍历的过程中查找K个数,维护一个队列,队列最多K个数,为最接近target的k的数,中序遍历树,然后维护队列,队头是离target最远的数,队列不足k个数时,当前节点值直接加入队列,如果超过k个数,当前节点比队头更接近target时,取出,队头,当前节点加入队尾,队列有K个数,并且当前节点距离target比队首更远,表示已经找到了最接近的k个数(剪枝优化)<a name="EpHBF"></a>#### AC代码```javapublic class Solution {// 存储k个接近target的数的队列Deque<Integer> deque = new ArrayDeque<>();public List<Integer> closestKValues(TreeNode root, double target, int k) {// 中序遍历order(root, target, k);List<Integer> list = new ArrayList<>(deque);return list;}private void order(TreeNode root, double target, int k) {if (root == null) { return; }// 中序遍历,保证队首是最远离target的数order(root.left, target, k);if (deque.size() < k) {// 队列不足k个数,当前节点直接加入队列deque.add(root.val);} else {if (Math.abs(deque.peek() - target) > Math.abs(root.val - target)) {// 当前节点比队首更接近target,淘汰队首deque.removeFirst();deque.add(root.val);} else {// 队首已经比当前节点更接近target了,按照中序遍历,后面的节点只会更加远离target,所以剪枝优化return;}}// 中序遍历order(root.right, target, k);}}
LintCode 87:删除二叉查找树的节点
https://www.lintcode.com/problem/remove-node-in-binary-search-tree
描述
给定一棵具有不同节点值的二叉查找树,删除树中与给定值相同的节点。如果树中没有相同值的节点,就不做任何处理。你应该保证处理之后的树仍是二叉查找树。
样例
输入:Tree = {5,3,6,2,4}k = 3输出: {5,2,6,#,4} 或 {5,4,6,2}说明:给定了以下二叉搜索树:5/ \3 6/ \2 4移去3,你可以返回:5/ \2 6\4或5/ \4 6/2输入:Tree = {5,3,6,2,4}k = 4输出: {5,3,6,2}说明:给定了以下二叉搜索树:5/ \3 6/ \2 4移去4,你应该返回5/ \3 6/2
解题思路
删除一个节点,有4种情况:
- 该节点是叶子节点:直接删除
- 该节点只有左儿子:将左儿子与父亲节点连接即可
- 该节点只有右儿子:将右儿子与父亲节点连接即可
- 左右儿子都有
- 从左子树中选出最大的,删掉它,把节点值设为该最大值。
- 从右儿子中选出最小的,删掉它,把节点值设为该最小值。
4种情况简化一下:
- 没有右儿子:将左儿子与父亲节点连接即可
- 有右儿子:找到右儿子中最小的节点,删掉,把节点设为该最小值
AC代码
```java public TreeNode removeNode(TreeNode root, int value) { // 因为root有可能也是删除的点,所以建个哨兵节点,降低代码复杂度,这样删除root的情况不用特殊处理 TreeNode sb = new TreeNode(0); sb.left = root; // 中序遍历找到要删除节点的父亲。 TreeNode father = find(root, value, sb); // 删除节点(没有则不用删除) if (father.left != null && father.left.val == value) {
} else if (father.right != null && father.right.val == value) {delete(father, father.left);
} // 返回数的根节点 return sb.left; }delete(father, father.right);
public TreeNode find(TreeNode root, int value, TreeNode father) { if (root == null) { return father; } if (root.val == value) { return father; } if (root.val < value) { return find(root.right, value, root); } return find(root.left, value, root); }
// 删除 p 的儿子 node,并保持二叉搜索树 public void delete(TreeNode p, TreeNode node) { if (node.right == null) { if (p.left == node) { p.left = node.left; } else { p.right = node.right; } } else { TreeNode tmp = node.right; TreeNode right = node; // 找到右子树中最小的节点 while (tmp.left != null) { right = tmp; tmp= tmp.left; } // 将最小节点与当前节点替换 if (right.right == tmp) { right.right = tmp.right; } else { right.left = tmp.right; } // 从树中删除当前节点 if (p.left == node) { p.left = tmp; } else { p.right = tmp; } // 最小节点接收删除节点的左右儿子 tmp.left = node.left; tmp.right = node.right; }
} ```
