编辑2:
这是对您问题的另一种看法。
让我们以图形方式考虑间隔:
1 1 1 2 2 2 3
0-2-4--7--0--3---7-0--4--7--0
[-------]
[-----------------]
[---------]
[--------------]
[-----]
当在下限上按升序排序时,我们可以得到类似于上面的区间列表的东西([2;10];[4;24];[7;17];[13;30];[20;27])
。每个下限都表示一个新区间的开始,也将标志着数字重复的另一个“级别”的开始。相反,上限标记该级别的结束,并降低一个的重复级别。因此,我们可以将上述内容转换为以下列表:
[2;+];[4;+];[7;+][10;-];[13;+];[17;-][20;+];[24;-];[27;-];[30;-]
其中第一个值表示边界的等级,第二个值表示边界是下限 ( +
) 还是上限 ( -
)。第 n 个元素的计算是通过遵循列表来完成的,遇到下限或上限时提高或降低重复级别,并使用重复级别作为计数因子。
让我们再次以图形方式考虑列表,但作为直方图:
3333 44444 5555
2222222333333344444555
111111111222222222222444444
1 1 1 2 2 2 3
0-2-4--7--0--3---7-0--4--7--0
上面的视图与第一个视图相同,所有间隔都垂直排列。
1
是第一个,2
第二个等的元素。实际上,这里重要的是每个索引处的高度,对应于每个索引在所有区间的联合中重复的次数。
3333 55555 7777
2223333445555567777888
112223333445555567777888999
1 1 1 2 2 2 3
0-2-4--7--0--3---7-0--4--7--0
| | | | | | || | |
我们可以看到直方图块从区间的下限开始,到上限或下限前一个单位结束,因此必须相应地修改新符号。
对于包含n 个区间的列表,作为第一步,我们将列表转换为上面的符号(O(n)),并按递增的绑定顺序对其进行排序(O(nlog(n)))。然后计算数字的第二步在O(n)中,总平均时间在O(nlog(n) ) 中。
这是 OCaml 中的一个简单实现,使用1
and-1
代替 '+' 和 '-'。
(* transform the list in the correct notation *)
let rec convert = function
[] -> []
| (l,u)::xs -> (l,1)::(u+1,-1)::convert xs;;
(* the counting function *)
let rec count r f = function
[] -> raise Not_found
| [a,x] -> (match f + x with
0 -> if r = 0 then a else raise Not_found
| _ -> a + (r / f))
| (a,x)::(b,y)::l ->
if a = b
then count r f ((b,x+y)::l)
else
let f = f + x in
if f > 0 then
let range = (b - a) * f in
if range > r
then a + (r / f)
else count (r - range) f ((b,y)::l)
else count r f ((b,y)::l);;
(* the compute function *)
let compute l =
let compare (x,_) (y,_) = compare x y in
let l = List.sort compare (convert l) in
fun m -> count m 0 l;;
注意: - 如果寻求的数字高于间隔,上述函数将引发异常。下面的其他方法不考虑这种极端情况。- OCaml 中使用的列表排序功能是归并排序,它在O(nlog(n))中有效执行。
编辑:
看到您可能有非常大的间隔,我最初给出的解决方案(见下文)远非最佳。相反,我们可以通过转换列表来使事情变得更快:我们尝试通过搜索重叠列表来压缩间隔列表,并通过为间隔添加前缀、重叠的几倍以及为间隔添加后缀来替换它们。然后我们可以直接计算列表中每个元素所涵盖的条目数。查看上面的拆分(前缀、中缀、后缀),我们看到进行处理的最佳结构是二叉树。该树的节点可以选择具有前缀和后缀。所以节点必须包含:
i
节点中的一个区间
- 一个整数,给出
i
列表中的重复次数,
- 下面所有区间的左子树
i
- 上述所有区间的右子树
i
有了这个结构,树就会自动排序。这是体现该树的 ocaml 类型的示例。
type tree = Empty | Node of int * interval * tree * tree
现在转换算法归结为构建树。
此函数从其组件中创建一棵树:
let cons k r lt rt =
the tree made of count k, interval r, left tree lt and right tree rt
此函数递归地在树中插入一个区间。
let rec insert i it =
let r = root of it
let lt = the left subtree of it
let rt = the right subtree of it
let k = the count of r
let prf, inf, suf = the prefix, infix and suffix of i according to r
return cons (k+1) inf (insert prf lt) (insert suf rt)
构建树后,我们对树进行前序遍历,使用节点的计数来加速第 n 个元素的计算。
下面是我之前的回答。
以下是我的解决方案的步骤:
- 您需要在每个间隔的下限按升序对间隔列表进行排序
- 您需要一个双端队列
dq
(或在某些时候反转的列表)来存储间隔
这是代码:
let lower i = lower bound of interval i
let upper i = upper bound of i
let il = sort of interval list
i <- 0
j <- lower (head of il)
loop on il:
i <- i + 1
let h = the head of il
let il = the tail of il
if upper h > j then push h to dq
if lower h > j then
il <- concat dq and il
j <- j + 1
dq <- empty
loop
if i = k then return j
loop
该算法通过简单地遍历区间来工作,仅考虑相关区间,并计算i
联合中元素的等级和该元素的值j
。当达到目标排名k
时,返回该值。
复杂度大致在 O(k) + O(sort(l)) 中。