Algorithm 最大化序列中数字之间的差异

Algorithm 最大化序列中数字之间的差异,algorithm,language-agnostic,recurrence,Algorithm,Language Agnostic,Recurrence,我需要一些帮助来找到解决以下问题的算法的总体思路。这个问题已在作业中交给我了。看起来它应该可以通过贪婪的方法来解决,但我无法找到一个简单的解决方案。以下是问题描述: 您将获得一系列N数字a\u 1。。。a_n使得0=a_1

我需要一些帮助来找到解决以下问题的算法的总体思路。这个问题已在作业中交给我了。看起来它应该可以通过贪婪的方法来解决,但我无法找到一个简单的解决方案。以下是问题描述:

您将获得一系列N数字
a\u 1。。。a_n
使得
0=a_1
。您必须消除这些数字中最多M,以使任意两个连续数字之间的最小差值
a_i+1-a_i
最大化

不能删除第一个和最后一个元素,
a\u 0
a\u n
。此外,您必须消除尽可能少的数字:如果消除
M-1
您得到的最短距离为
D
,而消除
M
您仍然有相同的最小差值,则不得消除最后一个数字

我不是在要求这个问题的完整解决方案。关于算法的外观,只有一些指导原则

编辑:一些测试样本。请记住,可能存在多个有效的解决方案

Remove at most 7 from:
0 3 7 10 15 18 26 31 38 44 53 60 61 73 76 80 81 88 93 100

Solution:
0 7 15 26 31 38 44 53 60 73 80 88 93 100

使用动态规划

线索X(i,j)包含与前i个元素的最小距离,其中j已选定(即未删除)

这将为您提供精确的解决方案。复杂性=O(MN^2),因为对于每个i值,只有M个有效的j值,并且每个函数调用都需要进行O(M)工作

设元素为A1、A2、…、An

更新公式为:


X(i+1,j+1)=Max(Min(A(i+1)-Ak,Xkj)对于k我想我得到了解决方案。它至少在两个样本集上有效。它不一定返回与答案相同的集合,但它返回的集合具有相同的最小值。它也是迭代和贪婪的,这很好,有很多方法可以优化它。看起来它是MLog(N)

重要的是要意识到数字并不重要——只有它们之间的差异才重要。当你“删除一个数字”,您实际上只是将两个相邻的差异组合起来。我的算法将重点关注这些差异。返回导致这些差异的项目并在执行时删除是一件简单的事情

算法
  • 创建每个数字之间差异的有序列表/数组
  • 找到最小的差值x。如果x的计数>剩余的M,停止。您已经处于最佳状态
  • 对于从最左边开始的每个x值,将该差值与较低的相邻值合并(并删除该x)。如果相邻值相等,则您的决定是任意的。如果只有一个相邻值为x,则与另一个相邻值合并。(如果您没有选择,例如[1,1,…],然后与匹配的X组合,但如果可以,请避免使用。)
  • 返回到步骤2,直到用完M
  • 关于算法的注记 步骤3有一点我称之为武断的决定。这可能不应该,但你进入了一个足够尖锐的情况,这是一个你想增加多少复杂性的问题。这种武断性允许有多个不同的正确答案。如果你看到两个邻居具有相同的值,在这一点上,我会说y任意选择一个。理想情况下,您可能应该检查两个相距2,然后是3,以此类推的邻居对,并选择较低的一个。我不确定如果在展开时碰到边缘该怎么办。最终,为了完美地完成这一部分,您可能需要递归调用此函数,看看哪个函数的计算结果更好

    遍历示例数据 第二个第一个: 从以下位置移除最多8个: 0377101526384536176808893100

    [3,4,3,5,11,12,6,9,8,15,4,8,5,7]M=8

    删除3个。删除边只能在一个方向上添加:

    [7,3,5,11,12,6,9,8,15,4,8,5,7]M=7

    [7,8,11,12,6,9,8,15,4,8,5,7]M=6

    接下来,删除4:[7,8,11,12,6,9,8,15,12,5,7]M=5

    接下来,删除5:[7,8,11,12,6,9,8,15,12,12]M=4

    接下来,删除6:[7,8,11,12,15,8,15,12,12]M=3

    接下来,移除7:[15,11,12,15,8,15,12,12]M=2

    接下来,删除8:[15,11,12,15,23,12,12]M=1//注意,添加方向的任意决定

    最后,删除11:[15,23,15,23,12,12]

    请注意,在答案中,最小的差异是12

    第一个最后一个 从以下位置移除最多7个: 03771015182638344536061737680818893100

    [3,4,3,5,3,8,5,7,6,9,7,1,12,3,4,1,7,5,7]M=7

    移除1的:

    [3,4,3,5,3,8,5,7,6,9,8,12,3,4,1,7,5,7]M=6

    [3,4,3,5,3,8,5,7,6,9,8,12,3,5,7,5,7]M=5

    还有4个3,所以我们可以删除它们:

    [7,3,5,3,8,5,7,6,9,8,12,3,5,7,5,7]M=4

    [7,8,3,8,5,7,6,9,8,12,3,5,7,5,7]M=3

    [7,8,11,5,7,6,9,8,12,3,5,7,5,7]M=2//注意右边任意添加

    [7,8,11,5,7,6,9,8,12,8,5,7,5,7]M=1

    我们将删除5的下一个,但只允许删除1,并有3,所以我们停止在这里。我们的最低差异是5,匹配的解决方案


    注意:根据组合相同X值的想法进行编辑,以避免这样做,SauceMaster提出的1、29、30、31、59案例。

    [编辑:我最初声称这是错误的,但现在我相信这不仅是正确的,而且与我的(后来的)案例几乎相同回答:-P对我的口味来说还是有点简洁!]

    这里有一个精确的O(NM^2)-时间,O(NM)-空间算法,它可以在毫秒内获得所有OP示例的正确答案。其基本思想是:

  • 每当我们强制规定某一特定数字不应删除时,它就形成了一道“篱笆”
    Remove at most 8 from:
    0 3 7 10 15 26 38 44 53 61 76 80 88 93 100
    
    Solution:
    0 15 38 53 76 88 100
    
    f(i, j) = max(g(i, j, d)) over all 0 <= d <= min(j, N-i-2)
    g(i, j, d) = min(x[i+d+1] - x[i], f(i+d+1, j-d))
    
    f(N-1, 0) = infinity (this has the effect of making min(f(N-1), 0), z) = z)
    f(N-1, j > 0) = 0 (this case only arises if M > N - 2)
    
    Problem: M = 1, X = [10 15 50 55].
    
    f(2, 0) = (5, 0) (leaving [50 55])
    f(1, 1) = (40, 1) (delete 50 to leave [15 55]); *locally* this appears better
              than not deleting anything, which would leave [15 50 55] and yield
              a min-gap of 5, even though the latter would be a better choice for
              the overall problem)
    f(0, 1) = max(min(5, f(1, 1)), min(40, f(2, 0))
            = max(min(5, 40), min(40, 5))
            = (5, 1) (leaving either [10 15 55] or [10 50 55])
    
    *Main> compareAllCombos 7 4 4
    Nothing
    
    1. Group the differences: [0, 6, 11, 13, 22] => [[6],[5],[2],[9]]
    
    2. While enough removals remain to increase the minimum difference, extend the 
       minimum difference to join adjacent groups in all possible ways:
    
       [[6],[5],[2],[9]] => [[6],[5,2],[9]] and [[6],[5],[2,9]]...etc.
    
       Choose the highest minimum difference and lowest number of removals.
    
    import Data.List (minimumBy, maximumBy, groupBy, find)
    import Data.Maybe (fromJust)
    
    extendr ind xs = 
      let splitxs = splitAt ind xs
          (y:ys) = snd splitxs
          left = snd y
          right = snd (head ys)
      in fst splitxs ++ [(sum (left ++ right), left ++ right)] ++ tail ys
    
    extendl ind xs = 
      let splitxs = splitAt ind xs
          (y:ys) = snd splitxs
          right = snd y
          left = snd (last $ fst splitxs)
      in init (fst splitxs) ++ [(sum (left ++ right), left ++ right)] ++ tail (snd splitxs)
    
    extend' m xs =
      let results = map (\x -> (fst . minimumBy (\a b -> compare (fst a) (fst b)) $ x, x)) (solve xs)
          maxMinDiff = fst . maximumBy (\a b -> compare (fst a) (fst b)) $ results
          resultsFiltered = filter ((==maxMinDiff) . fst) results
      in minimumBy (\a b -> compare (sum (map (\x -> length (snd x) - 1) (snd a))) (sum (map (\x -> length (snd x) - 1) (snd b)))) resultsFiltered
       where 
         solve ys = 
           let removalCount = sum (map (\x -> length (snd x) - 1) ys)
               lowestElem = minimumBy (\a b -> compare (fst a) (fst b)) ys
               lowestSum = fst lowestElem
               lowestSumGrouped = 
                 map (\x -> if (fst . head $ x) == 0 
                               then length x 
                               else if null (drop 1 x) 
                                       then 1 
                                       else if odd (length x)
                                               then div (length x + 1) 2
                                               else div (length x) 2)
                 $ filter ((==lowestSum) . fst . head) (groupBy (\a b -> (fst a) == (fst b)) ys)
               nextIndices = map snd . filter ((==lowestSum) . fst . fst) $ zip ys [0..]
               lastInd = length ys - 1
           in if sum lowestSumGrouped > m - removalCount || null (drop 1 ys)
                 then [ys]
                 else do
                   nextInd <- nextIndices          
                   if nextInd == 0
                      then solve (extendl (nextInd + 1) ys)
                      else if nextInd == lastInd
                              then solve (extendr (nextInd - 1) ys)
                              else do 
                                a <- [extendl nextInd ys, extendr nextInd ys]
                                solve a
    
    pureMaxDiff m xs = 
      let differences = map (:[]) $ tail $ zipWith (-) xs ([0] ++ init xs)
          differencesSummed = zip (map sum differences) differences
          result = extend' m differencesSummed
          lowestSum = fst result
          removalCount = sum (map (\x -> length (snd x) - 1) (snd result))
      in if null (filter ((/=0) . fst) differencesSummed)
            then (0,0)
            else (removalCount, lowestSum)
    
    -- __j_random_hacker's stuff begins here
    
    -- My algorithm from http://stackoverflow.com/a/15478409/47984.
    -- Oddly it seems to be much faster when I *don't* try to use memoisation!
    -- (I don't really understand how memoisation in Haskell works yet...)
    jrhMaxDiff m xs = fst $ fromJust $ find (\(x, y) -> snd x > snd y) resultPairsDesc
      where
        inf = 1000000
        n = length xs
        f i j =
          if i == n - 1
             then if j == 0
                     then inf
                     else 0
             else maximum [g i j d | d <- [0 .. min j (n - i - 2)]]
        g i j d = min ((xs !! (i + d + 1)) - (xs !! i)) (f (i + d + 1) (j - d))
        resultsDesc = map (\i -> (i, f 0 i)) $ reverse [0 .. m]
        resultPairsDesc = zip resultsDesc (concat [(tail resultsDesc), [(-1, -1)]])
    
    -- All following code is for looking for different results between my and groovy's algorithms.
    -- Generate a list of all length-n lists containing numbers in the range 0 - d.
    upto 0 _ = [[]]
    upto n d = concat $ map (\x -> (map (\y -> (x : y)) (upto (n - 1) d))) [0 .. d]
    
    -- Generate a list of all length-maxN or shorter lists containing numbers in the range 0 - maxD.
    generateAllDiffCombos 1 maxD = [[x] | x <- [0 .. maxD]]
    generateAllDiffCombos maxN maxD =
      (generateAllDiffCombos (maxN - 1) maxD) ++ (upto maxN maxD)
    
    diffsToNums xs = scanl (+) 0 xs
    
    generateAllCombos maxN maxD = map diffsToNums $ generateAllDiffCombos maxN maxD
    
    -- generateAllCombos causes pureMaxDiff to produce an error with (1, [0, 0]) and (1, [0, 0, 0]) among others,
    -- so filter these out to look for more "interesting" differences.
    --generateMostCombos maxN maxD = filter (\x -> length x /= 2) $ generateAllCombos maxN maxD
    generateMostCombos maxN maxD = filter (\x -> length x > 4) $ generateAllCombos maxN maxD
    
    -- Try running both algorithms on every list of length up to maxN having gaps of
    -- size up to maxD, allowing up to maxDel deletions (this is the M parameter).
    compareAllCombos maxN maxD maxDel =
      find (\(x, maxDel, jrh, grv) -> jrh /= grv) $ map (\x -> (x, maxDel, jrhMaxDiff maxDel x, pureMaxDiff maxDel x)) $ generateMostCombos maxN maxD
    --  show $ map (\x -> (x, jrhMaxDiff maxDel x, pureMaxDiff maxDel x)) $ generateMostCombos maxN maxD