8. LintCode 分治题目(二)

8. LintCode 分治题目(二)

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    -2 
11子树的平均值是4.333,为最大的。

输入:
{1,-5,11}
输出:11
说明:
     1
   /   \
 -5     11
1,-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定义为:

  • 节点的左子树中的值要严格小于该节点的值。
  • 节点的右子树中的值要严格大于该节点的值。
  • 左右子树也必须是二叉查找树。
  • 一个节点的树也是二叉查找树。
样例
输入:{-1}
输出:true
解释:
二叉树如下(仅有一个节点):
	-1
这是二叉查找树。

输入:{2,1,4,#,#,3,5}
输出:true
解释:
	二叉树如下:
	  2
	 / \
	1   4
	   / \
	  3   5
这是二叉查找树。
解题思路

两个思路:

  • 从下往上:对于一个节点,找出它左子树的最大值,应该小于它;找出它右子树的最小值,应该大于它
  • 从下往上:对于一棵左子树,它所有的值应该小于父亲,对于一棵右子树,它所有的值应该大于父亲,所以每个节点都有一个取值范围,满足这个范围便是二叉搜索树
AC代码
public 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}
没有数字介于610之间

输入:{20,8,22,4,12},10,22
输出:[12,20,22]
解释:
        20
       /  \
      8   22
     / \
    4   12
它将被序列化为 {20,8,22,4,12}
[12,20,22]介于1022之间
解题思路

按中序遍历走,在区间内的加入答案。可以剪枝优化。

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 个值的集合
样例
输入:
{1}
0.000000
1
输出:
[1]
解释:
二叉树 {1},表示如下的树结构:
 1
 
输入:
{3,1,4,#,2}
0.275000
2
输出:
[1,2]
解释:
二叉树 {3,1,4,#,2},表示如下的树结构:
  3
 /  \
1    4
 \
  2    
解题思路
  1. 二分法篇章中做过一道在排序数组中找最接近的k个数https://www.lintcode.com/problem/find-k-closest-elements,所以可以先将搜索树中序遍历转换为数组,然后采用该题解法。
  2. 还是中序遍历,但是不转化成数组了。直接在遍历的过程中查找K个数,维护一个队列,队列最多K个数,为最接近target的k的数,中序遍历树,然后维护队列,队头是离target最远的数,队列不足k个数时,当前节点值直接加入队列,如果超过k个数,当前节点比队头更接近target时,取出,队头,当前节点加入队尾,队列有K个数,并且当前节点距离target比队首更远,表示已经找到了最接近的k个数(剪枝优化)
AC代码
public 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
   \
    45
   / \
  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代码
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) {
        delete(father, father.left);
    } else if (father.right != null && father.right.val == value) {
        delete(father, father.right);
    }
    // 返回数的根节点
    return sb.left;
}

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

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值