← 返回编程题库
coding-min-routers-binsearch-load-cap困难免费版2000ms未尝试

在 BFD 负载封顶分配器下二分搜索最小路由器数量

Smallest Router Count under a BFD Load-Cap Assigner via Bisection

开始编码

实现 solution(job_loads: list[int], load_cap: int) -> int。交易桌当前有 N = len(job_loads) 笔不可拆分的子单待路由,第 i 笔子单的整数负载成本为 job_loads[i](每笔订单线性累加到路由器的累计负载;源自 venue 连接器对该笔订单的资源估算),并且必须完整分配到某一台路由器(不允许跨路由器拆分)。所有路由器的累计负载上限相同,均为 load_cap。求**最小的整数 K(范围 [1, N])**,使得确定性的 best-fit-decreasing 分配器能把全部 N 笔订单装入 K 台路由器,且每台路由器的累计负载不超过 load_cap。该分配器由契约固定(下文给出),因此答案是「在该特定贪心策略下」可行的最小 K — 并非最优装箱意义下的最小 K。

分配器规则:把 job_loads 按降序排序;按该顺序处理每笔订单,放到当前累计负载最小且仍有 cap-room 的路由器(load_cap - current_load >= job);若所有已开路由器都没有 room,则新开一台路由器(消耗 K 预算中的一台);若 K 已开满且都没有 room,分配器失败。仅当存在某个 job_loads[i] 严格大于 load_cap 时才返回 -1 — 即便 K = N(每笔订单独占一台路由器),单台路由器也无法容纳超额负载。

关键的算法形态是对整数 K 在单调可行性谓词上做二分。定义 feasibility(K) 为 TRUE 当且仅当 BFD 分配器能把每笔订单装入 K 台路由器。由于分配器的决策只依赖当前已开路由器负载堆,不依赖 K 预算本身,多给一台路由器空闲槽只能让贪心至少不更差,即 feasibility(K+1) >= feasibility(K)。谓词在 [1, N] 上呈 FALSE-then-TRUE;对 K 二分搜索最小 TRUE。从 K = 1N 顺序枚举 feasibility(K) 的朴素法在 N = 1500 时是 O(N^2 log N);二分省下一个 log N 因子,可在 2 秒内完成。

solution([10, 20, 30], 50) 返回 2。降序排列:[30, 20, 10]。当 K = 1 时,先把 30 放到 router-1(负载 30);处理 20 时,最空的 router-1 余量 50 - 30 = 20,装入,router-1 负载变 50;处理 10 时,router-1 余量 0,无路由器有空,K 预算已耗尽,分配失败。当 K = 2 时,前两步同前(router-1 负载到 50);第三步 router-1 没空,新开 router-2 负载 10,成功。所以 feasibility(1) 为 FALSE,feasibility(2) 为 TRUE — 最小 TRUE 的 K 等于 2。

同一组输入若 load_cap = 60,总负载等于 60,一台路由器即可装下,故 solution([10, 20, 30], 60) 返回 1;若 load_cap = 25,30 这笔已超过上限,solution([10, 20, 30], 25) 直接返回 -1,无需启动二分。

请参考 stubs/stub.py 的函数骨架。

实践背景

这是一个路由器容量规划工具:已知交易桌排队中的子单工作量(每笔子单携带一个由 venue 连接器估算出的整数负载成本 — token-bucket 成本、消息速率预算或类似的不可拆分的逐笔资源消耗指标),以及 venue 会话强制的单台路由器累计负载上限,desk 需要知道至少要预热多少台路由器,才能让 OMS 内置的确定性负载均衡器把队列中每一笔订单都成功落位、不让任何一台 session 溢出。「确定性贪心」是关键约束:生产系统不会在排单时跑离线装箱优化,而是直接调用 OMS 内置的固定 BFD 风格分配器,因此真正要回答的问题是「在这个分配器下,最少多少台路由器能跑完」而非「最优装箱解是几」。二分的形态使 N 达到 1500 笔子单时仍能在 O(N log^2 N) 内给出答案。-1 哨兵刻画的是无法靠扩容修复的退化情形 — 某笔子单的单笔负载已经超过了 venue 会话能承受的上限,无论开多少路由器它都不可能落位 — 这种情形被分配器捕获后回送为告警,而不是在分配器内部抛异常崩溃。本题与基于时间区间(并发重叠)驱动并发数的路由器排程类问题有本质区别:这里的形态是基于负载累加的装箱问题,绑定 cap 的量是累计负载本身,而非区间重叠。

约束条件

  • 1 <= N == len(job_loads) <= 1500;每个 job_loads[i] 为正整数,1 <= job_loads[i] <= 1000000
  • 1 <= load_cap <= 1000000000(单台路由器的整数累计负载上限)
  • 输出是使确定性 BFD 贪心(按负载降序;每个作业放到当前累计负载最小且仍有 cap-room 的路由器;若无路由器有空则新开一台)能够装下所有作业的最小整数 K(范围 [1, N]);若存在 job_loads[i] > load_cap 则返回 -1
  • 哨兵契约(不抛异常):若任一 job_loads[i] > load_cap 返回 -1;函数永不抛异常
  • 参考解法复杂度:O(N log^2 N) — 一次 O(N log N) 降序排序,然后 O(log N) 轮二分,每轮跑一次基于已开路由器负载小顶堆的 O(N log N) BFD 可行性检查

样例

Case 1 · statement-example: three jobs cap=50 needs K=2

输入: [[10,20,30],50]

期望: 2

排序降序得 [30,20,10];K=1 时 r1=30,然后 30+20=50 装入,再来 10 时 50+10>50 失败;K=2 时第三笔 10 落到新开的路由器,可行,故最小 K=2。

Case 2 · visible: load_cap >= sum collapses to single router

输入: [[10,20,30],60]

期望: 1

总负载 60 等于上限 60,一台路由器即可装下所有作业,故 K=1。

Case 3 · visible: oversized single job triggers -1 sentinel

输入: [[10,20,30],25]

期望: -1

30 > 25,即便每个作业独占一台路由器仍无法容纳 30,触发 -1 哨兵返回。

Case 4 · visible: every job above half-cap forces one router per job

输入: [[7,5,4,7],7]

期望: 4

所有作业负载都严格大于 cap/2 = 3.5(7,5,4,7),任意两笔合计都超过 cap=7,因此不可能共享路由器,K=N=4。

最近提交

还没有提交记录。

编码区

实现 solution(...)。本地运行当前支持 Python 可见样例;服务端提交会运行可见样例和隐藏测试。

加载编辑器...
计时0:00

默认展示公开样例。点击「运行样例」后会在这里显示实际输出;点击「提交评测」会进入隐藏测试。

Case 1 · statement-example: three jobs cap=50 needs K=2

输入: [[10,20,30],50]

期望: 2

排序降序得 [30,20,10];K=1 时 r1=30,然后 30+20=50 装入,再来 10 时 50+10>50 失败;K=2 时第三笔 10 落到新开的路由器,可行,故最小 K=2。

Case 2 · visible: load_cap >= sum collapses to single router

输入: [[10,20,30],60]

期望: 1

总负载 60 等于上限 60,一台路由器即可装下所有作业,故 K=1。

Case 3 · visible: oversized single job triggers -1 sentinel

输入: [[10,20,30],25]

期望: -1

30 > 25,即便每个作业独占一台路由器仍无法容纳 30,触发 -1 哨兵返回。

Case 4 · visible: every job above half-cap forces one router per job

输入: [[7,5,4,7],7]

期望: 4

所有作业负载都严格大于 cap/2 = 3.5(7,5,4,7),任意两笔合计都超过 cap=7,因此不可能共享路由器,K=N=4。