持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
题目链接:506. 相对名次
题目描述
给你一个长度为 n 的整数数组 score ,其中 score[i] 是第 i 位运动员在比赛中的得分。所有得分都 互不相同 。
运动员将根据得分 决定名次 ,其中名次第 1 的运动员得分最高,名次第 2 的运动员得分第 2 高,依此类推。运动员的名次决定了他们的获奖情况:
- 名次第
1的运动员获金牌"Gold Medal"。 - 名次第
2的运动员获银牌"Silver Medal"。 - 名次第
3的运动员获铜牌"Bronze Medal"。 - 从名次第
4到第n的运动员,只能获得他们的名次编号(即,名次第x的运动员获得编号"x")。
使用长度为 n 的数组 answer 返回获奖,其中 answer[i] 是第 i 位运动员的获奖情况。
提示:
score中的所有值 互不相同
示例 1:
输入:score = [5,4,3,2,1]
输出:["Gold Medal","Silver Medal","Bronze Medal","4","5"]
解释:名次为 [1st, 2nd, 3rd, 4th, 5th] 。
示例 2:
输入:score = [10,3,8,9,4]
输出:["Gold Medal","5","Bronze Medal","Silver Medal","4"]
解释:名次为 [1st, 5th, 3rd, 2nd, 4th] 。
整理题意
题目给定一组整数表示运动员在比赛中的得分,现在要求我们按照分数对每个运动员进行排名(分数越高排名越靠前),特殊要求为第 1、2、3 名需要分别返回"Gold Medal"、"Silver Medal"、"Bronze Medal",其他名次返回排名即可。按照原来的顺序返回每个运动员的获奖情况。
题目数据提示每个运动员的得分 互不相同
解题思路分析
因为需要排名,所以排序肯定少不了,又因为题目要求返回的顺序要按照原来的顺序进行返回,也就是相对名次,这里可以采用较为常规的 哈希表映射 来完成。
键值对 pair
在此基础上我们还可以使用 键值对 的方式来优化,键值对中第一个数表示得分(放在第一个位置,因为排序会先按照 first 排序),第二数字表示原来所在数组中的下标,这样排序后也能找到该元素之前所在的位置映射,这样在取映射的下标值时时间复杂度为标准的 O(1),而哈希表映射的方式为常数级别的 O(1)。
离散化
我们还可以换一种思维方式,由于该题不关心运动员的得分大小,只关心得分之间的大小关系,我们可以直接对数组进行 离散化 处理。
离散化:当我们只关心数据之间的大小关系,不关心数据具体的值为多少时,就可以对数据进行离散化处理,将大范围的数据离散化缩小到较小的范围中去。比如整数数值范围是 ,但是整数的个数最多 个,同时我们只关心整数数值之间的大小关系,不关心具体值为多少,那么就可以使用离散化进行处理,将数值范围 压缩到 ,这样的好处在于原本无法开辟大小为 的数组,但是通过离散化处理后就可以只开辟 大小的数组。
同时需要注意,由于题目数据提示每个运动员的得分 互不相同,所以在离散化的时候无需去重处理。
离散化得到的数组即为每个数在原数组中的排名。
具体实现
键值对 pair
- 将
[得分,下标]作为键值对进行存储; - 对键值对进行排序。
- 通过排序后的键值对数组,映射回原来的数组即可。
离散化
- 拷贝原数组;
- 对副本数组进行排序;
- 无需去重(因为题目保证得分 互不相同),直接遍历原数组,对排序后的副本数组进行二分查找排名即可。
小技巧
在 sort 排序函数中,如果要降序排序除了可以写自定义函数以外,还可以添加第三个参数为 greater<int>()(二分函数 lower_bound() 和 upper_bound() 也是同样的),表示从大到小排序,另外在实现过程中有个小技巧可以同样按照升序排序来查找排名,因为保证分数非负,所以可以对分数取负号,这样分数越大就越小,升序排序后也就是分数越大的排在越前面。
复杂度分析
- 时间复杂度:,其中
n为数组的长度。我们需要对数组进行一次排序,因此时间复杂度为 。 - 空间复杂度:,其中
n为数组的长度。
代码实现
键值对 pair
class Solution {
public:
vector<string> findRelativeRanks(vector<int>& score) {
int n = score.size();
// 记录分数和下标,因为要对分数排序所以分数放前面
vector<pair<int, int>> arr;
arr.clear();
// 注意这里的 -score[i],因为分数越大排名越靠前
for(int i = 0; i < n; i++) arr.emplace_back(-score[i], i);
// 对pair类型进行排序,会先对first进行排序
sort(arr.begin(), arr.end());
vector<string> ans(n);
// 将排序后的arr进行依次映射
for(int i = 0; i < n; i++){
if(i == 0) ans[arr[i].second] = "Gold Medal";
else if(i == 1) ans[arr[i].second] = "Silver Medal";
else if(i == 2) ans[arr[i].second] = "Bronze Medal";
else ans[arr[i].second] = to_string(i + 1);
}
return ans;
}
};
离散化
class Solution {
public:
vector<string> findRelativeRanks(vector<int>& score) {
// 拷贝副本数组
vector<int> arr = score;
// 排序(降序)也可以使用 -score[i] 的手法来升序排序
sort(arr.begin(), arr.end(), greater<int>());
// 因为题目保证没有重复元素,所以无需去重
int n = score.size();
vector<string> ans;
for(int i = 0; i < n; i++){
// 二分查找对应元素的排名 greater<int>() 表示在降序中二分
int num = upper_bound(arr.begin(), arr.end(), score[i], greater<int>()) - arr.begin();
if(num == 1) ans.emplace_back("Gold Medal");
else if(num == 2) ans.emplace_back("Silver Medal");
else if(num == 3) ans.emplace_back("Bronze Medal");
else ans.emplace_back(to_string(num));
}
return ans;
}
};
总结
- 这种相对名次的题目可以尝试使用
pair键值对来处理可以达到很好的效果,first存放需要排序的键值,second可以存放映射关系,在不同题目中,second还可以存放为排序的第二关键字的值。 - 该题还可以换种思维来看,其实本质上就是对分数进行 离散化 处理的一个过程,所以也可以直接对数组进行离散化处理即可。
- 测试结果:
由于测试用例较少,所以无法通过测试用例来判断孰优孰劣,但理论上的时间复杂度都是相同的为 ,键值对
pair 作为常规手法,在思维上更容易理解,而离散化作为一种算法,需要我们掌握和理解其后面的思维逻辑,融会贯通后运用在别的题目上。
结束语
生活中我们总要不断做选择,每一次选择,就意味着要放弃另外的很多种可能。不要因为错过的哪些可能性而遗憾,认准了眼前这一条路,那就全力以赴。对得起每一次选择,就是对自己的人生负责。新的一天,加油!