假设我们有一个列表/正整数数组x1,x2,...,xn。 我们可以对这个序列进行 join 操作,这意味着我们可以用一个元素替换两个彼此相邻的元素,这是这些元素的总和。例如:
- > array / list:[1; 2; 3; 4; 5; 6]
主要问题是找到给定序列的最小连接操作,之后该序列将按递增顺序排序。
注意:空元素和单元素序列 按递增顺序排序。
基本示例:
for [4; 6; 5; 3; 9]解决方案是1(我们加入 5和3)
表示[1; 3; 6; 5]解决方案也是1(我们加入 6和5)
我正在寻找的是一种解决此问题的算法。它可以是伪代码,C,C ++,PHP,OCaml或类似代码(我的意思是:如果您使用其中一种语言编写解决方案,我会理解解决方案)。
答案 0 :(得分:7)
这是使用动态规划解决的理想问题,@ lijie所描述的重现是正确的方法,只需稍作调整即可确保考虑所有可能性。有两个关键观察结果:(a)任何连接操作序列都会导致原始向量的一组非重叠求和子序列,以及(b)对于最佳连接序列,如果我们看在任何求和子序列(m ... n)的右边,该部分是问题的最佳解决方案:“找到子矢量(n + 1)... N的最佳连接序列,以便得到对最终序列进行排序,并且所有元素都是> = sum(m ... n)。
直接实现递归当然会导致指数时间算法,但使用动态编程的简单调整使其成为O(N ^ 2),因为基本上所有(m,n)对都被认为是一次。使用动态编程实现递归的一种简单方法是使(m,n)索引的数据结构在计算后存储f(m,n)的结果,以便下次调用f(m)时,n),我们可以查找以前保存的结果。以下代码使用R编程语言执行此操作。我正在使用我们想要找到最小连接数的公式来获得 非递减序列。 对于那些刚接触R的人来测试这段代码,只需从任何镜像下载R(Google“R Project”),将其激活,并将两个函数定义(f和solve)粘贴到控制台中,然后使用“solve(c(...))”解决任何向量在下面的例子中。
f <- function(m,n) {
name <- paste(m,n)
nCalls <<- nCalls + 1
# use <<- for global assignment
if( !is.null( Saved[[ name ]] ) ) {
# the solution for (m,n) has been cached, look it up
nCached <<- nCached + 1
return( Saved[[ name ]] )
}
N <- length(vec) # vec is global to this function
sum.mn <- -Inf
if(m >= 1)
sum.mn <- sum( vec[m:n] )
if(n == N) { # boundary case: the (m,n) range includes the last number
result <- list( num = 0, joins = list(), seq = c())
} else
{
bestNum <- Inf
bestJoins <- list()
bestSeq <- c()
for( k in (n+1):N ) {
sum.nk <- sum( vec[ (n+1):k ] )
if( sum.nk < sum.mn ) next
joinRest <- f( n+1, k )
numJoins <- joinRest$num + k-n-1
if( numJoins < bestNum ) {
bestNum <- numJoins
if( k == n+1 )
bestJoins <- joinRest$joins else
bestJoins <- c( list(c(n+1,k)), joinRest$joins )
bestSeq <- c( sum.nk, joinRest$seq)
}
}
result <- list( num = bestNum, joins = bestJoins, seq = bestSeq )
}
Saved[[ name ]] <<- result
result
}
solve <- function(input) {
vec <<- input
nCalls <<- 0
nCached <<- 0
Saved <<- c()
result <- f(0,0)
cat( 'Num calls to f = ', nCalls, ', Cached = ', nCached, '\n')
cat( 'Min joins = ', result$num, '\n')
cat( 'Opt summed subsequences: ')
cat( do.call( paste,
lapply(result$joins,
function(pair) paste(pair[1], pair[2], sep=':' ))),
'\n')
cat( 'Final Sequence: ', result$seq, '\n' )
}
以下是一些示例运行:
> solve(c(2,8,2,2,9,12))
Num calls to f = 22 , Cached = 4
Min joins = 2
Opt summed subsequences: 2:3 4:5
Final Sequence: 2 10 11 12
> solve(c(1,1,1,1,1))
Num calls to f = 19 , Cached = 3
Min joins = 0
Opt summed subsequences:
Final Sequence: 1 1 1 1 1
> solve(c(4,3,10,11))
Num calls to f = 10 , Cached = 0
Min joins = 1
Opt summed subsequences: 1:2
Final Sequence: 7 10 11
> solve(c (2, 8, 2, 2, 8, 3, 8, 9, 9, 2, 9, 8, 8, 7, 4, 2, 7, 5, 9, 4, 6, 7, 4, 7, 3, 4, 7, 9, 1, 2, 5, 1, 8, 7, 3, 3, 6, 3, 8, 5, 6, 5))
Num calls to f = 3982 , Cached = 3225
Min joins = 30
Opt summed subsequences: 2:3 4:5 6:7 8:9 10:12 13:16 17:19 20:23 24:27 28:33 34:42
Final Sequence: 2 10 10 11 18 19 21 21 21 21 26 46
请注意,@ kotlinski考虑的序列的最小连接数为 30,而不是32或33。
答案 1 :(得分:3)
import Data.List (inits)
joinSequence :: (Num a, Ord a) => [a] -> Int
joinSequence (x:xs) = joinWithMin 0 x xs
where joinWithMin k _ [] = k
joinWithMin k x xs =
case dropWhile ((< x) . snd) $ zip [0..] $ scanl1 (+) xs
of (l, y):_ -> joinWithMin (k + l) y $ drop (l+1) xs
_ -> k + length xs
joinSequence _ = 0
在每一步中,抓住更多元素,直到它们的总和不小于最后一个。如果元素用完,只需将剩下的所有元素加入到前一组中。
那是错的。
joinSequence :: (Num a, Ord a) => [a] -> Int
joinSequence = joinWithMin 0 0
where joinWithMin k _ [] = k
joinWithMin k m xs =
case dropWhile ((< m) . snd) $ zip [0..] $ scanl1 (+) xs
of [] -> k + length xs
ys -> minimum [ joinWithMin (k+l) y $ drop (l+1) xs
| (l, y) <- ys ]
尝试尽可能加入并尽量减少。我想不出一个聪明的启发式来限制回溯,但这应该是O(n²)dynamic programming和O(2 n )写的。
答案 2 :(得分:1)
动态编程方法:
让原始数组为a[i], 0 <= i < N
。
将f(m, n)
定义为使a[n..N-1]
排序所需的最小连接数,以便排序子列表中的所有元素都为>
(或>=
,如果是另一个期望变量)a[m..n-1]
的总和(让空列表的总和为-inf
)。
基本案例是f(m, N) = 0
(子列表为空)。
递归为f(m, n) = min_{n < k <= N s.t. sum(a[n..k-1]) > sum(a[m..n-1])} f(n, k) + k-n-1
。如果没有适合的k值,那么让f(m, n) = inf
(任何>= N
也可以使用,因为最多只有N-1
个连接。
按f(m,n)
和m
的降序计算n
。
然后,所需答案为f(0,0)
。
修改强>
哎呀这基本上是第二个答案,我相信,虽然我对Haskell不太熟悉,不知道它到底在做什么。
答案 3 :(得分:0)
一些Haskell代码:
sortJoin (a:b:c:xs)
| a <= b = a : sortJoin (b:c:xs)
| a+b <= c = a+b : sortJoin (c:xs)
| otherwise = sortJoin (a:b+c:xs)
sortJoin (a:b:[]) = if a <= b then [a,b] else [a+b]
sortJoin a@_ = a
edits xs = length xs - length (sortJoin xs)
更新:使用test = [2,8,2,2,8,3,8,9,9,2,9,8,8,7,4,2,7,5,9, 4,6,7,4,7,3,4,7,9,1,2,5,1,8,7,3,3,6,3,8,5,6,5]
...现在我们得到:
> sortJoin test
[2,8,12,20,20,23,27,28,31,55]
> edits test
32
答案 4 :(得分:0)
希望保持简单。这是一些指数时间的伪代码。
Function "join" (list, max-join-count, join-count) ->
Fail if join-count is greater than max-join-count.
If the list looks sorted return join-count.
For Each number In List
Recur (list with current and next number joined, max-join-count, join-count + 1)
Function "best-join" (list) ->
max-join-count = 0
while not join (list, max-join-count++)
这是Clojure的一个实现:
(defn join-ahead [f i v]
(concat (take i v)
[(f (nth v i) (nth v (inc i)))]
(drop (+ 2 i) v)))
(defn sort-by-joining
"Sort a list by joining neighboring elements with `+'"
([v max-join-count join-count]
(if (or (nil? max-join-count)
(<= join-count max-join-count))
(if (or (empty? v)
(= v (sort v)))
{:vector v :join-count join-count}
(loop [i 0]
(when (< (inc i) (count v))
(let [r (sort-by-joining (join-ahead + i v)
max-join-count
(inc join-count))]
(or r (recur (inc i)))))))))
([v max-join-count]
(sort-by-joining v max-join-count 0))
([v]
(sort-by-joining v nil 0)))
(defn fewest-joins [v]
(loop [i 0]
(if (sort-by-joining v i)
i
(recur (inc i)))))
(deftest test-fewest-joins
(is (= 0 (fewest-joins nil)))
(is (= 1 (fewest-joins [4 6 5 3 9])))
(is (= 6 (fewest-joins [1 9 22 90 1 1 1 32 78 13 1]))))
答案 5 :(得分:0)
这是F#中的pchalasani代码,有一些修改。 memoization类似,我在O(1)时间内为sums添加了sumRange函数生成器,并将起始位置移动到f 1 0以跳过在minJoins中检查n = 0。
let minJoins (input: int array) =
let length = input.Length
let sum = sumRange input
let rec f = memoize2 (fun m n ->
if n = length then
0
else
let sum_mn = sum m n
{n + 1 .. length}
|> Seq.filter (fun k -> sum (n + 1) k >= sum_mn)
|> Seq.map (fun k -> f (n + 1) k + k-n-1)
|> Seq.append {length .. length}
|> Seq.min
)
f 1 0
完整代码。
open System.Collections.Generic
// standard memoization
let memoize2 f =
let cache = new Dictionary<_, _>()
(fun x1 x2 ->
match cache.TryGetValue((x1, x2)) with
| true, y -> y
| _ ->
let v = f x1 x2
cache.Add((x1, x2), v)
v)
// returns a function that takes two integers n,m and returns sum(array[n:m])
let sumRange (array : int array) =
let forward = Array.create (array.Length + 1) 0
let mutable total = 0
for i in 0 .. array.Length - 1 do
total <- total + array.[i]
forward.[i + 1] <- total
(fun i j -> forward.[j] - forward.[i - 1])
// min joins to sort an array ascending
let minJoins (input: int array) =
let length = input.Length
let sum = sumRange input
let rec f = memoize2 (fun m n ->
if n = length then
0
else
let sum_mn = sum m n
{n + 1 .. length}
|> Seq.filter (fun k -> sum (n + 1) k >= sum_mn)
|> Seq.map (fun k -> f (n + 1) k + k-n-1)
|> Seq.append {length .. length} // if nothing passed the filter return length as the min
|> Seq.min
)
f 1 0
let input = [|2;8;2;2;8;3;8;9;9;2;9;8;8;7;4;2;7;5;9;4;6;7;4;7;3;4;7;9;1;2;5;1;8;7;3;3;6;3;8;5;6;5|]
let output = minJoins input
printfn "%A" output
// outputs 30