LCP 10. 二叉树任务调度

任务调度优化是计算机性能优化的关键任务之一。在任务众多时,不同的调度策略可能会得到不同的总体执行时间,因此寻求一个最优的调度方案是非常有必要的。

通常任务之间是存在依赖关系的,即对于某个任务,你需要先完成他的前导任务(如果非空),才能开始执行该任务。我们保证任务的依赖关系是一棵二叉树,其中 root 为根任务,root.left 和 root.right 为他的两个前导任务(可能为空),root.val 为其自身的执行时间。

在一个 CPU 核执行某个任务时,我们可以在任何时刻暂停当前任务的执行,并保留当前执行进度。在下次继续执行该任务时,会从之前停留的进度开始继续执行。暂停的时间可以不是整数。

现在,系统有两个 CPU 核,即我们可以同时执行两个任务,但是同一个任务不能同时在两个核上执行。给定这颗任务树,请求出所有任务执行完毕的最小时间。

示例 1:

LCP 10. 二叉树任务调度

输入:root = [47, 74, 31]

输出:121

解释:根节点的左右节点可以并行执行31分钟,剩下的43+47分钟只能串行执行,因此总体执行时间是121分钟。

示例 2:

LCP 10. 二叉树任务调度

输入:root = [15, 21, null, 24, null, 27, 26]

输出:87

示例 3:

LCP 10. 二叉树任务调度

输入:root = [1,3,2,null,null,4,4]

输出:7.5

这道题虽然代码量很少,但思维难度较高。

在通过对示例的观察后,我们可以得出以下重要结论:

对于任何一颗任务树,它一定有一个先并行后串行的最优策略。树的根结点只能串行。 这个结论的正确性是因为只有在这颗任务树退化成一个链以后,它才不能被并行,所以把串行延后执行不会导致执行时间变长。
设一颗任务树的所有任务时间之和为 xx ,最大并行时间为 yy ,那么这个任务最少需要 x - yx−y 的时间完成。 其中前 yy 秒用于并行,后 x - 2yx−2y 秒用于串行。 注意由于上一条结论,在区间 [x - 2y, x][x−2y,x] 内的所有剩余时间都是可以 只通过 并行取到的。对于叶子节点 node 来说,它的任务总时间即为 node.val ,最大并行时间为 0 。
所有任务时间之和很容易求,下面我们求最大并行时间。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public double minimalExecTime(TreeNode root) {
       return dfs(root)[1];
    }
    private double[] dfs(TreeNode node) {
        if (node == null) {
            return new double[] {0, 0};
        }
        // 先递归左右子树
        double[] resLeft = dfs(node.left);
        double[] resRight = dfs(node.right);
        return new double[] {
                resLeft[0] + resRight[0] + node.val,
                node.val + Math.max(Math.max(resLeft[1], resRight[1]),(resLeft[0] + resRight[0]) / 2.0)
        };
    }
}