【C/C++】506. 相对名次

241 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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 位运动员的获奖情况。

提示:

  • n==score.lengthn == score.length
  • 1n1041 \leqslant n \leqslant 10^4
  • 0score[i]1060 \leqslant score[i] \leqslant 10^6
  • 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]

整理题意

题目给定一组整数表示运动员在比赛中的得分,现在要求我们按照分数对每个运动员进行排名(分数越高排名越靠前),特殊要求为第 123 名需要分别返回"Gold Medal""Silver Medal""Bronze Medal",其他名次返回排名即可。按照原来的顺序返回每个运动员的获奖情况。

题目数据提示每个运动员的得分 互不相同

解题思路分析

因为需要排名,所以排序肯定少不了,又因为题目要求返回的顺序要按照原来的顺序进行返回,也就是相对名次,这里可以采用较为常规的 哈希表映射 来完成。

键值对 pair

在此基础上我们还可以使用 键值对 的方式来优化,键值对中第一个数表示得分(放在第一个位置,因为排序会先按照 first 排序),第二数字表示原来所在数组中的下标,这样排序后也能找到该元素之前所在的位置映射,这样在取映射的下标值时时间复杂度为标准的 O(1),而哈希表映射的方式为常数级别的 O(1)

离散化

我们还可以换一种思维方式,由于该题不关心运动员的得分大小,只关心得分之间的大小关系,我们可以直接对数组进行 离散化 处理。

离散化:当我们只关心数据之间的大小关系,不关心数据具体的值为多少时,就可以对数据进行离散化处理,将大范围的数据离散化缩小到较小的范围中去。比如整数数值范围是 10910^9,但是整数的个数最多 10510^5 个,同时我们只关心整数数值之间的大小关系,不关心具体值为多少,那么就可以使用离散化进行处理,将数值范围 10910^9 压缩到 10510^5,这样的好处在于原本无法开辟大小为 10910^9 的数组,但是通过离散化处理后就可以只开辟 10510^5 大小的数组。

同时需要注意,由于题目数据提示每个运动员的得分 互不相同,所以在离散化的时候无需去重处理。

离散化得到的数组即为每个数在原数组中的排名。

具体实现

键值对 pair

  1. [得分,下标] 作为键值对进行存储;
  2. 对键值对进行排序。
  3. 通过排序后的键值对数组,映射回原来的数组即可。

离散化

  1. 拷贝原数组;
  2. 对副本数组进行排序;
  3. 无需去重(因为题目保证得分 互不相同),直接遍历原数组,对排序后的副本数组进行二分查找排名即可。

小技巧

sort 排序函数中,如果要降序排序除了可以写自定义函数以外,还可以添加第三个参数为 greater<int>()(二分函数 lower_bound()upper_bound() 也是同样的),表示从大到小排序,另外在实现过程中有个小技巧可以同样按照升序排序来查找排名,因为保证分数非负,所以可以对分数取负号,这样分数越大就越小,升序排序后也就是分数越大的排在越前面。

复杂度分析

  • 时间复杂度:O(nlogn)O(n \log n),其中 n 为数组的长度。我们需要对数组进行一次排序,因此时间复杂度为 O(nlogn)O(n \log n)
  • 空间复杂度:O(n)O(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 还可以存放为排序的第二关键字的值。
  • 该题还可以换种思维来看,其实本质上就是对分数进行 离散化 处理的一个过程,所以也可以直接对数组进行离散化处理即可。
  • 测试结果:

506.相对名次 键值对.png

506.相对名次.png 由于测试用例较少,所以无法通过测试用例来判断孰优孰劣,但理论上的时间复杂度都是相同的为 O(n×logn)O(n \times \log n),键值对 pair 作为常规手法,在思维上更容易理解,而离散化作为一种算法,需要我们掌握和理解其后面的思维逻辑,融会贯通后运用在别的题目上。

结束语

生活中我们总要不断做选择,每一次选择,就意味着要放弃另外的很多种可能。不要因为错过的哪些可能性而遗憾,认准了眼前这一条路,那就全力以赴。对得起每一次选择,就是对自己的人生负责。新的一天,加油!