【问题标题】:Finding Smallest Number of Elements to make a Sum找到最少数量的元素来求和
【发布时间】:2014-04-12 21:49:08
【问题描述】:

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

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

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

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

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

请提出一个我可以研究的算法,以便我可以有效地解决这个问题。我已经尝试过蛮力,但它对于我的需要来说太慢了。

【问题讨论】:

  • 正好 12 个?或至少 12 个?
  • 我很惊讶你的蛮力如此缓慢。也许你太粗鲁了。你对你的输入数组进行排序吗?如果是这样,当总和太大时,很容易停止进一步查看。
  • 正好 12。但我想要一个适用于任何大小的通用算法,而且非常有效。
  • 一般你不能有效地解决这个问题,因为它是 NP 完全的。这是子集和问题。
  • 这个问题最好在计算机科学网站cs.stackexchange.com

标签: algorithm dynamic-programming greedy


【解决方案1】:

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

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);

    }
}

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 时间复杂度是多少?对于精确的解决方案,我们不能做得比指数更好。
    • 在最坏的情况下显然是指数级的。例如,列表不包含任何1,只有不同的值,您的目标是S-1(这是不可行的),其中S 是列表的总和。
    【解决方案3】:

    免责声明:这是一个广告,宣传漂亮但相对简单的数学,它导致非常聪明和快速的计数公式和算法。我知道您可以使用常规编程找到更简单有效的解决方案。我只是喜欢这样一个事实,即正确使用计算机代数系统,您可以一次性完成:让我们用这个列表获得 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: 仔细思考后,我还意识到:生成系列只是一个紧凑编码,如果您尝试实现一个动态规划解决方案。

    【讨论】:

    • 请提供时间复杂度。当您说“我知道您可以使用常规编程找到更简单有效的解决方案”时,请描述您的意思。据我了解,这个问题显然是 NP-Complete。
    • 如果您将系数运算视为常数时间,则此解决方案的复杂性接近2^(length of the list)。对于问题本身,它确实是NP完全的(作为某种背包问题)。用我的话来说,我的意思是通过计算s,我正在一次性解决目标的所有可能值的问题,这显然是次优的。
    • 谢谢。不过,您的帖子暗示有一个多项式时间解决方案,直到有人解决 P = NP 问题,我们才能知道。
    • 请看我的编辑。我没有暗示存在多项式时间解决方案。
    【解决方案4】:

    我不认为这个解决方案是最佳的,但它很容易理解和使用,你可以按降序对元素进行排序,然后将每个元素都放在你的数字中。如果您有序列 [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 或您需要的内容中使用。我希望这不是太暴力。

    【讨论】:

    • 其实我犯了一个错误,当条件是aux &gt; 0 然后在里面你检查if (aux - ordered_items[i] &lt;= 0) {count = count + 1; aux = aux - ordered_items[i];} 然后你就增加i
    猜你喜欢
    • 1970-01-01
    • 2021-10-05
    • 2011-11-04
    • 1970-01-01
    • 2018-03-13
    • 2010-11-25
    • 1970-01-01
    • 1970-01-01
    • 2020-08-12
    相关资源
    最近更新 更多