【C/C++】1331. 数组序号转换(离散化模板)

385 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情


题目链接:1331. 数组序号转换

题目描述

给你一个整数数组 arr ,请你将数组中的每个元素替换为它们排序后的序号。

序号代表了一个元素有多大。序号编号的规则如下:

  • 序号从 1 开始编号。
  • 一个元素越大,那么序号越大。如果两个元素相等,那么它们的序号相同。
  • 每个数字的序号都应该尽可能地小。

提示:

  • 0arr.length1050 \leqslant arr.length \leqslant 10^5
  • 109 arr[i]109-10^9 \leqslant arr[i] \leqslant 10^9

示例 1:

输入:arr = [40,10,20,30]
输出:[4,1,2,3]
解释:40 是最大的元素。 10 是最小的元素。 20 是第二小的数字。 30 是第三小的数字。

示例 2:

输入: arr = [100,100,100]
输出: [1,1,1]
解释: 所有元素有相同的序号。

示例 3:

输入: arr = [37,12,28,9,100,56,80,5,12]
输出: [5,3,4,2,8,6,7,1,3]

整理题意

题目给定一个整数数组,要求返回数组中每个整数对应的排名,从小到大排名,返回原数组中每个整数所对应的排名。

解题思路分析

由于需要排名,首先想到的是 排序 操作,那么对于排序后的数组进行哈希表映射处理,然后在原数组中根据值找到相应的排名即可。由于题目规定相同的整数排名也要一致,所以需要处理一下排序后连续相同的整数。

由于涉及给数组中每个整数进行排名,同时相同整数排名一致,将数组中的每个元素替换为它们排序后的序号,那么该操作其实和对数组进行 离散化 操作。

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

具体实现

对于排序后哈希表映射的方法不做过多介绍,直接暴力实现即可,这里着重讲解离散化的解法,在以后的题目中如果遇到需要使用离散化处理的题目可以直接套用该题代码作为模板使用。

  1. 首先拷贝一份原数组,对副本数组 nums 进行排序操作(从小到大);
  2. 对排好序的副本数组进行 去重 操作,这里为离散化的难点;

首先需要了解 unique()erase() 这两个函数:

  • erase() 函数就是删除数组中的元素;
  • unique() 函数是一个伪去重函数,它会把重复的元素添加到容器末尾,而返回值是去重之后的尾地址,注意这个尾地址相当于去重后的 nums.end(),也就是不包括尾地址这个元素。这里通常配合 begin() 函数进行使用,如 unique(nums.begin(), nums.end()) - nums.begin() 可以得到 nums 数组去重后的数组元素个数。需要 注意 使用 unique() 之前需要对需要去重的数组进行 排序 处理。
  1. 这里配合使用 unique()erase()nums 数组进行去重操作为:nums.erase(unique(nums.begin(), nums.end()), nums.end());

这行代码表示首先对 nums 数组进行去重操作:unique(nums.begin(), nums.end());然后删除 nums 数组在 [unique(nums.begin(), nums.end()), nums.end()] 之间的元素(因为 unique(nums.begin(), nums.end()) 返回的是去重之后的 尾地址 ),这样我们就得到了去重后的数组 nums,中间省略了计算重复元素的个数 cnt = unique(nums.begin(), nums.end()) - nums.begin();,一步到位。

  1. 最后就只需根据副本数组 nums 的值和下标对原数组 arr 进行替换即可。

由于 nums 数组为有序数组,当我们需要查找 arr[i] 的排名序号时只需在 nums 数组中进行二分查找即可,找到的下标即为 arr[i] 的排名序号,需要注意 nums 数组下标是从 0 开始的,如果需要使得排名从 1 开始,只需 +1 即可。这里还可以使用 lower_bound()upper_bound() 函数来简化二分代码为:upper_bound(nums.begin(), nums.end(), arr[i]) - nums.begin();,这样返回的值就是 arr[i] 的排名,排名序号从 1 开始,巧妙使用 upper_bound() 省去了 +1 操作。

复杂度分析

  • 时间复杂度:O(n×logn)O(n×\log n),其中 n 是输入数组 arr 的长度,排序消耗 O(n×logn)O(n \times \log{n}) 时间,离散化消耗 O(n×logn)O(n \times \log{n}) 时间。
  • 空间复杂度:O(n)O(n)。离散化使用的拷贝数组消耗 O(n)O(n) 空间。

代码实现

class Solution {
public:
    vector<int> arrayRankTransform(vector<int>& arr) {
        // 离散化操作
        vector<int> nums = arr;
        sort(nums.begin(), nums.end());
        // 去重操作
        nums.erase(unique(nums.begin(), nums.end()), nums.end());
        for(int &num : arr){
            // 在排好序的 nums 数组中二分查找位置,排名从1开始所以用 upper
            num = upper_bound(nums.begin(), nums.end(), num) - nums.begin();
        }
        return arr;
    }
};

总结

  • 该题可以作为离散化模板题目,在以后遇到需要使用离散化的题目时可以复用本题代码作为模板使用。
  • 题目需要注意的数据范围细节为数组可能为空,也就是 arr 数组没有元素,此时是需要返回空数组,注意空数组时代码中是否会出现运行错误。
  • 离散化的代码虽然简短,但是调用了很多函数,需要理解 unique()erase()upper_bound()lower_bound() 这些函数的作用和返回值,在应对不同题目时也能根据题目实际情况进行相应的修改。
  • 需要特别注意 unique() 函数的去重并非真正意义的去重,而是将重复的元素放到容器的末尾,返回值是去重之后的尾地址。并且 unique() 针对的是 相邻元素,所以对于需要去重的数组或者容器,需要提前进行排序处理。
  • 测试结果:

1331.数组序号转换.png

结束语

微笑只是一种表情,有时候却比语言更有力量。当你笑起来的时候,不仅可以感染别人,还可以疗愈自己。学会用微笑面对难关、化解难题,你会发现,爱笑的人运气不会太差。新的一天,加油!