3134.找出唯一性数组的中位数

目标

给你一个整数数组 nums 。数组 nums 的 唯一性数组 是一个按元素从小到大排序的数组,包含了 nums 的所有 非空子数组中 不同元素的个数。

换句话说,这是由所有 0 <= i <= j < nums.length 的 distinct(nums[i..j]) 组成的递增数组。

其中,distinct(nums[i..j]) 表示从下标 i 到下标 j 的子数组中不同元素的数量。

返回 nums 唯一性数组 的 中位数 。

注意,数组的 中位数 定义为有序数组的中间元素。如果有两个中间元素,则取值较小的那个。

示例 1:

输入:nums = [1,2,3]
输出:1
解释:
nums 的唯一性数组为 [distinct(nums[0..0]), distinct(nums[1..1]), distinct(nums[2..2]), distinct(nums[0..1]), distinct(nums[1..2]), distinct(nums[0..2])],即 [1, 1, 1, 2, 2, 3] 。唯一性数组的中位数为 1 ,因此答案是 1 。

示例 2:

输入:nums = [3,4,3,4,5]
输出:2
解释:
nums 的唯一性数组为 [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3] 。唯一性数组的中位数为 2 ,因此答案是 2 。

示例 3:

输入:nums = [4,3,5,4]
输出:2
解释:
nums 的唯一性数组为 [1, 1, 1, 1, 2, 2, 2, 3, 3, 3] 。唯一性数组的中位数为 2 ,因此答案是 2 。

说明:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

思路

定义数组的唯一性数组为其所有 子数组 中不同元素个数从小到大排序,求唯一性数组的中位数。

长度为 n 的子数组的个数为 n(n+1)/2,唯一性数组的中位数下标为 n(n+1)/4 - 1 是第 (n(n+1)/2 + 1)/2 个元素。

问题的关键在于,如何快速判断数组中不同元素的个数。我们想要这样一个hash表,可以根据 start end 动态调整其中的元素。

一般来说枚举子数组使用的是双层循环,外层枚举起点,内层从起点开始枚举终点直到结尾,当然也可以外层枚举终点,内层枚举0到终点作为起点,时间复杂度为 O(n^2)。这里的问题在于如何保存区间与对应不重复元素个数的对应关系,以及如何计算不重复元素个数。本来 O(n^2) 就会超时,如果针对每个区间再循环判断,就更不行了。这里其实可以模拟变长的滑动窗口,通过修改窗口中加入与移除元素在map中对应的计数,如果计数为0则删除,这样map里的元素个数即为当前窗口内不重复元素个数。但是并没有保存这个状态(区间对应的不同元素个数)。我们可以将 start, end 压缩到一个long型数字中,倒是也可以记录。假如我们有了这个对应关系,我们还需要将它排序然后取中位数。

看了题解使用的是二分+滑动窗口,确实比较绕,我也没有仔细想清楚,这里面有几个关键点:

  • 唯一性数组中元素的取值范围是 1 ~ n,元素递增的步长为1,如果某个子数组比之前的子数组多了2个不同的元素,那么总是可以去掉其中一个使得子数组仅多1个不同元素。
  • 思考 唯一性元素的个数 小于等于 m 的子数组有多少个?找到唯一性元素个数第一次覆盖 (n(n+1)/2 + 1)/2 的 m 就是要找的答案。
  • 假设我们已经知道 m 对应的 cnt,只需要找到第一个大于等于 (n(n+1)/2 + 1)/2 的cnt即可,可以使用二分查找。
  • 问题转化为 计算唯一性元素的个数 小于等于 m 的子数组个数。使用滑动窗口。

而这里需要计算的是子数组中不同元素的个数,

// todo

代码

性能