使用OCaml中的列表找到下一个最大的数字

时间:2013-03-25 17:05:08

标签: algorithm functional-programming ocaml

这是一个采访问题:

  

找到可以通过给定数字的相同数字形成的下一个最大数字。

     

输入:4765,输出:5467

如果使用array,那就不那么难了。

  1. 我们将数字转换为数字数组。
  2. 我们扫描数组,记录下一个邻居更大的最新(最低位置)数字(比如x
  3. 如果下一个邻居较小(这意味着它正在下降),那么我们会记录大于x的最小数字(比如y
  4. 最后,我们交换x和y
  5. 但是,在OCaml中,如果我们使用list,那么我不知道该怎么做。似乎算法难以递归。

    有什么建议吗?

    编辑:

    请注意我希望以实用的方式(使用列表

    修改

    通过关注@gorestorm的建议,我实现了这样:

    exception NotFound;;
    
    
    let rev_list_of_int n = 
      let rec process n = 
        let x = n / 10 and y = n mod 10 
        in 
        if x = 0 && y = 0 then []
        else 
          y::(process x)
      in 
      process n;;
    
    let find_next_by_list n =
      let l = rev_list_of_int n
      in
      let rec find_x before_x x after_x = 
        match after_x with
        | [] | _::[] -> (before_x, -1, after_x)
        | hd1::hd2::tl -> 
          if hd1 > hd2 then (hd1::before_x, hd2, tl)
          else find_x (hd1::before_x) x (hd2::tl)
      in 
      let (bx, x, ax) = find_x [] (-1) l (* ax is rev *)
      in 
      if x = -1 then raise NotFound
      else 
        let rec find_y x path = function
        | [] -> raise NotFound
        | hd::tl -> 
          if hd > x then (path, hd, tl)
          else find_y x (hd::path) tl
        in 
        let (left, y, right) = find_y x [] (List.rev bx) (* left is rev, right is not  *)
        in 
        (* rev ax, then y, then right, then x, then rev left*)
        (List.rev_append (y::ax) right) @ (x::List.rev left)
    

3 个答案:

答案 0 :(得分:2)

为了以功能的方式实现这一点,以相反的顺序维护您的数字列表更有效 - 这样最快速变化的数字更接近列表的头部。这允许实现避免重新分配整个列表,从而提高分摊的性能。

给出digits的反向列表(从最不重要到最重要):

Starting at the least-significant digit (the head of the reversed list):
  look for the first digit smaller than its predecessor:  call it "a_k"
  save the list of digits after a_k as:  "unchanged"
Starting again at the least-significant digit:
  look for the first digit larger than a_k:  call it "a_l"
Accumulate the output list functional-style, starting with "unchanged":
  add a_l to the head of the list
  starting a third time at the least-significant digit:
    add each digit to the head of the output (reversing that portion of the list)
      stopping before a_l
    add a_k to the head of the output, instead of a_l
    after skipping a_l, continue to add digits from original to output
      stopping before a_k

简而言之:要实现功能风格,您必须在列表中构建“已修改”的交换/反向部分。您不必将列表保持在最低有效位数的第一个顺序(如上面的伪代码所假设的那样),但是如果这样做,您的next_permutation函数将具有O(1)的摊销性能及时和分配的记忆。

[编辑:更正,只有当所有数字都不同时,O(1)表现才会......]


如果您确实希望以最重要的数字优先顺序维护数字,您仍需要进行类似的扫描并重建交换/反向区域,然后是未更改区域的有序副本

答案 1 :(得分:1)

只需按字典顺序生成下一个排列:

Find the largest index k such that a[k] < a[k + 1]. If no such index exists, the permutation is the last permutation.
Find the largest index l such that a[k] < a[l]. Since k + 1 is such an index, l is well defined and satisfies k < l.
Swap a[k] with a[l].
Reverse the sequence from a[k + 1] up to and including the final element a[n].

wikipedia上找到。可以在here找到OCaml中的实现:

The following Ocaml code implements a variation on this algorithm (it returns the permutations of 1..n in reverse reverse-lexicographic order) :

(* exchange into l @ [x] @ accu x with the last element of l which is > x *)
let rec swap l x accu = match l with
| a::b::t when b > x -> a :: (swap (b::t) x accu)
| a::t -> (x::t) @ (a::accu)
| _ -> failwith "this can't happen"
;;

(* permut l m accu computes the permutation p' following p = rev(l)@m,
stores it into accu and recalls itself until p has no successor *)
let rec permut l m accu = match l,m with
| a::_, b::t when a > b -> let p = swap l b t in permut [] p (p::accu)
| _, b::t -> permut (b::l) t accu
| _, [] -> accu
;;

答案 2 :(得分:0)

正如Kwariz指出的那样(基本上),你可以使用与列表相同的解决方案。阵列解决方案可以在恒定时间内交换数字,但它仍然需要线性时间才能首先扫描它们。使用数字列表,您仍然可以进行线性扫描,还可以使用线性操作来交换数字。

有可能提出一个更漂亮的递归解决方案。问题是你正在使用列表中相当“全局”的属性。将它分解为通常的递归部分并不容易。但它并不太遥远。首先,如果您可以在列表的尾部执行操作,那么这也是整个列表的正确结果(重新添加头部)。