【问题标题】:Split a subset with a constraint使用约束拆分子集
【发布时间】:2018-01-11 05:19:03
【问题描述】:

今天,在练习一些算法问题时,我发现了一个有趣的问题。 问题是

您必须将 1 到 n (缺少一个值 x )分成两个相等的 一半,使得两半之和相等。

例子:

如果n = 7x = 4

解决方案是{7, 5}{1, 2, 3, 6}

我可以用蛮力方法回答它,但我想要一个有效的解决方案 谁能帮帮我?

【问题讨论】:

  • 您应该检查您是否能够达到 (Sum(1 to n) - x) /2。带有不包括 x 的数字。它与子集总和相同。 n = 7 和 x = 4 的答案也是 [7, 5], [1, 2, 3, 6]
  • 谢谢,我纠正了那个错字。
  • 我没有尝试证明这一点,但可能是贪心算法总是有效的(如果有解决方案的话)。你应该花一些时间研究这个方向。

标签: algorithm subset-sum


【解决方案1】:

如果没有 x 的元素 1→N 之和是奇数,则没有解。

否则,您可以通过平衡选择在 O(N) 中找到解决方案。

连续 4 个

首先让我们考虑任何四个连续数字的序列都可以分成两个总和相等的集合:

[x, x+1, x+2, x+3] → [x+3, x];[x+2, x+1]

因此选择它们并将它们放入集合 A B B A 平衡集合 A 和 B。

4 跨

此外,当我们有两对跨越一个省略值时,它可以拥有相似的属性:

[x-2, x-1, x+1, x+2] → [x+2, x-2]; [x+1, x-1]

还是A B B A

此时我们可以修复以下情况:

  • 我们有一个四胞胎:我们将其拆分为案例 1
  • 我们有 2 个数字,x 和其他 2 个数字:我们按照案例 2 进行拆分

好的,但是我们有 3 个数字、x 和其他 3 个数字或其他条件可能会发生。无论如何,我们如何才能以平衡的方式进行选择?

+2 差距

如果我们再看一下 x 之间的差距:

[x-1, x+1]

我们可以注意到,如果我们以某种方式将两个邻居分成两个单独的集合,我们必须平衡集合上的 +2 和更大的总和。

平衡尾

我们可以通过使用序列的最后四个数字来做到这一点:

[4 3 2 1] → [4, 2] ; [3, 1] → 6 ; 4

最后我们必须考虑到我们可能没有其中一个,所以让我们构建另一个案例:

[3 2 1] → [2] ; [3, 1] → 2 ; 4

让我们也意识到我们可以在序列的另一端使用 A B A B(或 B A B A)模式做同样的事情 - 如果我们的 +2 位于 B(或 A)上;

4 跨 +

如果我们跳 h(奇数!)个数字,4 跨仍然成立,这真是太神奇了:

[x+3, x+2, x-2, x-3] → [x+3, x-3]; [x+2, x-2]

所以,探索数组,我们可以一步一步得出解

一个例子:

11 10 9 8 7 6 5 4 3 2 1

总和是偶数,所以 x 只能是偶数:

x = 10
11 - 9 | 8 7 6 5 | 4 3 2 1 → (+2 gap - on A) (4 in a row) (balancing tail)
 A   B   A B B A   B A B A

x = 8
11 10 | 9 - 7 | 6 5 | 4 3 2 1 → (4 across +) (+2 gap - on A) (balancing tail)
 a  b   A   B | b a | B A B A

x = 6
11 10 9 8 | 7 - 5 | 4 3 2 1 → (4 in a row) (+2 gap - on A) (balancing tail)
 A  B B A   A   B   A B B B

x = 4 we have no balancing tail - we have to do that with head
11 10 9 8 | 7 6 | 5 - 3 | 2 1 → (balancing head) (4 across +) (+2 gap)
 A  B A B   A B | b   a | B A

x = 2
11 10 9 8 | 7 6 5 4 | 3 - 1 → (balancing head) (4 in a row) (+2 gap)
 A  B A B   A B B A   B   A

注意解的对称性很有趣。另一个例子。

10 9 8 7 6 5 4 3 2 1

总和是奇数,所以x只能是奇数,现在元素个数是奇数。

x = 9
10 - 8 | 7 6 5 4 | 3 2 1 → (+2 gap - on A) (4 in a row) (balancing tail)
 A   B   A B B A   B A B

x = 7
10 9 | 8 - 6 | 5 4 | 3 2 1 → (4 across +) (+2 gap - on A) (balancing tail)
 a b | A   B | b a   B A B

x = 5
10 9 8 7 | 6 - 4 | 3 2 1 → (4 in a row) (+2 gap - on A) (balancing tail)
 A B B A   A   B   B A B

x = 3
10 9 8 7 | 6 5 | 4 - 2 | 1 → (balancing head) (4 across + virtual 0) (+2 gap)
 A B A B   B A | a   b | A

x = 1
10 9 8 7 | 6 5 4 3 | 2 → (balancing head) (4 in a row) (+2 gap virtual 0)
 A B A B   A B B A   B 

最后值得注意的是,只要我们有一个完全平衡的段(连续 4 个或跨 4 个),我们就可以从 A 切换到 B

有趣的是 - 但是如果你自己尝试,要求 sum([1 ... N]-x) 的属性甚至会使案例变得非常多余。


我很确定这个算法可以推广——我可能很快会提供一个修订版。

【讨论】:

  • 你能推荐任何参考来更好地研究这个解决方案
【解决方案2】:

这个问题可以通过用预处理步骤包装动态编程的标准subset sum problem来解决。这些步骤是 O(1) com

算法(n,x):

  1. 总和 = n * (n+1) / 2
  2. neededSum = sum - x
  3. 如果 (neededSum % 2 != 0):返回 0

  4. 创建数组 [1..n] 并从中删除 x

  5. 调用标准的subsetsum(arr, 0, requiredSum/2, [])

subsetsum 算法的工作 python 实现 - 打印所有子集如下。

def subsetsum(arr, i, sum, ss):
    if i >= len(arr):
        if sum == 0:
            print ss
            return 1
        else:
            return 0

    ss1 = ss[:]
    count = subsetsum(arr, i + 1, sum, ss1)
    ss1.append(arr[i])
    count += subsetsum(arr, i + 1, sum - arr[i], ss1)
    return count


arr = [1, 2, 3, 10, 5, 7]
sum = 14
a = []
print subsetsum(arr, 0, sum, a)

希望对你有帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 2018-11-04
    • 2016-12-14
    相关资源
    最近更新 更多