我需要一些帮助来找到算法的一般思路来解决以下问题。在任务中给了我这个问题。看起来它应该可以通过贪婪的方法解决,但我无法找到一个简单的解决方案。这是问题描述:
您将获得 N 数字a_1 ... a_n
的序列,以便0 = a_1 < a_2 < ... < a_n
。您必须消除这些数字的最多 M ,以便最大化任意两个连续数字之间的最小差异a_i+1 - a_i
。
您可能无法消除第一个和最后一个元素a_0
和a_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
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
答案 0 :(得分:5)
[编辑:我最初声称ElKamina's answer错了,但我现在已经说服自己,这不仅是正确的,而且与我(后来的)几乎相同回答:-P虽然我的口味还有点简洁!]
这是一个精确的 O(NM ^ 2) - 时间,O(NM) - 空间 dynamic programming算法,可以在所有OP&#39;以毫秒为单位的示例。基本的想法是:
在下文中,x [i]表示列表中的第i个数字,索引从0开始。
令f(i,j)是从位置0 <= i <1开始的数字列表的后缀可获得的最佳(最大最小)间隔大小。 N通过保持这个(即第i个)数并准确删除后来(不一定是连续的)数字的j。以下递归显示了如何计算:
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))
min(j, N-i-2)
不是仅仅是普通的j来防止删除&#34;超过结束&#34;。我们需要的唯一基本情况是:
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)
更详细地说,为了计算f(i,j),我们所做的是循环从位置i + 1开始的连续删除的所有可能数(包括零),在每种情况下计算(a)的最小值由这个删除块形成的间隔和(b)从该块右边的第一个未删节数开始的子问题的最优解。 指定块(x [i])中的第一个数字不被删除是很重要的,这样前一个(父)子问题的间隔总是&#34;上限&#34 ; 这是一个棘手的部分,花了我一段时间来弄明白。
如果您对上面的普通递归进行编码,它将起作用,但在M和N中需要时间指数。通过memoising f(),我们保证其主体最多运行N * M次(每个不同的参数组合一次)。每次运行该函数时,它会执行O(M)工作扫描,通过越来越长的删除块,整个O(NM ^ 2)时间。
您不能通过使用较少的删除来创建更大的间隙,因此可以通过查看M + 1结果f(0,M),f(0,M-1),...找到总体最大最小间隔大小。 。,f(0,0)表示小于前一个数字的第一个数字:前一个数字是答案,f()的第二个参数是所需的最小删除数。要找到最佳解决方案(即删除的特定数字列表),您可以记录在单独的前任数组中做出的决策,以便p [i,j]给出d的值(可以将其转换为先前的值i和j)导致f(i,j)的最优解。 (也许&#34;前任&#34;在这里令人困惑:它指的是在当前子问题之前解决的子问题,尽管这些子问题出现在&#34;之后#34;(在右边) of)代表当前子问题的后缀。)然后可以遵循这些链接以恢复删除/不删除决定。
有了这样一个棘手的问题,看看错误的方法并确切地看出他们出错的地方会有所帮助......:/ /我以为我解决了这个问题,但我没有注意到要求返回尽可能少删除的解决方案,而我最初尝试解决这个问题并不起作用。
首先,我尝试将f(i,j)定义为从位置0&lt; = i&lt; 0开始的数字列表的后缀可获得的最佳(最大最小)间隔大小。 N通过保持该(即第i个)数字并删除从后到(不一定是连续)数字的0到j 的任何地方。但是这引起了一个微妙的问题:不一定是你可以从最优解决方案到子问题的最佳解决方案。我最初认为这可以通过更改函数来修复,以返回(间隔大小,实现该间隔大小所需的最小删除次数)对而不仅仅是间隔大小,并使其在共享最大最小间隔的操作之间断开关系通过始终选择最小化删除次数的操作来确定大小。但这一般情况并非如此,因为子问题的最佳解决方案(即数字列表的某些后缀)将花费删除使得该区域中的最小间隔大小尽可能大,即使这些删除变得浪费因为完整解决方案的前缀无论如何都会迫使总体最小值降低。这是一个反例,使用f()返回(间隔大小,达到该大小所需的最小删除次数)对:
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])
我还没有显示为f(0,1)返回的对的第二个元素的工作,因为它很难简洁地表达,但显然它将是1,因为两个替代方案都需要1删除。
答案 1 :(得分:4)
使用动态编程。
线索X(i,j)包含与第一个i元素的最小距离,其中j被选中(即未删除)。
这将为您提供准确的解决方案。复杂度= O(MN ^ 2),因为对于每个i值,只有m个有效值为j,并且每次调用该函数都需要进行O(M)工作。
让元素为A1,A2,...,An
更新公式为:
X(i + 1,j + 1)=最大(Min(A(i + 1)-Ak,Xkj),k <= i)
[由j_random_hacker编辑以添加评论中的信息]
答案 2 :(得分:1)
我想我得到了解决方案。它至少适用于两个样本集。它不一定返回与答案相同的集合,但它返回的集合具有相同的最小值。它也是迭代和贪婪的,这很好,并且有很多方法可以优化它。看起来像是MLog(N)。
重要的是要意识到数字无关紧要 - 只有它们之间的差异才有意义。当你“删除一个数字”时,你实际上只是将两个相邻的差异结合起来。我的算法将关注于差异。回到哪些项目导致这些差异并随时删除是一件简单的事情。
第3步有一点我被标记为任意决定。它可能不应该,但你正在进入足够边缘的情况下,这是一个你想要添加多少复杂性的问题。这种任意性允许存在多个不同的正确答案。如果您看到两个具有相同值的邻居,此时我会随意选择一个。理想情况下,你应该检查一对2,然后是3等邻居,并支持较低的邻居。我不确定如果你在扩张时遇到边缘该怎么办。最终,要完美地完成这一部分,您可能需要递归调用此函数并查看哪个评估更好。
最多删除8个: 0 3 7 10 15 26 38 44 53 61 76 80 88 93 100
[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个: 0 3 7 10 15 18 26 31 38 44 53 60 61 73 76 80 81 88 93 100
[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,5,5,7,5,7] M = 1
我们会删除5的下一个,但只允许删除1,并且有3,所以我们在这里停止。我们的最低差异是5,与解决方案相匹配。
注意:根据SauceMaster提供的1,29,30,31,59案例编辑,将相同的 X 值组合起来以避免这样做。 / p>
答案 3 :(得分:1)
我希望不使用全组合方法,但经过多次尝试后,似乎是将我的结果与j_random_hacker匹配的唯一方法。 (下面的一些评论涉及到这个答案的早期版本。)我对J_random_hacker / ElKamina的算法在Haskell('jrhMaxDiff')中表达的简洁性印象深刻。他的函数'compareAllCombos'寻找两种方法结果的差异:
*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