news 2026/9/12 2:15:03

LeetCode-Go 题解:528. Random Pick with Weight —— 前缀和 + 二分查找实现权重随机采样

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode-Go 题解:528. Random Pick with Weight —— 前缀和 + 二分查找实现权重随机采样

LeetCode-Go 题解:528. Random Pick with Weight —— 前缀和 + 二分查找实现权重随机采样

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

导读

本文围绕 LeetCode-Go 仓库中 528. Random Pick with Weight 一题的官方题解文档展开,深入讲解"按权重随机取下标"这一经典算法问题:先构建权重前缀和数组,再在[1, total]区间内取随机数,通过二分查找定位目标下标,从而让每个下标被选中的概率与其权重严格成正比。读完本文,你将掌握前缀和数组 + 二分查找这一套 O(log n) 的权重随机采样方案,并能够结合仓库中的 Go 源码 与 测试用例 完整理解其实现与正确性验证方法。

题目理解

给定一个正整数数组w,其中w[i]表示下标i的权重。需要实现一个pickIndex()函数,它随机返回一个下标i,并且返回下标i的概率与w[i]成正比。例如权重数组w = [1, 3]表示下标0被选中的概率是 1/4,下标1被选中的概率是 3/4。

题目约束如下:

  1. 1 <= w.length <= 10000
  2. 1 <= w[i] <= 10^5
  3. pickIndex将被调用不超过10000

即权重数组长度最多 10000,单个权重最大 10^5,总权重可达 10^9,因此中间计算需要使用 64 位整数(Go 的int在 64 位平台上足以覆盖该范围)。

输入输出示例

示例 1:

Input: ["Solution","pickIndex"] [[[1]],[]] Output: [null,0]

只有一个下标,权重为 1,无论调用多少次pickIndex都返回 0。

示例 2:

Input: ["Solution","pickIndex","pickIndex","pickIndex","pickIndex","pickIndex"] [[[1,3]],[],[],[],[],[]] Output: [null,0,1,1,1,0]

权重数组为[1, 3],下标1的权重是下标0的三倍,因此多次调用后返回1的次数约为返回0的三倍。示例输出中的0,1,1,1,0正是这种比例关系的体现(实际输出具有随机性,不要求与示例完全一致)。

输入语法说明

输入由两个列表组成:第一个列表是依次调用的成员函数名(Solution构造函数与pickIndex),第二个列表是对应函数调用的参数。Solution的构造函数接收一个参数:权重数组wpickIndex没有参数。所有参数都被包装在一个列表中,即使某个函数没有参数,也会传入一个空列表[](如上例中的[],[],[],[],[])。

解题思路:前缀和 + 二分查找

这是典型的"加权随机采样"(weighted random sampling)问题,其核心思想是把权重分布转化到一维数轴上:

第一步:构建前缀和数组

遍历权重数组w,计算前缀和prefixSum[i] = w[0] + w[1] + ... + w[i]。此时数组被映射为数轴上的连续区间:

  • 下标0对应区间[0, w[0])
  • 下标i对应区间[prefixSum[i-1], prefixSum[i]),区间长度为w[i]

于是"按权重随机"就等价于"在总区间[0, prefixSum[n-1])内均匀随机取一个点,看它落在哪个下标对应的区间里"。

第二步:均匀随机 + 二分定位

[0, prefixSum[n-1])区间内随机选一个整数x,然后寻找满足x < prefixSum[i]的最小下标i,该下标即为最终解。因为区间长度恰好等于权重w[i],所以点落到每个区间的概率与该区间长度(即权重)成正比。

对于某些下标i,所有满足prefixSum[i] - w[i] <= v < prefixSum[i]的整数v都会映射到这个下标,这也从数学上保证了"每个下标被选中的概率与下标权重成比例"。

由于prefixSum是严格递增的(所有w[i] >= 1),可以使用二分查找在 O(log n) 时间内完成定位,而不是线性扫描。

复杂度分析

  • 预处理(构建前缀和):时间复杂度 O(n),空间复杂度 O(n)(需要保存前缀和数组);
  • pickIndex():时间复杂度 O(log n)(一次二分查找),空间复杂度 O(1)(仅使用常数级额外变量)。

仓库源码实现解析

仓库中该题的完整实现位于 528. Random Pick with Weight.go,代码风格遵循项目一贯的命名约定(以题号作为类型与构造函数后缀,避免与其他题目类型冲突)。

package leetcode import ( "math/rand" ) // Solution528 define type Solution528 struct { prefixSum []int } // Constructor528 define func Constructor528(w []int) Solution528 { prefixSum := make([]int, len(w)) for i, e := range w { if i == 0 { prefixSum[i] = e continue } prefixSum[i] = prefixSum[i-1] + e } return Solution528{prefixSum: prefixSum} } // PickIndex define func (so *Solution528) PickIndex() int { n := rand.Intn(so.prefixSum[len(so.prefixSum)-1]) + 1 low, high := 0, len(so.prefixSum)-1 for low < high { mid := low + (high-low)>>1 if so.prefixSum[mid] == n { return mid } else if so.prefixSum[mid] < n { low = mid + 1 } else { high = mid } } return low }

关键实现细节

1. 前缀和的构建(Constructor528)

Constructor528通过一次线性遍历(O(n))完成前缀和构建:首元素直接复制,后续元素累加前一项。最终prefixSum的最后一个元素即所有权重之和,它同时决定了随机数的取值上限。

2. 随机数的生成与区间偏移

PickIndex中的rand.Intn(total) + 1生成[1, total]区间内的整数ntotal为权重总和)。与题解文档中描述的[0, prefixSum)区间相比,这里整体右移了 1,等价关系为x = n - 1,对应的映射区间相应变为(prefixSum[i-1], prefixSum[i]],长度依然为w[i],比例关系不受影响。

3. 二分查找的三种分支

  • prefixSum[mid] == n时直接返回mid:此时n恰好落在区间(prefixSum[mid-1], prefixSum[mid]]的右端点上,属于下标mid的区间,直接返回是正确且省时的;
  • prefixSum[mid] < n时,目标在右半区,low = mid + 1
  • 否则目标在左半区(含mid),high = mid

mid := low + (high-low)>>1使用位移代替除法,并避免(low+high)/2可能出现的整数溢出问题,是二分查找的经典稳健写法。

4. 使用方式

源码末尾注释给出了标准的实例化与调用方式:

obj := Constructor(w); param_1 := obj.PickIndex();

测试验证:正确性与分支覆盖

仓库为该题提供了完整的测试用例,位于 528. Random Pick with Weight_test.go,包含两部分验证:

第一部分:基础示例验证

使用题目给出的示例权重w = [1, 3],连续调用 6 次PickIndex并打印结果,用于人工确认输出符合权重比例(约 1/4 概率返回 0,3/4 概率返回 1)。

第二部分:边界与分支覆盖

使用五元素权重w2 = [3, 1, 1, 5, 2](总权重 12),固定随机种子rand.Seed(1)后循环调用 2000 次,每次断言返回下标均在[0, len(w2))范围内。测试注释明确指出:多元素、多次迭代的设计是为了让二分查找充分执行每一条分支——包括low = mid + 1high = mid以及prefixSum[mid] == n的提前返回分支。

这也呼应了仓库的整体质量保障机制:根目录下的 gotest.sh 通过go test -covermode=atomic -coverprofile=coverage.txt ./leetcode/...对全部题解执行带覆盖率统计的测试(项目 go.mod 声明 Go 1.19),确保每个题解均有测试覆盖支撑。

扩展思考

  1. 为什么不用线性扫描?若每次pickIndex线性遍历查找区间,单次复杂度为 O(n),在pickIndex最多被调用 10000 次、w.length最多 10000 的前提下最坏可达 10^8 量级;前缀和 + 二分将单次查询降为 O(log n),是本题的标准最优解法。

  2. 前缀和是必须的中间结构吗?是的。若不预计算前缀和,二分查找将无法直接在原权重数组上进行——因为二分要求查找对象具备单调性,而前缀和正是把"任意前缀区间的累计权重"这一单调序列显式化。

  3. 与区间映射的联系:本解法本质上构建了一个离散化的一维概率分布,均匀随机变量经"逆变换采样"(inverse transform sampling)思路映射到离散下标,这是权重随机采样问题最通用的建模方式,可迁移至抽奖、负载均衡、采样等真实场景。

小结

  1. Random Pick with Weight 的解法以"前缀和 + 二分查找"为核心:预处理阶段 O(n) 构建权重前缀和数组,查询阶段 O(log n) 完成随机下标定位,整体空间复杂度 O(n)。LeetCode-Go 仓库中的 题解文档、实现源码 与 测试代码 三者相互印证,既给出了严谨的数学映射证明,也提供了可编译、可测试、覆盖全部二分分支的完整 Go 工程实现,是学习加权随机采样算法的优质参考范本。

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/12 2:14:48

MIMO系统中FLMS算法的MATLAB仿真实现与优化

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:13:08

5 分钟装好 res-downloader:跨平台无水印视频下载完整指南

5 分钟装好 res-downloader&#xff1a;跨平台无水印视频下载完整指南 【免费下载链接】res-downloader 视频号、小程序、抖音、快手、小红书、直播流、m3u8、酷狗、QQ音乐等常见网络资源下载! 项目地址: https://gitcode.com/GitHub_Trending/re/res-downloader 下午三…

作者头像 李华
网站建设 2026/9/12 2:11:11

区域综合能源系统双层优化调度复现:需求响应与KKT条件求解实践

复现这篇《计及需求响应的区域综合能源系统双层优化调度策略研究》花了我大概三周时间。期间踩了不少坑&#xff0c;也把整套模型从头到尾捋了一遍&#xff0c;包括上下层各自在优化什么、需求响应到底怎么“计及”进去、为什么要用双层而不是一个单层大模型硬解&#xff0c;以…

作者头像 李华
网站建设 2026/9/12 2:09:55

Java知识:异常

介绍&#xff1a; 在Java中&#xff0c;将程序执行过程中发生的不正常行为称为异常。本篇旨在叙述对异常的捕获与处理&#xff0c;异常处理主要的5个关键字&#xff1a;throw、try、catch、final、throws。一、介绍1.异常的概念在日常开发中&#xff0c;绞尽脑汁将代码写的尽善…

作者头像 李华
网站建设 2026/9/12 2:09:27

AI辅助学术写作:书匠策AI的文献分析与数据可视化实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/12 2:08:52

以为的岁月静好,不过是有人在替你负重前行

《躺平不是休息&#xff0c;而是慢性欠费》——秩序从来不免费&#xff0c;你不付账&#xff0c;混乱替你付你什么都没做&#xff0c;但一切都在悄悄垮掉。没人推墙&#xff0c;墙皮自己掉&#xff1b;没人拔草&#xff0c;草从地砖缝里钻出来&#xff1b;没人放水&#xff0c;…

作者头像 李华