不同面额的硬币一个接一个地放置,挑选硬币以最大化总和

时间:2016-12-28 07:07:34

标签: algorithm puzzle

不同面额的硬币一个接一个地放置。您需要逐个挑选硬币(除了第一个和最后一个),直到只剩下2个硬币(第一个和最后一个)。每次你选择一枚硬币时,你会将它的左右硬币值相乘。问题是以这样的顺序挑选硬币,使得所有乘法的总和最大。例如:

让硬币放置为1,6,7,4

有两种方法可以选择硬币:

第一种方式:首先选择6,这将导致1 * 7 = 7然后选择7,这将导致1 * 4 = 4,因此总数将为7 + 4 = 11

第二种方式:首先选择7,这将导致6 * 4 = 24然后选择6,这将导致1 * 4 = 4,因此总数将为24 + 4 = 28

28是最大的,这是我们的答案。

我可以通过递归遍历所有可能的情况并比较它们的输出值来找到正确的答案,但这种解决方案效率非常低,因为它需要指数时间。请让我们知道如何更有效地解决这个问题。

编辑: 递归解决方案

int remove (int a [], int end, int pos) {
    int tmp = a[pos];
    for (int i = pos + 1; i <= end; i++) {
        a[i - 1] = a[i];
    } a[end] = 0;
    return tmp;
}

int * insert (int a [], int end, int pos, int val) {
    for (int i = end; i >= pos; i--) {
        a[i + 1] = a[i];
    } a[pos] =  val;
    return a;
}

/*  a: input array, {1, 7, 6, 4}
    end: array last index, 3 for above case
*/
int getMaxSum (int a [], int end, int sum = 0) {
    if (end == 1) {
        return sum;
    }

    int maxSum = 0;

    for (int i = 1; i < end; i++) {
        auto mult = a[i - 1]*a[i + 1];
        auto val = remove(a, end, i);
        auto tmp = getMaxSum (a, end - 1, sum + mult);
        if (tmp > maxSum)
            maxSum = tmp;
        insert(a, end - 1, i, val);
    }

    return maxSum;
}

5 个答案:

答案 0 :(得分:4)

可以使用Matrix Chain Multiplication修改Dynamic programming问题解决此问题。

假设给定的数字是A,B,C,D

A B C D
1 6 7 4

现在将这些数字转换为:

  • 尺寸AxB
  • 的矩阵M1
  • 尺寸为BxC
  • 的矩阵M2
  • 尺寸为CxD的矩阵M3

    M1 M2 M3
    AB BC CD
    16 67 74
    

通常,如果乘以AB和BC的2个兼容矩阵,则乘法成本为AB x BC = ABCABC is product of 3 elements A, B & C

在此修改后的算法中,成本将为AxC(因为拣选元素[i]将导致成本[i-1]x[i+1])。

AB x BC = ACAC is product of 2 elements A & C

现在,尝试以所有可能的方式对矩阵M1,M2和M3进行括号,使成本最大化。

可能的括号:

[1] (M1 (M2 M3))
[2] ((M1 M2) M3) 


[1] 
{AB x (BCxCD)} => {AB x BD}
{(AB) x (6 x 4 = 24)} => {1 x 4 = 4}  ,  so 24 + 4 = 28
Elements picking order {C} -> {B}

[2]
{(AB x BC) x CD} => {AC x CD}
{(1 x 7 = 7) x CD} => {1 x 4 = 4} , so 7 + 4 = 11
Elements picking order {B} -> {C}

因此,使用[1],费用为最高,即28,应按以下顺序挑选元素:C -> B

答案 1 :(得分:2)

我设法用(我认为)DP来解决它。 但是,其空间要求是2^n的因子,其中n是要移除的硬币数量。

基本思想是我们在n维空间中的点之间进行遍历。

说:coins = [ _, x, x, x, _ ]

有3&#34;中&#34;硬币。如果我们通过1 s(和0 s)表示硬币的存在(和不存在),我们将从(1, 1, 1)遍历到(0, 0, 0)

在两者之间,我们可能通过多条路径达到许多过渡状态。

例如:(1, 0, 0)可能会以(1, 1, 1) - &gt;到达(1, 1, 0) - &gt; (1, 0, 0)(1, 1, 1) - &gt; (1, 0, 1) - &gt; (1, 0, 0)

因此,如果相邻数字的乘法是得分,我们可以将状态value的{​​{1}}设置为导致它的路径中的较高者。由于每次转型需要&#34;踩下&#34;通过将一个(1, 0, 0)转换为1,我们可以在从0迭代到(1, 1, 1)时保存状态的值。最终答案将存储在(0, 0, 0)

以下是一个这样的实现。我不知道如何去表示n维状态。所以,我只使用了一个从(0, 0, 0)递减的整数,然后用它的二进制值来编码状态。所以,如果2^n - 1,则意味着我们已经删除了第三个&#34;中间&#34;硬币。

要进行转换,我们会从cur_state = 6 (110)中减去2^(index of 1 to be removed)

(警告:使用一些丑陋的指数计算来查找要移除的硬币的相邻硬币。)

cur_state

答案 2 :(得分:1)

这是一个动态编程c ++解决方案,我认为它与sameerkn给出的Matrix Chain Multiplication概念非常接近。我们在表sums[i][j]中存储从索引i开始并以索引j结尾的maxSum。我们迭代指数之间的硬币数量,从三个硬币(j-i = 2)的序列开始。迭代步骤是选择sums[i][j] sum[i][k] + sum[k][j] + coins[i]*coins[j]中最大的一个j-i<2。如果m[i][j]=0int maxSum(const std::vector<int>& coins){ int n = coins.size(); std::vector<std::vector<int>> sums; for (int i = 0; i < n-1; ++i){ sums.push_back(std::vector<int>(n)); } for (int l = 2; l < n; ++l){ for (int i = 0; i < n - l; ++i){ int j = i + l; sums[i][j] = -1; for (int k = i+1; k < j; k++){ int val = sums[i][k] + sums[k][j] + coins[i]*coins[j]; if (val > sums[i][j]){ sums[i][j] = val; } } } } return sums[0][n-1]; }

O(N^2)

可以很容易地看出,时间复杂度为O(N ^ 3),空间为@Styles.Render("~/Content/css") @Scripts.Render("~/bundles/modernizr") @Scripts.Render("~/bundles/jquery")

答案 3 :(得分:0)

我建议您为函数getMaxSum使用缓存。实际上,它并没有避免算法的指数复杂性,但它节省了很多时间进行可重复的计算。这是我在Python中的实现(因为它看起来很相似):

cache = {}

def getMaxSum(a):
    if tuple(a) in cache:
        return cache[tuple(a)]

    sum_arr = []
    for i in range(1, len(a) - 1):
        s = a[i-1] * a[i+1] + getMaxSum(a[:i] + a[i+1:])
        sum_arr.append(s)

    cache[tuple(a)] = max(sum_arr) if sum_arr else 0

    return cache[tuple(a)]

print(getMaxSum([1, 6, 7, 4])) # 28
print(getMaxSum([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])) # 3420

对于长度最多为20的列表,它的工作速度非常快。

对于非指数解决方案,我认为,这是一个困难的数学问题需要认真研究才能回答。

答案 4 :(得分:0)

很容易证明索引ij之间的最佳排除顺序问题可以减少到f(i,j)到两个子问题f(i,k)的最佳分割和f(k,j)。由于当中间元素被消除(或a[i]a[k]相邻)时,最终a[k]必须乘以某些a[i],我们可以修复i,k,j和将函数递归地应用于每个子问题。基本案例在下面的JavaScript代码中列出。这个解决方案与Ari Hietanen的解决方案基本相同。

function f(a,i,j){
  if (Math.abs(i - j) === 2)
    return a[i] * a[j];

  if (Math.abs(i - j) === 1) 
    return 0;

  var best = -Infinity;

  for (var k=i+1; k<j;k++){
    best = Math.max(best, a[i] * a[j] + f(a,i,k) + f(a,k,j));
  }

  return best;
}

var a = [1,6,7,4];

console.log(f(a,0,3)); // 28