交换后字典序最小 —— 排序分组重分配
交换后字典序最小数组 —— 排序分组重分配
日期: 2026-08-28
难度: 中等
标签: 排序、分组(连通块)、贪心、字典序
题目链接: [2948. 交换后的字典序最小数组]
题目描述
正整数数组 nums 和正整数 limit。每次操作可选任意两个下标 i、j,若 |nums[i] - nums[j]| ≤ limit 则可交换这两个元素。返回执行任意次操作后能得到的字典序最小数组。
- 示例:
nums = [1,5,3,9,8], limit = 2→[1,3,5,8,9] - 提示:
n ≤ 10^5,nums[i] ≤ 10^9
第一次思路
逐位贪心:先排序得到 temp,从前往后扫描,希望每一位都放尽可能小的数:
- 若
temp[i] == nums[i]跳过 - 若
|temp[i] - nums[i]| ≤ limit直接交换 - 否则尝试间接交换:找
j > i使|temp[j] - nums[i]| ≤ limit且|temp[i] - temp[j]| ≤ limit,先换temp[j]和nums[i],再换temp[i]和temp[j]
// 第一版:直接交换 + 一层间接交换(卡住) |
问题:间接交换可能不止一层——nums[i] 也许要经过两跳、三跳才能换到位。只试一层就放弃会漏解,但继续枚举所有跳数既复杂又难证贪心正确。这个方向走进了死胡同。
最终方案
关键洞察:把”任意次交换”看成图上的连通性——每个元素是点,差值 ≤ limit 连一条边。能通过一系列交换互达 ⇔ 在同一连通块里:
同一连通块内:可以任意排列(链式交换可达) |
注意陷阱:|a-b| ≤ limit 本身不传递(a≈b、b≈c 推不出 a≈c),但”通过中间值链式交换“是可传递的。所以排序后,只要相邻两个值差 ≤ limit 就在同一块——整段连通。
算法(排序 + 分组 + 重分配):
temp = sort(nums)- 分组:
temp[i] - temp[i-1] > limit则开新块,group[i]记录块号 - 值 → 块号映射
valToGroup(重复值排序后连续,天然同块,无需特判) - 每块记录在
temp中的起始下标start[g],指针ptr[g]从start[g]递增 - 遍历原数组:
g = valToGroup[nums[i]],ans[i] = temp[ptr[g]++]——每块内从最小开始取
正确性:每个位置只能取自己块内的值(块间无法交换);块内可任意排列,取”块内最小未用值”不损害后续任何选择。从前往后逐位取最小 ⇒ 字典序最小。
完整代码
class Solution { |
复杂度分析
| 复杂度 | 分析 |
|---|---|
| 时间复杂度 | O(n log n) — 排序 O(n log n),分组 + 映射 + 构造各 O(n) |
| 空间复杂度 | O(n) — sorted / group / 哈希表 / ans |
补充:从 block/bp 到 start/ptr 的简化
我第一版 AC 用的是 block(vector<vector<int>> 存每块元素)+ bp(每块剩余数量)+ 从后往前取(block[g][size - bp[g]])。正确但绕。因为块在排序数组里就是连续的一段,只需记起始下标 + 指针递增:
- 去掉
block:直接用sorted取数 - 去掉
bp:ptr[g]从start[g]开始自增 - 三个数组(group / start / ptr)+ 一个哈希表,逻辑更直白
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Chippandaの技术小站!
评论
