这是一个我的朋友得到的面试问题,我无法想出如何解决它。
问题:
您将获得一组n个红色或蓝色按钮。有k个容器。容器的值由红色按钮和蓝色按钮的产品给出。问题是将按钮放入容器中,使得容器的所有值的总和最小。此外,所有容器必须包含按钮,并且必须按顺序放置它们。 例如,第一个按钮只能转到第一个容器,第二个按钮可以转到第一个或第二个而不是第三个(否则第二个容器将没有任何按钮)。 k将小于或等于n。
我认为必须有一个动态编程解决方案。
你是如何解决这个问题的? 到目前为止,我只有轻微的案例
修改:
我举一个例子。
n = 4且k = 2
输入:R B R R
第一个容器获得前两个(R和B)使其值为1(1R X 1B) 第二个容器获取剩余的(R和R)使其值为0(2R x 0B) 答案是1 + 0 = 1
如果k = 3, 第一个容器只有第一个按钮(R) 第二个容器只有第二个容器(B) 第三个将有最后两个按钮(R和R) 每个容器的值为0,因此sum和answer为0。
希望这可以解决疑虑。答案 0 :(得分:4)
可能的DP解决方案:
让dp[i, j] = minimum number possible if we put the first i numbers into j containers
。
dp[i, j] = min{dp[p, j - 1] + numRed[p+1, i]*numBlues[p+1, i]}, p = 1 to i - 1
答案将在dp[n, k]
。
int blue = 0, red = 0;
for (int i = 1; i <= n; ++i)
{
if (buttons[i] == 1)
++red;
else
++blue;
dp[i][1] = red * blue;
}
for (int i = 2; i <= n; ++i)
for (int j = 2; j <= k; ++j)
{
dp[i][j] = inf;
for (int p = 1; p <= i; ++p)
dp[i][j] = min(dp[p][j - 1] + getProd(p + 1, i), dp[i][j]);
}
return dp[n][k];
复杂性将为O(n^3*k)
,但可以通过O(n^2*k)
在某些预计算的帮助下getProd
运行来减少到O(1)
(提示:使用dp[i][1]
)。如果在此之前没有人发现这实际上是错误的,我明天会发布它。
也可能减少到O(n*k)
,但这可能需要采用不同的方法......
答案 1 :(得分:3)
如果我正确理解了这个问题,只要每个容器中至少有一个按钮,你可以选择任何容器来放入其余按钮。鉴于此,在每个容器中放一个按钮,确保有至少一个带红色按钮的容器,至少一个带蓝色按钮的容器。然后使用其余按钮,将所有红色按钮放在带有红色按钮的容器中,并将所有蓝色按钮放入带有蓝色按钮的容器中。这将使每个容器至少有一个按钮,每个容器只有一种颜色的按钮。然后每个容器的得分为0.因此,总和为0,并且您已将组合得分最小化。
答案 2 :(得分:3)
警告:证明不是最佳
贪婪算法让人们说话怎么样?我不打算在这一点上证明它是最优的,但它是解决问题的一种方式。
在此解决方案中,我们使用G来表示按钮序列中一种颜色的连续区域的数量。假设我们有(我使用x表示红色,o表示蓝色,因为R和B看起来太相似):
x x x o x o o o x x o
这将给出G = 6.让我们将其分成组(红色/蓝色),其中,首先,每个组获得一致颜色的整个区域:
3/0 0/1 1/0 0/3 2/0 0/1 //total value: 0
当G <= k时,您至少为零,因为每个分组都可以进入自己的容器。现在假设G> ķ。我们的贪婪算法虽然有多个组而不是容器,但是将两个相邻的组折叠成一个导致容器值 delta (valueOf(merged(a, b)) - valueOf(a) - valueOf(b)
)的组。用我们上面的例子说k = 5。我们的选择是:
Collapse 1,2: delta = (3 - 0 - 0) = 3
2,3: delta = 1
3,4: delta = 3
4,5: delta = 6
5,6: delta = 2
所以我们崩溃2和3:
3/0 1/1 0/3 2/0 0/1 //total value: 1
并且k = 4:
Collapse 1,2: delta = (4 - 0 - 1) = 3
2,3: delta = (4 - 1 - 0) = 3
3,4: delta = (6 - 0 - 0) = 6
4,5: delta = 2
3/0 1/1 0/3 2/1 //total value: 3
k = 3
4/1 0/3 2/1 //total value: 6
k = 2
4/1 2/4 //total value: 12
k = 1
6/5 //total value: 30
这个案例似乎是最佳,但我只是想让人们谈论解决方案。请注意,按钮到容器的起始分配是一个快捷方式:您可以从自己的存储桶中的序列中的每个按钮开始然后减少,但是您总是会到达每个容器的最大按钮数为1的位置。颜色。
反例:感谢JulesOlléon提供的反例让我懒得想:
o o o x x o x o o x x x
如果k = 2,则最佳映射为
2/4 4/2 //total value: 16
让我们看看贪婪算法如何接近它:
0/3 2/0 0/1 1/0 0/2 3/0 //total value: 0
0/3 2/0 1/1 0/2 3/0 //total value: 1
0/3 3/1 0/2 3/0 //total value: 3
0/3 3/1 3/2 //total value: 9
3/4 3/2 //total value: 18
我会留下这个答案,因为它完成了让人们谈论解决方案的唯一目的。我想知道贪婪的启发式算法是否可以用于一个知情的搜索算法,如A *,以改善穷举搜索的运行时间,但这不会实现多项式运行时。
答案 3 :(得分:1)
我总是要求在面试中澄清问题陈述。想象一下,你永远不会把蓝色的红色按钮放在一起。然后总和为0,就像n == k一样。因此,对于k> 1的所有情况。 1,然后最小值为0.
答案 4 :(得分:0)
这是我到目前为止所理解的:算法是处理一系列值{R,B}。 如果存在下一个容器,它可以选择将值放在当前容器中或下一个容器中。
我首先要问几个问题来澄清我还不知道的事情:
提前知道算法是否知道k和n?我这么认为。
我们事先知道完整的按钮序列吗?
如果我们事先不知道序列,那么平均值是否应该最小化?或最大(最坏的情况)?
Mark Peters证明algortihm的想法
编辑:证明的想法(抱歉,无法在评论中使用)
设L(i)为第i组的长度。设d(i)是折叠容器i和i + 1 =&gt;得到的差异。 d(i)= L(i)* L(i + 1)。
我们可以通过折叠的容器序列来定义分布。作为索引,我们使用包含具有较小索引的容器的折叠容器中包含的原始容器的最大索引。
给定的折叠序列I = [i(1),... i(m)]得到一个值,其下限等于d(i(m))之和,从1到1的所有m NK。
我们需要证明,除了具有较小diff的算法创建的序列之外,不能存在序列。所以让上面的序列是算法产生的序列。设J = [j(1),... j(m)]。
这里变得吝啬: 我认为应该可以证明J的下限大于I的实际值,因为在每个步骤中我们通过构造从I中选择崩溃操作,因此它必须小于替代序列中的匹配崩溃
我想我们可能会认为序列是分离的,但我并不完全确定它。
答案 5 :(得分:0)
这是一个用Python编写的强力算法,似乎有效。
from itertools import combinations
def get_best_order(n, k):
slices = combinations(range(1, len(n)), k-1)
container_slices = ([0] + list(s) + [len(n)] for s in slices)
min_value = -1
best = None
def get_value(slices, n):
value = 0
for i in range(1, len(slices)):
start, end = slices[i-1], slices[i]
num_red = len([b for b in n[start:end] if b == 'r'])
value += num_red * (end - start - num_red)
return value
for slices in container_slices:
value = get_value(slices, n)
if value < min_value or min_value == -1:
min_value = value
best = slices
return [n[best[i-1]:best[i]] for i in range(1, len(best))]
n = ['b', 'r', 'b', 'r', 'r', 'r', 'b', 'b', 'r']
k = 4
print(get_best_order(n, k))
# [['b', 'r', 'b'], ['r', 'r', 'r'], ['b', 'b'], ['r']]
基本上算法的工作原理如下: