多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

二分答案求解“最大化最小值“:LeetCode 2226 分糖果给 k 个小孩(codeforces-go 仓库实现剖析)

二分答案求解“最大化最小值“:LeetCode 2226 分糖果给 k 个小孩(codeforces-go 仓库实现剖析) 科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载本文围绕 LeetCode 第 287 场周赛 T3「Maximum Candies Allocated to K Children」分糖果给 k 个小孩题解文档见 leetcode/weekly/287/c/2226.md系统讲解如何用二分答案解决最大化最小值类问题。你将从能否让每个小孩都至少有 low 颗糖果的单调性判定出发掌握判定函数 check 的推导、开区间二分端点选取与上界优化技巧并结合本仓库的 Go 实现 c.go 与测试用例 c_test.go看懂sort.Search的取反技巧在最大化最小值场景下的应用。问题背景把糖果分给 k 个小孩题面weekly 287 第三题题解给定一个长度为 $n$ 的数组candies其中candies[i]表示第 $i$ 堆糖果的数量。现在要把糖果分给 $k$ 个小孩要求每个小孩恰好得到相同数量的糖果糖果堆只能分割、不能合并即可以从一堆中切出任意数量给若干小孩但不能把两堆拼在一起。求每个小孩最多能拿到多少颗糖果若无法分配返回 $0$。从仓库中题解与实现代码的函数签名可以看出本题的 $k$ 使用 64 位整数Java 的long k、Go 的k int64累加过程也需要注意使用足够宽的整数类型// leetcode/weekly/287/c/c.go func maximumCandies(candies []int, k int64) int对应测试用例c.txt也非常直观输入candies [5,8,6]、k 3答案为5可以切出三堆大小为 5 的糖果剩余部分丢弃输入candies [2,5]、k 11答案为0糖果总数只有 7 颗少于 11 个小孩无法满足每人至少 1 颗返回 0。核心思路从最大化最小值到二分答案这个问题不是问最多能分多少而是等价于每个小孩至少分到 $\textit{low}$ 颗时能否让所有小孩都满足。于是先考虑一个判定型子问题能否让每个小孩都至少有 $\textit{low}$ 颗糖果关键观察是单调性$\textit{low}$ 越大越难实现$\textit{low}$ 越小越容易实现。例如当 $\textit{low}5$ 时可以满足要求而 $\textit{low}6$ 时无法满足那么答案就是 $5$。具有这种单调性的问题就可以二分猜答案——这就是二分答案算法的适用前提该思路在竞赛界常配合红蓝染色法理解满足条件的一端涂一种颜色不满足的一端涂另一种颜色答案就是红蓝分界点。因为糖果堆只能分割不能合并对于每一堆candies[i]当每人分 $\textit{low}$ 颗时最多能分出$$ c\left\lfloor\dfrac{\textit{candies}[i]}{\textit{low}}\right\rfloor $$个大小为 $\textit{low}$ 的糖果堆即满足 $c$ 个小孩。对全部堆求和只要$$ \sum_{i0}^{n-1} \left\lfloor\dfrac{\textit{candies}[i]}{\textit{low}}\right\rfloor \ge k $$就说明每个小孩都可以拿到至少 $\textit{low}$ 颗糖果此时答案还能更大于是增大二分左边界 $\textit{left}$否则需要减小右边界 $\textit{right}$。这就是典型的最大化最小值Maximize the Minimum二分答案模型仓库的算法模板 copypasta/sort.go 也把这类题目单列为「最大化最小值二分下界 low1…」一档本题正是这一模型的标准例题。二分查找细节开区间、端点初始化与上界优化原题解采用开区间二分即left是已知满足要求的答案下界、right是已知不满足要求的上界循环不变量为left 答案 ≤ right此处具体写法为left 1 right时退出。这只是一种写法闭区间或半闭半开区间同样可以关键是维护好单调性与区间不变量。三个初始化细节直接决定了二分的正确性与效率开区间左端点初始值为 $0$一颗糖果都不分每人 0 颗显然满足至少有 0 颗所以 $0$ 一定是可行解作为下界开区间右端点初始值为 $\max(\textit{candies}) 1$由于糖果堆只能分割不能合并任何小孩拿到超过最大单堆数量的糖果都是不可能的即 $\max(\textit{candies})$ 一定不可行加 1 作为上界右端点上界优化设 $\textit{avg} \left\lfloor\dfrac{\sum_{i} \textit{candies}[i]}{k}\right\rfloor$因为总量只有这么多每个小孩均分也不可能超过 $\textit{avg}$所以 $\textit{avg}1$ 同样无法满足要求。取 $$ \textit{right} \min\big(\max(\textit{candies}),\ \textit{avg}\big) 1 $$ 可以显著缩小二分范围减少二分次数。这个优化在本仓库 c.go 中体现为min(mx, total/int(k))也出现在 2226.md 的全部四种语言实现里。判定函数 check 与多语言实现判定函数是二分答案的核心给定候选值low遍历所有糖果堆累加candies[i] / low判断总和是否 $\ge k$。一旦累加达到 $k$ 即可提前返回避免无谓的计算Java/C/Go 实现都做了这一剪枝。原题解给出了四种语言的完整实现全部可直接运行Python3手写开区间二分class Solution: def maximumCandies(self, candies: List[int], k: int) - int: def check(low: int) - bool: return sum(c // low for c in candies) k left, right 0, min(max(candies), sum(candies) // k) 1 while left 1 right: mid (left right) // 2 if check(mid): left mid else: right mid return leftPython3库函数bisect_leftkey把判定改写成二分最大的不满足要求的low1那么答案就是 low从而直接复用bisect_leftkey参数需要 Python 3.10class Solution: def maximumCandies(self, candies: List[int], k: int) - int: # 二分最大的不满足要求的 low1那么答案就是 low def check(low: int) - bool: low 1 return sum(c // low for c in candies) k right min(max(candies), sum(candies) // k) return bisect_left(range(right), True, keycheck)Java注意k为longsum也用long累加并做提前返回class Solution { public int maximumCandies(int[] candies, long k) { int mx 0; long sum 0; for (int c : candies) { mx Math.max(mx, c); sum c; } int left 0; int right (int) Math.min(mx, sum / k) 1; while (left 1 right) { int mid (left right) 1; if (check(mid, candies, k)) { left mid; } else { right mid; } } return left; } private boolean check(int low, int[] candies, long k) { long sum 0; for (int c : candies) { sum c / low; if (sum k) { // 提前返回 return true; } } return false; } }C同样在 check 内提前返回并用reduce/ranges::maxC20计算总和与最大值class Solution { public: int maximumCandies(vectorint candies, long long k) { auto check - bool { long long sum 0; for (int c : candies) { sum c / low; if (sum k) { // 提前返回 return true; } } return false; }; long long avg reduce(candies.begin(), candies.end(), 0LL) / k; int left 0, right min(1LL * ranges::max(candies), avg) 1; while (left 1 right) { int mid left (right - left) / 2; (check(mid) ? left : right) mid; } return left; } };仓库 Go 实现剖析sort.Search 的取反技巧本仓库把该题收录在 leetcode/weekly/287/c/c.go其实现与题解的 Python 库函数版思路完全一致但借助 Go 标准库sort.Search完成了最大化最小值的经典写法package main import sort // github.com/EndlessCheng/codeforces-go func maximumCandies(candies []int, k int64) int { mx, total : 0, 0 for _, c : range candies { mx max(mx, c) total c } // 二分最大的不满足要求的 low1那么答案就是 low return sort.Search(min(mx, total/int(k)), func(low int) bool { low sum : 0 for _, candy : range candies { sum candy / low if sum int(k) { // 提前返回 return false } } return true }) }这里有一个非常值得掌握的技巧仓库算法模板 copypasta/sort.go 的「最大化最小值」分类下也有同样的注释sort.Search(n, f)返回的是第一个使f(i)为 true 的下标即不满足f的最小位置本题要求的是满足sum(c // low) k的最大low恰好与sort.Search的语义相反。因此把判定函数取反f(low)表示每人分low1颗时无法满足要求sum k那么sort.Search找到的第一个不满足位置减 1 就是最大可行值——这正是注释所写的二分最大的不满足要求的 low1那么答案就是 lowlow的写法把开区间二分的右端点不可行语义直接映射为f的定义域配合min(mx, total/int(k))作为搜索长度整套代码无需手写 while 循环边界处理非常干净。这一技巧与bisect_left库函数版是同一思想的两种表达把最大化最小值转成二分最小不可行值。仓库内的测试验证从生成器到用例文件这道题的解法在仓库中是有完整测试闭环的测试入口leetcode/weekly/287/c/c_test.go 调用testutil.RunLeetCodeFuncWithFile(t, maximumCandies, c.txt, targetCaseNum)用 c.txt 中的样例批量验证maximumCandies的输出该测试文件头部标注了Code generated by copypasta/template/leetcode/generator_test.go说明它由 copypasta/template/leetcode/generator_test.go 自动生成——仓库为每道 LeetCode 题提供了统一的题解 用例文件 自动测试工作流用例文件 c.txt 按输入数组 / k / 期望答案的格式组织如[5,8,6]、3、5一组涵盖普通解与返回 0 的退化情形[2,5]、11、0。在仓库根目录执行go test ./leetcode/weekly/287/c/即可运行该题的单测验证二分实现与题解推导一致。复杂度分析时间复杂度$\mathcal{O}(n\log U)$其中 $n$ 是candies的长度$U$ 为二分上界初始值未优化时为 $\max(\textit{candies})$采用 $\min(\max, \textit{avg})$ 优化后更小。每次 check 需要遍历一次数组做除法求和空间复杂度$\mathcal{O}(1)$仅使用常数个变量未申请额外数组。延伸二分答案的常见变体与仓库题单最大化最小值只是二分答案的一个分支。本仓库 copypasta/sort.go 开篇的算法题单对该模型做了完整的分类整理包含二分答案求最小 / 求最大把可行性判定嵌入二分check 通常为贪心或最短路等子过程最小化最大值二分上界 upper好比用一个盖子去压住最大值check 判断是否存在方案使所有元素不超过 upper最大化最小值二分下界 low1即本题模型copypasta/sort.go 同时给出了配套的sort.Search返回语义取反技巧第 K 小/大、0-1 分数规划、二分间接值、最小化中位数进阶变体覆盖 Codeforces/AtCoder/Luogu 等平台的大量题目如 CF1623C、CF460C、LC 等。把一道题抽象为单调性 可快速判定的 check 函数再套用对应的二分变体是处理这类最优化问题的高效通用方法。理解了本题的分割糖果判定也就掌握了二分答案中最经典的一类骨架。参考资料仓库内题解文档leetcode/weekly/287/c/2226.mdGo 题解实现leetcode/weekly/287/c/c.go测试与用例leetcode/weekly/287/c/c_test.go、leetcode/weekly/287/c/c.txt二分答案分类题单与sort.Search技巧copypasta/sort.go周赛题解生成模板copypasta/template/leetcode/generator_test.go赞分享科学计算【免费下载链接】codeforces-go算法竞赛模板库 by 灵茶山艾府 项目地址https://gitcode.com/GitHub_Trending/co/codeforces-go点击查看免费下载相关推荐LeetCode 2064 最小化最大值二分答案判定模型实战codeforces-go 仓库题解与源码解析LeetCode 2064 最小化最大值二分答案判定模型实战codeforces go 仓库题解与源码解析 导读 本文围绕 LeetCode 周赛 26科学计算二分答案 贪心判定LeetCode 410「分割数组的最大值」最小化最大值模板全解codeforces-go 仓库实战二分答案 贪心判定LeetCode 410「分割数组的最大值」最小化最大值模板全解codeforces go 仓库实战 本文以 leetcode/pr科学计算codeforces-go中的二分答案最大化最小值问题codeforces go中的二分答案最大化最小值问题 你是否在解决算法问题时遇到过这样的场景需要在一系列约束条件下找到一个最优解使得某个值尽可能大同科学计算上一篇超详细LevelDB性能测试指南从db_bench工具到性能优化实战下一篇Clypra时间线拖拽交互设计React DnD在复杂UI中的高级应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表