您有一个包含 n 元素的数组。元素为0或1。 您希望将数组拆分为k 连续的子数组。每个子阵列的大小可以在ceil(n / 2k)和floor(3n / 2k)之间变化。你可以假设k <&lt; ñ。 将数组拆分为k个子数组后。将随机选择每个子阵列的一个元素。
设计一种算法,用于最大化k个子阵列中随机选择的元素的总和。 基本上意味着我们希望以这样的方式分割数组,使得从每个子阵列中选择的元素的所有期望值的总和最大。
您可以假设n是2的幂。
Example:
Array: [0,0,1,1,0,0,1,1,0,1,1,0]
n = 12
k = 3
Size of subarrays can be: 2,3,4,5,6
Possible subarrays [0,0,1] [1,0,0,1] [1,0,1,1,0]
Expected Value of the sum of the elements randomly selected from the subarrays: 1/3 + 2/4 + 3/5 = 43/30 ~ 1.4333333
Optimal split: [0,0,1,1,0,0][1,1][0,1,1,0]
Expected value of optimal split: 1/3 + 1 + 1/2 = 11/6 ~ 1.83333333
答案 0 :(得分:6)
我认为我们可以使用动态编程来解决这个问题。
基本上,我们有:
f(i,j)定义为从大小 i 的数组中选择的所有预期值的最大总和,并分为 j 子阵列。因此,解决方案应该是 f(n,k)。
递归方程是:
f(i,j) = f(i-x,j-1) + sum(i-x+1,i)/x where (n/2k) <= x <= (3n/2k)
答案 1 :(得分:3)
我不知道这是否仍然是一个悬而未决的问题,但似乎OP已经设法添加了足够的说明,这应该是直截了当的解决。无论如何,如果我理解你所说的话,在面试环境中询问软件开发职位似乎是公平的事情。
这是基本的O(n ^ 2 * k)解决方案,它应该适用于小k(如面试官所指定):
def best_val(arr, K):
n = len(arr)
psum = [ 0.0 ]
for x in arr:
psum.append(psum[-1] + x)
tab = [ -100000 for i in range(n) ]
tab.append(0)
for k in range(K):
for s in range(n - (k+1) * ceil(n/(2*K))):
terms = range(s + ceil(n/(2*K)), min(s + floor((3*n)/(2*K)) + 1, n+1))
tab[s] = max( [ (psum[t] - psum[s]) / (t - s) + tab[t] for t in terms ])
return tab[0]
我使用了numpy ceil / floor功能,但你基本上可以理解。这个版本中唯一的“技巧”就是它会将窗口开销减少到O(n)而不是O(n * k),并且预先计算部分和以计算框a的预期值。恒定时间操作(从而从内循环中保存O(n)因子)。
答案 2 :(得分:1)
我不知道是否有人仍然有兴趣看到这个问题的解决方案。半小时前偶然发现了这个问题并考虑发布我的解决方案(Java)。其复杂性为O(n * K ^ log10)。证明有点复杂,所以我宁愿提供运行时编号:
n k time(ms)
48 4 25
48 8 265
24 4 20
24 8 33
96 4 51
192 4 143
192 8 343919
解决方案是同一个旧的递归方法,给定一个数组,选择第一个大小为ceil的分区(n / 2k)并以其他方式递归找到最佳解决方案,其中分区数= k -1,然后取ceil( n / 2k)+ 1等等。
代码:
public class PartitionOptimization {
public static void main(String[] args) {
PartitionOptimization p = new PartitionOptimization();
int[] input = { 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0};
int splitNum = 3;
int lowerLim = (int) Math.ceil(input.length / (2.0 * splitNum));
int upperLim = (int) Math.floor((3.0 * input.length) / (2.0 * splitNum));
System.out.println(input.length + " " + lowerLim + " " + upperLim + " " +
splitNum);
Date currDate = new Date();
System.out.println(currDate);
System.out.println(p.getMaxPartExpt(input, lowerLim, upperLim,
splitNum, 0));
System.out.println(new Date().getTime() - currDate.getTime());
}
public double getMaxPartExpt(int[] input, int lowerLim, int upperLim,
int splitNum, int startIndex) {
if (splitNum <= 1 && startIndex<=(input.length -lowerLim+1)){
double expt = findExpectation(input, startIndex, input.length-1);
return expt;
}
if (!((input.length - startIndex) / lowerLim >= splitNum))
return -1;
double maxExpt = 0;
double curMax = 0;
int bestI=0;
for (int i = startIndex + lowerLim - 1; i < Math.min(startIndex
+ upperLim, input.length); i++) {
double curExpect = findExpectation(input, startIndex, i);
double splitExpect = getMaxPartExpt(input, lowerLim, upperLim,
splitNum - 1, i + 1);
if (splitExpect>=0 && (curExpect + splitExpect > maxExpt)){
bestI = i;
curMax = curExpect;
maxExpt = curExpect + splitExpect;
}
}
return maxExpt;
}
public double findExpectation(int[] input, int startIndex, int endIndex) {
double expectation = 0;
for (int i = startIndex; i <= endIndex; i++) {
expectation = expectation + input[i];
}
expectation = (expectation / (endIndex - startIndex + 1));
return expectation;
}
}
答案 3 :(得分:0)
不确定我理解,算法是将数组分组,对吧?总和可以具有的最大值是1的数量。因此,将数组拆分为每个1个元素的“n”组,并且加法将是可能的最大值。但它必须是别的东西,我不明白这个问题,这看起来太傻了。
答案 4 :(得分:0)
我认为这可以通过动态编程来解决。在每个可能的拆分位置,如果您在该位置拆分,并且如果您不在该点拆分,则获取最大金额。递归函数和存储历史的表可能很有用。
sum_i = max{ NumOnesNewPart/NumZerosNewPart * sum(NewPart) + sum(A_i+1, A_end),
sum(A_0,A_i+1) + sum(A_i+1, A_end)
}
这可能导致一些事情......
答案 5 :(得分:0)
我认为这是一个糟糕的面试问题,但这也是一个容易解决的问题。
每个整数对权重1 / s的预期值有贡献,其中s是放置它的集合的大小。因此,如果你猜测分区中集合的大小,你只需要用从最小集合开始的集合填充集合,然后用零填充剩余的最大集合。
你可以很容易地看到,如果你有一个分区,如上所示填充,其中集合的大小是S_1,...,S_k,你进行转换,你从集合S_i中删除一个项目并将其移动到设置S_i + 1,您有以下情况:
在所有这些情况下,您可以将元素从S_i移位到S_i + 1,保持填充最小集合的填充规则为1,以便预期值增加。这导致了简单的算法:
答案 6 :(得分:0)
递归函数怎么样:
int BestValue(Array A, int numSplits)
// Returns the best value that would be obtained by splitting
// into numSplits partitions.
这又使用了一个帮手:
// The additional argument is an array of the valid split sizes which
// is the same for each call.
int BestValueHelper(Array A, int numSplits, Array splitSizes)
{
int result = 0;
for splitSize in splitSizes
int splitResult = ExpectedValue(A, 0, splitSize) +
BestValueHelper(A+splitSize, numSplits-1, splitSizes);
if splitResult > result
result = splitResult;
}
ExpectedValue(Array A,int l,int m)计算从1到m的A的分裂的期望值,即(A [1] + A [l + 1] + ... A [m] )/(m-l + 1)。
BestValue在计算ceil(n / 2k)和floor(3n / 2k)之间的有效分割大小数组后调用BestValueHelper。
我省略了错误处理和一些结束条件,但这些不应该太难添加。
答案 7 :(得分:0)
让
期望(a,from,to)=数组a的所有元素从“from”到“to”的平均值
Optimal(a[], from, k) = MAX[ for(j>=minSize-1 to <=maxSize-1) { expectation(a, from, from+j) + Optimal(a, j+1, k-1)} ]
运行时(假设记忆或dp)= O(n * k * d)