题目描述

✅ LCR 075. 数组的相对排序

image-20260929005746506

题意分析

按 arr2 指定的先后顺序重排 arr1:在 arr2 中出现的值放在前面,每个值的全部重复项都保留;没有出现在 arr2 中的值放在后面,并按数值升序排列。

arr2 中的值互不相同,所以每个值只有一个确定的优先名次。先把“值对应哪个名次”存入哈希表,再把题目的两层顺序转换成可比较的键,就能调用普通排序完成重排。

解法:按给定名次构造排序键

核心思路

[!blue]

设 m = arr2.length,建立 pos[value] 表示这个值在 arr2 中的下标。已指定的优先名次是 0..m-1,未指定元素需要排在这些名次之后,再按原值比较。

Java 使用一个数值键。已指定元素的键为 pos[value],未指定元素的键为 m+value。这一写法依赖本篇原文给出的非负值域:未指定元素的键至少为 m,不会与前面的优先名次混在一起;它们的键又随原值增大,因此后半部分自然升序。配对数组保存的是 [原值,键],按第二项排序,再取回第一项。

Go 使用两项组成的键:已指定元素保存 [名次,原值],未指定元素保存 [m,原值]。先比较第一项,第一项相同再比较原值。已指定元素按名次分组,所有未指定元素共享最后一个名次,并在这一组内按值升序。这与 Java 的单键写法达到相同顺序,但字段含义不同。

对任意两个不同值,以上键的比较结果都与题目要求一致;同一个值的重复项拥有相同键,排序只移动位置,不丢失次数。因此排完键后取出原值,就得到所需结果,正确性不依赖排序是否稳定。

代码将排序后的值写回 arr1。哈希表只用于查名次,不能把 arr1 转成集合;重复值必须继续作为多个独立元素保存在配对数组中。

解题步骤

  1. 遍历 arr2,建立值到名次的映射。
  2. 遍历 arr1,为每次元素出现建立携带原值的排序键。
  3. Java 按配对第二项排序;Go 按第一项、第二项依次比较。
  4. 从排序后的配对中取出原值,写回 arr1 并返回。

Go 查询哈希表时用 p, ok := pos[x] 区分“未指定”与合法名次零。若 arr2 覆盖全部值,未指定组为空;否则该组统一放在所有指定元素后面。

代码实现

class Solution {
    public int[] relativeSortArray(int[] arr1, int[] arr2) {
        Map<Integer, Integer> pos = new HashMap<>(arr2.length);

        for (int i = 0; i < arr2.length; ++i) {
            pos.put(arr2[i], i);
        }

        int[][] arr = new int[arr1.length][];

        for (int i = 0; i < arr.length; ++i) {
            arr[i] = new int[] {
                arr1[i],
                pos.getOrDefault(arr1[i], arr2.length + arr1[i])
            };
        }

        Arrays.sort(arr, (a, b) -> a[1] - b[1]);

        for (int i = 0; i < arr.length; ++i) {
            arr1[i] = arr[i][0];
        }

        return arr1;
    }
}
import (
    "sort"
)

func relativeSortArray(arr1 []int, arr2 []int) []int {
    pos := map[int]int{}
    for i, x := range arr2 {
        pos[x] = i
    }
    arr := make([][2]int, len(arr1))
    for i, x := range arr1 {
        if p, ok := pos[x]; ok {
            arr[i] = [2]int{
                p,
                x,
            }
        } else {
            arr[i] = [2]int{
                len(arr2),
                x,
            }
        }
    }
    sort.Slice(arr, func(i, j int) bool {
        return arr[i][0] < arr[j][0] || arr[i][0] == arr[j][0] && arr[i][1] < arr[j][1]
    })
    for i, x := range arr {
        arr1[i] = x[1]
    }
    return arr1
}

复杂度分析

  • 时间复杂度:$O(m+n\log(n+1))$,其中 $m,n$ 分别为两个数组的长度。建立名次和配对是线性工作,排序占主要开销。
  • 空间复杂度:$O(m+n)$,分别保存名次映射和配对数组,返回值复用 arr1。

关键点总结

[!green]

  • 自定义顺序先由名次划分优先组,未指定值只在最后一组按数值排序。
  • Java 单键利用非负值域分开两组;Go 双键直接保存组别和原值,不要把两份代码的字段顺序混淆。
  • 键决定位置,原值决定返回内容,二者必须一起移动。

易错点总结

[!yellow]

  • 把 arr2 自身按数值排序,会破坏它给定的优先顺序。
  • 把哈希表查询结果零当作“不存在”,会丢掉 arr2 中第一个值的优先级。
  • 对 arr1 去重,会丢掉应当保留的重复项。
  • 将 Java 的 m+value 写法直接用于任意负数值域,可能让未指定值混入优先名次;本实现按原文的非负范围构造键。

相似题目

题目 难度 关联与区别
791. 自定义字符串排序 中等 同样由外部序列规定一部分元素的排序优先级,本题对象是整数而不是字符。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/11913221
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!