将列表分成两部分,它们的总和彼此最接近
Posted
技术标签:
【中文标题】将列表分成两部分,它们的总和彼此最接近【英文标题】:divide list in two parts that their sum closest to each other 【发布时间】:2011-05-27 15:22:31 【问题描述】:这是一个难算法问题:
将列表分成两部分(总和),它们的总和彼此最接近(最)
问题中给出的列表长度为 1
例如:23 65 134 32 95 123 34
1.sum = 256
2.sum = 250
1.list = 1 2 3 7
2.list = 4 5 6
我有一个算法,但它不适用于所有输入。
-
初始化。列出 list1 = [], list2 = []
对元素进行排序(给定列表)[23 32 34 65 95 123 134]
弹出最后一个(最多一个)
插入差异较小的列表
实施: list1 = [], list2 = []
-
选择 134 插入列表 1。 list1 = [134]
选择 123 插入列表 2。因为如果您插入 list1 差异会变得更大 3. 选择 95 并插入 list2 。因为 sum(list2) + 95 - sum(list1) 更小。
等等……
【问题讨论】:
显示您的所有内容,以便其他人可以评论您出错的地方。 好的,我现在正在编辑。但是,我正在寻找不同且正确的算法 你看***.com/questions/890171/…了吗? 我搜索了,但我没有在 SO 中找到这个问题。谢谢 哎呀,我投票决定以完全欺骗的身份结束,现在看到迈克尔建议的问题(虽然仍然高度相关)具有两个列表大小相等的额外限制。 【参考方案1】:打字稿代码:
import * as _ from 'lodash'
function partitionArray(numbers: number[]):
arr1: number[]
arr2: number[]
difference: number
let sortedArr: number[] = _.chain(numbers).without(0).sortBy((x) => x).value().reverse()
let arr1: number[] = []
let arr2: number[] = []
let median = _.sum(sortedArr) / 2
let sum = 0
_.each(sortedArr, (n) =>
let ns = sum + n
if (ns > median)
arr1.push(n)
else
sum += n
arr2.push(n)
)
return
arr1: arr1,
arr2: arr2,
difference: Math.abs(_.sum(arr1) - _.sum(arr2))
【讨论】:
【参考方案2】:试图解决同样的问题我遇到了以下想法,这似乎是一个太多的解决方案,但它在线性时间内起作用。谁能提供一个例子来证明它不起作用或解释为什么它不是一个解决方案?
arr = [20,10,15,6,1,17,3,9,10,2,19] # a list of numbers
g1 = []
g2 = []
for el in reversed(sorted(arr)):
if sum(g1) > sum(g2):
g2.append(el)
else:
g1.append(el)
print(f"sum(g1): g1")
print(f"sum(g2): g2")
【讨论】:
这种贪心算法并不总是有效。以10 9 8 7 4
为例。【参考方案3】:
问题是NPC,但是有一个伪多项式算法,这是一个2-Partition问题,你可以按照sub set sum问题的伪多项式时间算法的方式来解决这个问题。如果输入大小与输入值呈多项式相关,则可以在多项式时间内完成。
在您的情况下(权重 总和
设Sum =权重之和,我们要创建二维数组A,然后逐列构造A
A[i,j] = true if (j == weight[i] or j - weight[i] = weight[k] (k is in list))。
使用此算法创建数组需要 O(n^2 * sum/2)。
最后我们应该找到最有价值的具有真正价值的列。
这是一个例子:
项目:0,1,2,3 权重:4,7,2,8 => sum = 21 sum/2 = 10
items/weights 0| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10
---------------------------------------------------------
|0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0
|1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0
|2 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 1
|3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1
所以因为 a[10, 2] == true 分区是 10, 11
这是我找到here 并稍作编辑以解决您的问题的算法:
bool partition( vector< int > C )
// compute the total sum
int n = C.size();
int N = 0;
for( int i = 0; i < n; i++ ) N += C[i];
// initialize the table
T[0] = true;
for( int i = 1; i <= N; i++ ) T[i] = false;
// process the numbers one by one
for( int i = 0; i < n; i++ )
for( int j = N - C[i]; j >= 0; j--)
if( T[j] ) T[j + C[i]] = true;
for(int i = N/2;i>=0;i--)
if (T[i])
return i;
return 0;
我只是返回了第一个 T[i],这是真的,而不是返回 T[N/2](从最大到最小的顺序)。
找到给出这个值的路径并不难。
【讨论】:
【参考方案4】:您可以将其重新表述为knapsack problem。
您有一份总重量为 M 的物品清单,这些物品应装入可容纳最大重量为 M/2 的垃圾箱中。装入垃圾箱的物品应尽可能称重,但不要超过垃圾箱的重量。
对于所有权重都是非负数的情况,这个问题只有weakly NP-complete,并且有多项式时间解。
关于这个问题的动态规划解决方案的描述可以在Wikipedia找到。
【讨论】:
背包是“NPC-Strong”问题,但这个问题只是 NPC(它不强),如果您将其重新表述为 KNapsack,您将产生更难的问题,这就像将 P 问题重新表述为 NPC 问题(不完全是这样)。 @Saeed:对于所有权重都是非负的特殊情况,背包问题不是NPC强的。 是的,所以编辑你的第一句话,这个问题完全是已知问题(2-Partition),除非所有事情都解释清楚,否则任何重新表述都可能导致歧义(像这样)。例如,马克·拜尔斯(Mark Byers)提供的蛮力在这种情况下是错误的,但可以在多项式时间内完成。(恕我直言,您没有犯错,但可能会导致其他人的错误)。 @Saeed:你说得有道理。另一方面,分区问题(您可能知道)只是众所周知的背包问题的一个特例。因此,采取哪种攻击路径在很大程度上取决于口味。就我个人而言,我认为尝试从最普遍的众所周知的问题中推导出可能更容易。 IMO 不,分区不是 KNapsak 的特例(我不知道你在哪里读到或你怎么想),可能是“2-Partition”是“具有非负权重的 KNapsak ”,但一般分区非常不同,例如 3-Partition 是显示其 NPC-Strong 的(第一个)问题,由此表明 4-Partition 是 NPC Strong 并且从显示 3-Dimensional Matching is Strong(现在我们有证明的层次),如果你想说 X 是 YI 的特例,可以反过来说,但正如我所说,如果没有很好的澄清,就会导致歧义。【参考方案5】:这个问题至少和 NP 完全问题subset sum 一样难。你的算法是一个贪心算法。这种算法速度快,可以快速生成近似解,但无法找到NP完全问题的精确解。
蛮力方法可能是解决您的问题的最简单方法,尽管如果元素太多,它会变慢。
尝试所有可能的方法,将元素分成两组并计算总和的绝对差。 选择绝对差异最小的分区。可以通过考虑从 0 到 2^n 的每个整数的二进制表示来生成所有分区,其中每个二进制数字确定对应的元素是在左分区还是右分区。
【讨论】:
蛮力不是大多数算法问题的答案,因为时间很重要。 2)子集和解决方案听起来不错。我去搜一下 请注意:在所有权重都是非负的情况下,这个问题只是弱NP完全的,并且有多项式时间解。蛮力不是一个好主意。 @hilal:你说得对,这不是最优算法。我以为您正在尝试找到正确的算法。 kotlinski 建议的背包方法看起来不错。 :)以上是关于将列表分成两部分,它们的总和彼此最接近的主要内容,如果未能解决你的问题,请参考以下文章