news 2026/9/10 19:02:06

LeetCode-Go 题解:506. Relative Ranks(相对名次)——map 索引 + 降序排序的 Go 实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode-Go 题解:506. Relative Ranks(相对名次)——map 索引 + 降序排序的 Go 实现

LeetCode-Go 题解:506. Relative Ranks(相对名次)——map 索引 + 降序排序的 Go 实现

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

本篇文章以 LeetCode-Go 仓库中 0506.Relative-Ranks 题解 为核心,深入讲解第 506 题「Relative Ranks(相对名次)」的完整解题思路与 Go 实现。你将掌握如何用「哈希表记录原始下标 + 降序排序」这一经典组合,将一维分数数组高效转换为排名字符串数组,并了解该解法在仓库中的源码、单元测试与覆盖率验证全貌。

一、题目理解:将分数数组转换为名次数组

第 506 题给定一个长度为 n 的整数数组score,其中score[i]是第 i 位运动员在比赛中的得分,且所有得分互不相同(保证唯一性)。

运动员根据得分决定名次:得分最高者为第 1 名,次高者为第 2 名,依此类推。名次与获奖情况的映射规则如下:

  • 第 1 名获得金牌"Gold Medal"
  • 第 2 名获得银牌"Silver Medal"
  • 第 3 名获得铜牌"Bronze Medal"
  • 从第 4 名到第 n 名,获得其名次编号对应的字符串(第 x 名得到字符串"x")。

最终返回长度为 n 的字符串数组answer,其中answer[i]是第 i 位运动员的获奖情况。

输入输出示例

示例 1:

Input: score = [5,4,3,2,1] Output: ["Gold Medal","Silver Medal","Bronze Medal","4","5"] Explanation: The placements are [1st, 2nd, 3rd, 4th, 5th].

分数本身已按降序排列,因此下标 0~4 恰好对应第 1~5 名,前三位直接颁发金银铜牌,后两位输出名次编号。

示例 2:

Input: score = [10,3,8,9,4] Output: ["Gold Medal","5","Bronze Medal","Silver Medal","4"] Explanation: The placements are [1st, 5th, 3rd, 2nd, 4th].

这个示例更能体现题目的核心难点:原始数组中下标 0 的10是最高分(第 1 名),下标 1 的3却是最低分(第 5 名),下标 2 的8是第 3 名,下标 3 的9是第 2 名,下标 4 的4是第 4 名。输出的顺序必须与输入数组的下标保持一致,而不是按排名顺序输出。

约束条件

  • n == score.length
  • 1 <= n <= 10000
  • 0 <= score[i] <= 1000000
  • 数组中所有值互不相同

由于分数值域可达 100 万、数组规模可达 1 万,且需要同时维护「分数 → 名次」与「原下标 → 名次」两层映射,选择合适的数据结构至关重要。

二、核心解题思路:map 记录下标 + 降序排序回填

原文档给出的解题思路十分精炼:

用 map 记录原来 score 中元素对应的坐标,然后对 score 进行排序,对排序后的元素我们通过 map 就可以知道它排的名次了。

将其拆解为三个关键步骤,即可理解算法全貌:

第一步:建立「分数 → 原始下标」的哈希映射

遍历原数组,将每个分数v与其原始下标i存入mp[v] = i。这一步利用了「所有分数互不相同」的约束——正因为分数唯一,mp的键才不会冲突,才能保证每个分数唯一地对应一个原始下标。

第二步:对分数数组原地降序排序

通过sort.Slice配合score[i] > score[j]的比较器实现降序。排序完成后,排序后下标0、1、2、3…恰好对应名次第 1、2、3、4…名。

第三步:遍历排序后的数组,将名次回填到原始下标处

排序后第 i 个元素v的名次就是i+1(前三位特殊处理为奖牌字符串),再通过mp[v]找到它原来在数组中的下标,写入ans数组的对应位置。

整个算法的时间复杂度为 O(n log n)(排序主导),空间复杂度为 O(n)(哈希表与答案数组各占一份)。

三、Go 完整实现与逐行解读

以下是 506.Relative Ranks.go 中的完整源码,与 README 文档中的代码完全一致,可直接复制运行:

package leetcode import ( "sort" "strconv" ) func findRelativeRanks(score []int) []string { mp := make(map[int]int) for i, v := range score { mp[v] = i } sort.Slice(score, func(i, j int) bool { return score[i] > score[j] }) ans := make([]string, len(score)) for i, v := range score { if i == 0 { ans[mp[v]] = "Gold Medal" } else if i == 1 { ans[mp[v]] = "Silver Medal" } else if i == 2 { ans[mp[v]] = "Bronze Medal" } else { ans[mp[v]] = strconv.Itoa(i + 1) } } return ans }

关键点逐行解读

1. 哈希表mp:分数到原始下标的反向索引

mp := make(map[int]int) for i, v := range score { mp[v] = i }

这是整个解法的枢纽。原数组在排序后下标会完全被打乱,mp让我们在排序完成后仍然能回答「这个分数原本在哪个位置」。由于题目保证分数唯一,mp的键不会发生覆盖。

2. 降序排序:sort.Slice的比较器

sort.Slice(score, func(i, j int) bool { return score[i] > score[j] })

这里对传入的score原地排序,直接修改了原切片。排序后第 i 个元素的名次为i+1,这是后续回填逻辑的基础。sort.Slice底层使用 pdqsort(Go 1.19 起默认的混合排序算法),在近乎有序等场景下也有良好表现。

3. 前三名特殊处理 + 其余名次编号

if i == 0 { ans[mp[v]] = "Gold Medal" } else if i == 1 { ans[mp[v]] = "Silver Medal" } else if i == 2 { ans[mp[v]] = "Bronze Medal" } else { ans[mp[v]] = strconv.Itoa(i + 1) }

排序后的下标 0、1、2 对应前三名,映射为奖牌字符串;从下标 3 开始,用strconv.Itoa(i + 1)将名次整数转为字符串。注意i+1是因为名次从 1 开始计数,而下标从 0 开始。每一次赋值的目标位置都是ans[mp[v]],即该分数在原数组中的下标,从而保证输出顺序与输入顺序一致。

算法复杂度

  • 时间复杂度:O(n log n)sort.Slice的排序为 O(n log n),两次线性遍历(建表与回填)均为 O(n),整体由排序主导。
  • 空间复杂度:O(n)。哈希表mp与答案数组ans各占用 O(n) 空间,sort.Slice在部分排序场景下可能有少量额外空间。

四、源码、单元测试与覆盖率验证

1. 单元测试:覆盖两个官方示例

仓库为本题提供了专门的测试文件 506.Relative Ranks_test.go,采用「参数-答案」结构化的表格驱动风格组织用例:

type question506 struct { para506 ans506 } // para 是参数 type para506 struct { score []int } // ans 是答案 type ans506 struct { ans []string } func Test_Problem506(t *testing.T) { qs := []question506{ { para506{[]int{5, 4, 3, 2, 1}}, ans506{[]string{"Gold Medal", "Silver Medal", "Bronze Medal", "4", "5"}}, }, { para506{[]int{10, 3, 8, 9, 4}}, ans506{[]string{"Gold Medal", "5", "Bronze Medal", "Silver Medal", "4"}}, }, } // ... for _, q := range qs { _, p := q.ans506, q.para506 fmt.Printf("【input】:%v 【output】:%v\n", p.score, findRelativeRanks(p.score)) } }

用例分别对应 README 中的两个官方示例:第一个验证完全降序输入下的直白输出,第二个验证乱序输入下「排名与原始下标交错」的关键场景(即[10,3,8,9,4]["Gold Medal","5","Bronze Medal","Silver Medal","4"]),能够有效检验 map 回填逻辑的正确性。

2. 覆盖率佐证:100% 行覆盖

仓库通过 gotest.sh 统一执行测试并收集覆盖率:

go test -covermode=atomic -coverprofile=coverage.txt ./leetcode/...

在 coverage.txt 中可以找到本题源码的覆盖记录,例如:

github.com/halfrost/LeetCode-Go/leetcode/0506.Relative-Ranks/506.Relative Ranks.go:8.46,10.26 2 2 github.com/halfrost/LeetCode-Go/leetcode/0506.Relative-Ranks/506.Relative Ranks.go:13.40,15.3 1 12 github.com/halfrost/LeetCode-Go/leetcode/0506.Relative-Ranks/506.Relative Ranks.go:20.20,22.4 1 2 github.com/halfrost/LeetCode-Go/leetcode/0506.Relative-Ranks/506.Relative Ranks.go:24.9,26.4 1 4

从记录可看出:建表循环执行了 10 次(对应两个用例共 5 + 5 个元素)、排序调用 2 次、回填循环 10 次,三个分支(金牌、银牌、铜牌、数字名次)均被执行到,函数整体达到 100% 行覆盖。

3. 在题库总表中的定位

仓库根目录 README.md 的题库总表中记录着本题的元信息:0506 | Relative Ranks | Easy,难度为 Easy,且提供了指向本题目录的入口链接。本仓库模块名在 go.mod 中声明为github.com/halfrost/LeetCode-Go,Go 版本为 1.19,sort.Slice的用法与该项目使用的 Go 版本完全兼容。

4. 本地运行验证方式

在仓库根目录执行以下命令,即可复现测试输出与覆盖率验证:

# 运行全部测试(含本题用例) go test ./leetcode/... # 按 gotest.sh 的方式生成覆盖率文件 bash gotest.sh # 仅运行本题测试并输出用例日志 go test -v -run Test_Problem506 ./leetcode/0506.Relative-Ranks/

五、边界情况与扩展思考

  • n = 1 时:数组只有一个元素,排序后i == 0,直接返回["Gold Medal"],代码无需额外特判。
  • 分数含 0:约束允许score[i] = 0。由于mp[0]同样能正常建立映射(0 与任意下标都不冲突),代码天然兼容,无需处理。
  • 不修改原数组的替代方案:当前实现是对score原地排序,若业务要求保留原始分数数组,可额外拷贝一份排序,或改为对下标切片idx := []int{0,1,...,n-1}score[idx[i]] > score[idx[j]]排序,本质思路一致,空间开销会略有增加。
  • 为什么不用「分数→名次」直接映射:若只建score -> rank的映射,遍历原数组时仍能通过映射得到每个下标的排名,这是另一种等价写法(可省去回填时的mp[v]查找)。本仓库的解法选择先记录原始下标再回填,二者的时间复杂度相同,都基于「分数唯一」这一前提,可结合场景选择。

小结

第 506 题的核心价值在于训练「索引保序」思维:当排序会破坏元素与位置的对应关系时,先用哈希表建立反向索引,排序后再按需回填,即可用 O(n log n) 的时间、O(n) 的空间干净利落地解决。通过阅读本题的 题解文档、源码 与 测试,你可以完整掌握这一模式,并将其迁移到「排名、Top-K、按值恢复原序」等更广泛的 LeetCode 问题中。

【免费下载链接】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/10 19:01:28

vLLM部署实战:从环境配置到性能调优,快速跑通大模型服务

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

作者头像 李华
网站建设 2026/9/10 18:56:34

SMT车间智能ESD防护闸机设计与应用

1. 项目概述&#xff1a;当SMT车间遇上智能ESD防护闸机 在SMT&#xff08;表面贴装技术&#xff09;车间里&#xff0c;静电就像个看不见的杀手。去年我们产线就发生过一起典型案例&#xff1a;某批次主板在测试阶段出现不明原因的信号干扰&#xff0c;追溯发现是操作员未规范释…

作者头像 李华
网站建设 2026/9/10 18:56:01

Rust egui窗口配置全攻略:从NativeOptions到ViewportBuilder实战

我在Rust桌面应用里用egui写小工具也有段时间了&#xff0c;每次新建项目都要重新配一遍窗口&#xff1a;大小、标题、全屏、图标、渲染器、垂直同步……这些参数统一由eframe::NativeOptions管理。今天这篇就直接给一份“窗口配置小抄”&#xff0c;把常见窗口形态的配置方式都…

作者头像 李华
网站建设 2026/9/10 18:55:50

2026国产电脑监控软件选型指南:信创适配与合规实践

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

作者头像 李华
网站建设 2026/9/10 18:55:37

ClickHouse实时数据立方体构建与优化实战

1. 为什么选择ClickHouse构建实时数据立方体第一次接触ClickHouse是在三年前的一个电商大促监控项目&#xff0c;当时需要实时分析每分钟千万级的用户行为数据。传统MySQL在写入时就已崩溃&#xff0c;而Hadoop生态的方案又无法满足亚秒级响应需求。当我用单机版ClickHouse轻松…

作者头像 李华