查找最小数量的元素以进行求和

时间:2014-03-09 22:42:11

标签: algorithm dynamic-programming greedy

我有一个简单的算法问题:

如果我有某些整数值的元素,如:

1 1 1 1 1 1 1 1 1 1 1 1 10 12 2

我必须得到总和12,所需元素的最小数量为1,我只使用12。

因此,我的问题是你将如何: 找到要求总和的最小元素数,如果不能输出-1。

请建议我可以研究的算法,以便我可以有效地解决这个问题。我已经尝试过蛮力,但是为了满足我的需求会慢下来。

4 个答案:

答案 0 :(得分:2)

问题是np-complete,可以简化为子集和背包问题。有伪多项式时间算法可以使用动态编程来解决它。以下是类似于背包类比的解决方案: -

1. Knapsack capacity = Sum
2. Items have same weight and value
3. Maximize profit 
4. if max_profit == Sum then there is a solution
5. else Sum cannot be made from the items given.
6. Evaluate the minimum items needed using matrix alongside the DP.
7. Can also reconstruct all solutions and get the minimum one.

时间复杂度: - O(Sum*Items)

Java实施: -

public class SubSetSum {
    static int[][] costs;
    static int[][] minItems;

    public static void calSets(int target,int[] arr) {

        costs = new int[arr.length][target+1];
        minItems = new int[arr.length][target+1];
        for(int j=0;j<=target;j++) {
            if(arr[0]<=j) {

                costs[0][j] = arr[0]; 
                minItems[0][j] = 1;
            }
        }
        for(int i=1;i<arr.length;i++) {

            for(int j=0;j<=target;j++) {
                costs[i][j] = costs[i-1][j];
                minItems[i][j] = minItems[i-1][j];
                if(arr[i]<=j) {
                    costs[i][j] = Math.max(costs[i][j],costs[i-1][j-arr[i]]+arr[i]);
                    if(costs[i-1][j]==costs[i-1][j-arr[i]]+arr[i]) {

                        minItems[i][j] = Math.min(minItems[i][j],minItems[i-1][j-arr[i]]+1);
                    }
                    else if(costs[i-1][j]<costs[i-1][j-arr[i]]+arr[i]) {
                        minItems[i][j] = minItems[i-1][j-arr[i]]+1;
                    }
                }
            }

        }

       // System.out.println(costs[arr.length-1][target]);
       if(costs[arr.length-1][target]==target) {

           System.out.println("Minimum items need : "+minItems[arr.length-1][target]);

       } 

       else System.out.println("No such Set found");

    } 



    public static void main(String[] args) {
        int[] arr = {1,1,1,1, 1 ,1 ,1, 1 ,1, 1 ,1 ,1, 10 ,12, 2};
        calSets(12, arr);

    }
}

答案 1 :(得分:0)

这是一个应该相当快的递归方法:

1)如果输入向量的长度为1,则如果值等于目标,则返回1;如果不是,则返回-1。同样,如果您的目标小于输入向量中的任何项目,则返回-1。 2)否则,循环输入向量中的(唯一)值(按降序排列,以获得性能):    2a)删除矢量的值,并从目标中减去它。    2b)递归地在新向量和新目标上调用此函数    注意:你可以向算法传递一个max.step参数,这样如果你已经找到了一个长度为K的解决方案,你就可以在那个深度停止递归调用,但不能超过。记得在每次递归调用中减少max.step值。 3)从递归调用中收集所有值,取最小值(不是-1)并向其中加1并返回,或者,如果循环中的所有值都为-1,则返回-1。

答案 2 :(得分:0)

免责声明:这是一个很好但相对简单的数学广告,这导致了非常聪明和快速的计数公式和算法。我知道你可以使用通常的编程找到一个更简单有效的解决方案。我只是喜欢这样一个事实:使用正确的计算机代数系统,你可以在一个班轮中完成:让这个列表获得19:

sage: l = [1,1,1,2,5,2,1,3,12,1,3]; goal = 19
sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,goal).low_degree(t)
3

25:

sage: goal=25
sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,goal).low_degree(t)
5

36不可行:

sage: goal=36
sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,goal).low_degree(t)
0

以下是一些细节:只需展开产品

(1+t*x^l[0]) (1+t*x^l[1]) ... (1+t*x^l[n])

您的列表在l。然后,要找到获得总和S所需的最小元素数,请收集x^S的系数,并在t中返回最小学位。

以下是中的完成方式:

sage: var("x t")
(x, t)
sage: l = [1,1,1,2,5,2,1,3,12,1,3]
sage: s = prod((1+t*x^i) for i in l)
sage: s = expand(s).collect(x)

现在

sage: print(s)
t^11*x^32 + 5*t^10*x^31 + 2*(t^10 + 5*t^9)*x^30 + 2*(t^10 + 5*t^9 + 5*t^8)*x^29 + (11*t^9 + 20*t^8 + 5*t^7)*x^28 + (t^10 + 4*t^9 + 25*t^8 + 20*t^7 + t^6)*x^27 + 2*(3*t^9 + 10*t^8 + 15*t^7 + 5*t^6)*x^26 + (2*t^9 + 17*t^8 + 40*t^7 + 20*t^6 + 2*t^5)*x^25 + (2*t^9 + 12*t^8 + 30*t^7 + 40*t^6 + 7*t^5)*x^24 + (11*t^8 + 30*t^7 + 35*t^6 + 20*t^5 + t^4)*x^23 + 2*(2*t^8 + 13*t^7 + 20*t^6 + 13*t^5 + 2*t^4)*x^22 + (t^8 + 20*t^7 + 35*t^6 + 30*t^5 + 11*t^4)*x^21 + (t^10 + 7*t^7 + 40*t^6 + 30*t^5 + 12*t^4 + 2*t^3)*x^20 + (5*t^9 + 2*t^7 + 20*t^6 + 40*t^5 + 17*t^4 + 2*t^3)*x^19 + 2*(t^9 + 5*t^8 + 5*t^6 + 15*t^5 + 10*t^4 + 3*t^3)*x^18 + (2*t^9 + 10*t^8 + 10*t^7 + t^6 + 20*t^5 + 25*t^4 + 4*t^3 + t^2)*x^17 + (11*t^8 + 20*t^7 + 5*t^6 + 5*t^5 + 20*t^4 + 11*t^3)*x^16 + (t^9 + 4*t^8 + 25*t^7 + 20*t^6 + t^5 + 10*t^4 + 10*t^3 + 2*t^2)*x^15 + 2*(3*t^8 + 10*t^7 + 15*t^6 + 5*t^5 + 5*t^3 + t^2)*x^14 + (2*t^8 + 17*t^7 + 40*t^6 + 20*t^5 + 2*t^4 + 5*t^2)*x^13 + (2*t^8 + 12*t^7 + 30*t^6 + 40*t^5 + 7*t^4 + t)*x^12 + (11*t^7 + 30*t^6 + 35*t^5 + 20*t^4 + t^3)*x^11 + 2*(2*t^7 + 13*t^6 + 20*t^5 + 13*t^4 + 2*t^3)*x^10 + (t^7 + 20*t^6 + 35*t^5 + 30*t^4 + 11*t^3)*x^9 + (7*t^6 + 40*t^5 + 30*t^4 + 12*t^3 + 2*t^2)*x^8 + (2*t^6 + 20*t^5 + 40*t^4 + 17*t^3 + 2*t^2)*x^7 + 2*(5*t^5 + 15*t^4 + 10*t^3 + 3*t^2)*x^6 + (t^5 + 20*t^4 + 25*t^3 + 4*t^2 + t)*x^5 + (5*t^4 + 20*t^3 + 11*t^2)*x^4 + 2*(5*t^3 + 5*t^2 + t)*x^3 + 2*(5*t^2 + t)*x^2 + 5*t*x + 1

好的,这是一个巨大的表达。这里的一个很好的功能是,如果我得到系数x^17,我得到:

sage: s.coefficient(x, 17)
2*t^9 + 10*t^8 + 10*t^7 + t^6 + 20*t^5 + 25*t^4 + 4*t^3 + t^2

其中说明如下:术语10*t^7告诉我有10种不同的方法来使用7号来获得总和17。另一个例子,有25种方法可以使用4个数字(25*t^4)得到17。

此外,由于此表达式以t^2结尾,因此我了解到我只需要两个数字即可获得17。不幸的是,这并没有说明哪些数字。

如果您想了解这一技巧,请查看Wikipedia article on generating functionsThis Page

注1 :这不是最有效的,因为我的计算量远远超过您的需求。实际描述的巨大表达并以某种方式计算了所有可能的选择(即列表长度的2 ^)。但这只是一个班轮:

sage: prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,17).low_degree(t)
2

仍然相对有效:

sage: %timeit prod((1+t*x^i) for i in l).expand().collect(x).coefficient(x,17).low_degree(t)
10 loops, best of 3: 42.6 ms per loop

注2:仔细考虑后,我还意识到以下几点:生成系列只是一个紧凑编码,如果你试图实现一个动态编程解决方案。

答案 3 :(得分:0)

我不认为这个解决方案是最优的,但是它很容易理解和使用,你按顺序排序元素,然后你拿出每个元素并尝试将它放在你的数字中。如果您有序列[5,6,2,7]并且您需要输入15号码,您将重新排序序列[7,6,5,2]并取7,那么您需要提取8以便您将需要6,然后你需要2个,检查5但是它太大你会跳过它并检查最后一个数字,2,它是完美的并完成你的号码。所以你打印出来3.这是算法的最坏情况,即O(n)。但是在12的例子中,它将是O(1),因为你将从有序序列的第一次检查中选择12。 (运行时间仅适用于选择项目的程序,不适用于排序)

resolve_sum(ordered_items[], number) {
    count = 0;
    aux = number;
    i = 0;
    while (aux - ordered_items[i] <= 0) {
         count = count + 1;
         aux = aux - ordered_items[i];
         i = i + 1;
    }
    if (aux == 0) return count;
    else return -1;
}

我没有包含排序算法,你可以选择一个你最了解的算法或尝试学习一个新的高效算法。 Link with sorting algorithms and their running time。这只是您可以在C / C ++或Java中使用的示例代码或您需要的代码。我希望这不是太暴力。