【问题标题】:Smallest sub-list that contains all numbers包含所有数字的最小子列表
【发布时间】:2019-04-04 19:31:45
【问题描述】:

我正在尝试用 sml 编写一个程序,该程序包含列表的长度、列表中出现的最大数量以及当然列表。然后它计算包含所有数字的最小“子列表”的长度。

我尝试使用滑动窗口方法,有两个索引,前和尾。前端首先扫描,当它找到一个数字时,它会在地图中写入它已经看到这个数字的次数。如果程序找到所有数字,则它调用尾部。尾部扫描列表,如果发现某个数字的出现次数超过 1 次,则将其删除。

到目前为止我尝试过的代码如下:

structure Key=
 struct
  type ord_key=int
  val compare=Int.compare
 end


fun min x y = if x>y then y else x;


structure mymap = BinaryMapFn ( Key  );

fun smallest_sub(n,t,listall,map)=
let
 val k=0
 val front=0
 val tail=0

 val minimum= n; 

 val list1=listall;
 val list2=listall;

 fun increase(list1,front,k,ourmap)=
  let 
   val number= hd list1
   val elem=mymap.find(ourmap,number)
   val per=getOpt(elem,0)+1

   fun decrease(list2,tail,k,ourmap,minimum)=
    let 
     val number=hd list2
     val elem=mymap.find(ourmap,number)
     val per=getOpt(elem,0)-1
     val per1=getOpt(elem,0)
    in
     if k>t then
      if (per1=1) then decrease(tl list2,tail+1,k-1,mymap.insert(ourmap,number,per),min minimum (front-tail))
      else decrease(tl list2,tail+1,k,mymap.insert(ourmap,number,per),min minimum (front-tail))
     else increase (list1, front,k,ourmap)
    end

  in
   if t>k then
    if (elem<>NONE) then increase (tl list1,front+1,k,mymap.insert(ourmap,number,per))
    else increase(tl list1,front+1,k+1,mymap.insert(ourmap,number,per))
   else (if (n>front) then decrease(list2,tail,k,ourmap,minimum) else minimum)
  end


in
  increase(list1,front,k,map)
end


fun solve (n,t,acc)= smallest_sub(n,t,acc,mymap.empty)

但是当我用这个 minimum_sub(10,3,[1,3,1,3,1,3,3,2,2,1]);这没用。我做错了什么??

示例:如果输入是 1,3,1,3,1,3,3,2,2,1 程序应该识别出列表中包含所有数字且最小的部分是 1,3, 3,2 和 3,2,2,1 所以输出应该是 4

【问题讨论】:

    标签: sml smlnj


    【解决方案1】:

    这个“包含所有值的最小子列表”的问题似乎在 没有成功答案的新问题。这是因为它不是minimal, complete, and verifiable example

    因为你使用“滑动窗口”的方法,索引前面后面 在您的输入中,花费 O(n) 时间来索引元素的列表并不理想。你 真的很想在这里使用数组。如果你的输入函数必须有一个列表,你 可以将其转换为用于算法目的的数组。

    我想在回答之前对代码进行清理,因为正在运行 您当前的手动代码有点困难,因为它非常简洁。这是一个 您如何抽象出是否给定的簿记的示例 子列表包含原始列表中每个元素的至少一个副本:

    编辑:我在最初发布后更改了下面的代码。

    structure CountMap = struct
        structure IntMap = BinaryMapFn(struct
            type ord_key = int
            val compare = Int.compare
        end)
    
        fun count (m, x) =
            Option.getOpt (IntMap.find (m, x), 0)
    
        fun increment (m, x) =
            IntMap.insert (m, x, count (m, x) + 1)
    
        fun decrement (m, x) =
            let val c' = count (m, x)
            in if c' <= 1
               then NONE
               else SOME (IntMap.insert (m, x, c' - 1))
            end
    
        fun flip f (x, y) = f (y, x)
        val fromList = List.foldl (flip increment) IntMap.empty
    end
    

    也就是说,CountMapint IntMap.map,其中 Int 代表 map的固定key类型,为int,前面有int参数 表示 map 的值类型,是 this 的次数的 count 值发生。

    在构建下面的initialCountMap 时,您使用CountMap.increment,并且 当您使用“滑动窗口”方法时,您使用CountMap.decrement 来 生成一个新的countMap,您可以对其进行递归测试。

    如果您将出现次数减少到 1 以下,则您正在查看一个子列表 不包含每个元素至少一次;我们排除任何解决方案 让CountMap.decrement 返回NONE

    所有这些机制都被抽象出来后,算法本身就变得很多了 更容易表达。首先,我想将列表转换为数组,以便 索引变成O(1),因为我们要做很多索引。

    fun smallest_sublist_length [] = 0
      | smallest_sublist_length (xs : int list) =
        let val arr = Array.fromList xs
            val initialCountMap = CountMap.fromList xs
            fun go countMap i j =
                let val xi = Array.sub (arr, i)
                    val xj = Array.sub (arr, j)
                    val decrementLeft = CountMap.decrement (countMap, xi)
                    val decrementRight = CountMap.decrement (countMap, xj)
                in
                    case (decrementLeft, decrementRight) of
                       (SOME leftCountMap, SOME rightCountMap) =>
                         Int.min (
                           go leftCountMap (i+1) j,
                           go rightCountMap i (j-1)
                         )
                     | (SOME leftCountMap, NONE) => go leftCountMap (i+1) j
                     | (NONE, SOME rightCountMap) => go rightCountMap i (j-1)
                     | (NONE, NONE) => j - i + 1
                end
        in
          go initialCountMap 0 (Array.length arr - 1)
        end
    

    这似乎可行,但是...

    执行Int.min (go left..., go right...) 会产生 O(n^2) 堆栈成本 内存(在你不能排除任何一个是最优的情况下)。这是一个 动态编程的好用例,因为您的递归子问题具有 常见的子结构,即

    go initialCountMap 0 10
     |- go leftCountMap 1 10
     |   |- ...
     |   `- go rightCountMap 1 9  <-.
     `- go rightCountMap 0 9        | possibly same sub-problem!
         |- go leftCountMap 1 9   <-'
         `- ...
    

    所以也许有一种方法可以将递归子问题存储在内存数组中,而不是 如果您知道此子问题的结果,请执行递归查找。如何 在 SML 中做 memoization 本身就是一个很好的问题。纯粹怎么做 非惰性语言中的函数式记忆是一种更好的记忆。

    您可以进行的另一项优化是,如果您找到子列表 独特元素数量的大小,您无需再寻找。这个号码 顺便说一下 initialCountMapIntMap 中的元素数量 可能有找到它的功能。

    【讨论】:

    • 我实际上通过分离增加和减少并从循环的函数中调用它们来解决问题。不过,您的代码似乎更清晰,所以我会研究一下。感谢您的宝贵时间!
    • @maverick98:既然你说你解决了这个问题,我将我的建议扩展为一个完整但低效的解决方案,并强调了不使用 memoization 的性能问题。
    • 谢谢你,很抱歉有多个问题,需要一些时间来适应 SML
    猜你喜欢
    • 2016-09-08
    • 2021-02-02
    • 2019-11-04
    • 1970-01-01
    • 1970-01-01
    • 2021-12-31
    • 2020-07-16
    • 2021-12-31
    • 2023-01-22
    相关资源
    最近更新 更多