任务调度优化是计算机性能优化的关键任务之一。在任务众多时,不同的调度策略可能会得到不同的总体执行时间,因此寻求一个最优的调度方案是非常有必要的。
通常任务之间是存在依赖关系的,即对于某个任务,你需要先完成他的前导任务(如果非空),才能开始执行该任务。我们保证任务的依赖关系是一棵二叉树,其中 root 为根任务,root.left 和 root.right 为他的两个前导任务(可能为空),root.val 为其自身的执行时间。
在一个 CPU 核执行某个任务时,我们可以在任何时刻暂停当前任务的执行,并保留当前执行进度。在下次继续执行该任务时,会从之前停留的进度开始继续执行。暂停的时间可以不是整数。
现在,系统有两个 CPU 核,即我们可以同时执行两个任务,但是同一个任务不能同时在两个核上执行。给定这颗任务树,请求出所有任务执行完毕的最小时间。
示例 1:
输入:root = [47, 74, 31]
输出:121
解释:根节点的左右节点可以并行执行31分钟,剩下的43+47分钟只能串行执行,因此总体执行时间是121分钟。
示例 2:
输入:root = [15, 21, null, 24, null, 27, 26]
输出:87
示例 3:
输入: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) }; } }