查找数组中的最大段,使其中的最小值大于或等于段的大小

时间:2015-01-29 18:38:58

标签: algorithm ocaml

我试图在O(n)复杂性中执行以下任务:

Given an array [|x_1,x_2,...,x_n|] return the biggest s such that there exists 
a segment of length s - [|x_i,x_(i+1),...,x_j|], in which the minimum value 
is greater or equal (j-i+1).

更新: 这里有一个不正确的O(n)解决方案,我设法编写了正确的(至少我认为是这样),简单的O(n * log n)。这是:

let segment a = 
    let n = Array.length a in
    let pointer_1 = ref 0 in
    let pointer_2 = ref 0 in
    let q = ref (put empty_queue (a.(0), 0)) in
    let best = ref 0 in
    begin
        while (!pointer_2 < n) do
            let size = (!pointer_2 - !pointer_1 + 1) in
            q := put !q (a.(!pointer_2), !pointer_2);
            let ((lmin, pmin), qright) = getmin !q in
            if (size > lmin) then begin
                best := max !best (size-1);
                pointer_1 := pmin + 1;
                pointer_2 := !pointer_2 + 1;
                q := qright
            end
            else
                begin
                    best := max !best (size);
                    pointer_2 := !pointer_2 + 1
                end
        done;
        !best
    end;;

其中q是对优先级队列的引用,该优先级队列首先返回最小元素。那么,这可以用O(n)以简单的方式完成吗?

1 个答案:

答案 0 :(得分:1)

这是一个不错的小谜题!这是一个基于双端队列的解决方案。

我的算法与你的算法大致相同,只是我使用dequeue来跟踪最小值:队列包含一个递增的值序列:第一个是当前间隔的最小值,第二个是在第一个最小值之后开始的子间隔的最小值,依此类推。

当我使左指针超过最小值时,我从队列的左侧移除此值,队列的其余部分自然以剩余间隔的最小值开始。

当我前进右指针时,我会检查队列右侧的新值。如果新值较小,我会删除最右边的条目,然后再次检查剩余的队列。删除所有较大的值后,我可以在出列结束时添加新值。

当在数组中找到非正数值时,我必须添加一个特殊情况:同时推进两个指针。

注意:我使用(并返回)半开放区间,因为它们比封闭区间更不容易出错。

此代码仅经过轻微测试。要表明它是O(n),请注意每个值在出队时最多输入一次。

let segment a =
  let len = Array.length a in
  let p1 = ref 0 in
  let p2 = ref 0 in
  let bestlen = ref 0 in
  let best = ref [(0,0)] in
  let q = Dq.make len in
  while !p2 < len do
    let l = !p2 - !p1 in
    if (Dq.is_empty q || Dq.peek_left q > l) && a.(!p2) > l
    then begin
      while not (Dq.is_empty q) && Dq.peek_right q > a.(!p2) do
        Dq.pop_right q;
      done;
      Dq.push_right q a.(!p2);
      incr p2;
    end else if !p1 < !p2 then begin
      if a.(!p1) = Dq.peek_left q then Dq.pop_left q;
      incr p1;
    end else begin
      assert (Dq.is_empty q);
      incr p1;
      incr p2;
    end;
    let l = !p2 - !p1 in
    if l > !bestlen then begin
      bestlen := l;
      best := [];
    end;
    if l = !bestlen then best := (!p1, !p2) :: !best;
  done;
  !best
;;

这是一个双端队列的简单实现,而不是特定于上面的代码:

module Dq = struct
  type t = {
    mutable hd : int;
    data : int array;
    mutable tl : int;
  }
  let make len = {
    hd = 0;
    data = Array.make len 0;
    tl = 0;
  };;
  let push_right q x =
    assert (q.tl < Array.length q.data);
    q.data.(q.tl) <- x;
    q.tl <- q.tl + 1;
  ;;
  let is_empty q = q.tl = q.hd;;
  let peek_right q =
    assert (q.hd < q.tl);
    q.data.(q.tl - 1)
  ;;
  let peek_left q =
    assert (q.hd < q.tl);
    q.data.(q.hd)
  ;;
  let pop_left q =
    assert (q.hd < q.tl);
    q.hd <- q.hd + 1;
  ;;
  let pop_right q =
    assert (q.hd < q.tl);
    q.tl <- q.tl - 1;
  ;;
end