求最小值“加入”序列操作
假设我们有一个正整数 x1, x2, ... , xn 的列表/数组。 我们可以对此序列执行join操作,这意味着我们可以用一个元素替换彼此相邻的两个元素,该元素是这些元素的总和。例如:
-> array/list: [1;2;3;4;5;6]
- 我们可以连接 2 和 3,并用 5 替换它们;
- 我们可以加入 5 和 6,并用 11 替换它们;
- 我们不能 加入 2 和 4;
- 我们不能 连接 1和3等。
主要问题是找到给定序列的最小连接操作,之后该序列将按升序排序。
注意:空序列和单元素序列按升序排序。
基本示例:
for [4; 6; 5; 3; 9] 解决方案是 1(我们加入 5 和 3)
对于 [1; 3; 6; 5] 解决方案也是 1(我们加入 6 和 5)
我正在寻找的是解决这个问题的算法。它可以是伪代码、C、C++、PHP、OCaml 或类似的形式(我的意思是:如果您用这些语言之一编写解决方案,我会理解解决方案)。
Let's say, we have a list/an array of positive integers x1, x2, ... , xn.
We can do a join operation on this sequence, that means that we can replace two elements that are next to each other with one element, which is sum of these elements. For example:
-> array/list: [1;2;3;4;5;6]
- we can join 2 and 3, and replace them with 5;
- we can join 5 and 6, and replace them with 11;
- we cannot join 2 and 4;
- we cannot join 1 and 3 etc.
Main problem is to find minimum join operations for given sequence, after which this sequence will be sorted in increasing order.
Note: empty and one-element sequences are sorted in increasing order.
Basic examples:
for [4; 6; 5; 3; 9] solution is 1 (we join 5 and 3)
for [1; 3; 6; 5] solution is also 1 (we join 6 and 5)
What I am looking for, is an algorithm that solve this problem. It could be in pseudocode, C, C++, PHP, OCaml or similar (I mean: I would understand solution, if You wrote solution in one of these languages).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
这是使用动态编程解决的理想问题,@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 新手,要测试此代码,只需从任何镜像(Google“R 项目”)下载 R,启动它,并将两个函数定义(f 和solve)粘贴到控制台中,然后使用“solve(c(...))”求解任何向量,如下例所示。
以下是一些示例运行:
请注意,@kotlinski 考虑的序列的最小连接数是 30,而不是 32 或 33。
This is an ideal problem to solve using Dynamic Programming, and the recurrence described by @lijie is exactly the right approach, with a few minor tweaks to ensure all possibilities are considered. There are two key observations: (a) Any sequence of join operations results in a set of non-overlapping summed subsequences of the original vector, and (b) For the optimal join-sequence, if we look to the right of any summed subsequence (m...n), that portion is an optimal solution to the problem: "find an optimal join-sequence for the sub-vector (n+1)...N such that the resulting final sequence is sorted, and all elements are >= sum(m...n).
Implementing the recurrence directly would of course result in an exponential time algorithm, but a simple tweak using Dynamic Programming makes it O(N^2), because essentially all (m,n) pairs are considered once. An easy way to implement the recurrence using Dynamic Programming is to have a data-structure indexed by (m,n) that stores the results of f(m,n) once they are computed, so that the next time we invoke f(m,n), we can lookup the previously saved results. The following code does this using the R programming language. I am using the formulation where we want to find the min-number of joins to get a non-decreasing sequence. For those new to R, to test this code, simply download R from any mirror (Google "R Project"), fire it up, and paste the two function definitions (f and solve) into the console, and then solve any vector using "solve(c(...))" as in the examples below.
Here are some sample runs:
Note that the min number of joins for the sequence considered by @kotlinski is 30, not 32 or 33.
贪心算法!
每一步,获取更多元素,直到它们的总和不小于最后一个。如果元素用完,只需将剩余的所有元素加入前一组即可。
那是错误的。
组合爆炸!
尝试每一种可能的加入并采取最少的加入。我想不出一种聪明的启发式方法来限制回溯,但这应该是 O(n²) 动态编程< /a> 和 O(2n) 如下所示。
Greedy algorithm!
At each step, grab more elements until their sum is not less than the last. If you run out of elements, just join all the ones that remain to the prior group.
That was wrong.
Combinatorial explosion!
Just try every possible joining and take the minimum. I couldn't think of a smart heuristic to limit backtracking, but this should be O(n²) with dynamic programming, and O(2n) as written.
动态规划方法:
设原数组为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 st sum(a[n..k-1]) > sum(a[m..n-1])} f(n, k) + kn-1
.如果没有合适的 k 值,则让f(m, n) = inf
(任何>= N
也可以,因为最多有N-1
加入)。按
m
和n
的降序计算f(m,n)
。那么,所需的答案是
f(0,0)
。编辑
哎呀,我相信这基本上是 ehemient 的第二个答案,尽管我对 Haskell 不够熟悉,无法确切知道它在做什么。
A dynamic programming approach:
Let the original array be
a[i], 0 <= i < N
.Define
f(m, n)
to be the minimum number of joins needed to makea[n..N-1]
sorted, such that all elements in the sorted sublist are>
(or>=
, if another variant is desired) the sum ofa[m..n-1]
(let the sum of an empty list to be-inf
).The base case is
f(m, N) = 0
(the sublist is empty).The recursion is
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
. If no values of k are suitable, then letf(m, n) = inf
(anything>= N
will also work, because there are at mostN-1
joins).Calculate
f(m,n)
in decreasing order ofm
andn
.Then, the desired answer is
f(0,0)
.EDIT
Oops this is basically ephemient's second answer, I believe, although I am not familiar enough with Haskell to know exactly what it is doing.
一些 Haskell 代码:
更新:使用 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]
...现在我们得到:
Some Haskell code:
UPDATE: Made this work with 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]
...now we get:
希望保持简单。这是一些指数时间的伪代码。
这是 Clojure 上的实现:
Hopefully keeping it simple. Here's some pseudo-code that's exponential time.
Here's an implementation on Clojure:
这是 F# 中的 pchalasani 代码,经过一些修改。记忆过程类似,我添加了一个 sumRange 函数生成器,用于在 O(1) 时间内求和,并将起始位置移动到 f 1 0 以跳过 minJoins 中 n = 0 的检查。
完整代码。
This is pchalasani code in F# with some modifications. The memoization is similar, I added a sumRange function generator for sums in O(1) time and moved the start position to f 1 0 to skip checking for n = 0 in minJoins.
Full code.