获取目标和的数组子集的算法没有给出工作

时间:2015-06-24 22:43:50

标签: java arrays algorithm subset subset-sum

问题是一个未排序的数组,给出可以产生目标总和的数组的子集:

例如:

target =  15
data = {3,4,5,7,1,2,9};

预期结果(请注意,结果是为了简单而排序的。不是必需的):

[1, 2, 3, 4, 5]
[1, 2, 3, 9]
[1, 2, 5, 7]
[1, 3, 4, 7]
[1, 5, 9]
[2, 4, 9]
[3, 5, 7]

这是我对这个问题的天真态度 - 简单而蛮力。

public static void naiveSubset(int[] arr, int target){
        int sum=0;
        List<Integer> result = new ArrayList<>();
        for (int i=0; i< arr.length;i++){
                sum =arr[i];
                result.add(arr[i]);
         for (int j=0;j<arr.length;i++){
             if (sum==target){
                 System.out.println(result);
                 result.clear();
                 break;
             }
             else if (i!=j && sum+arr[j] <= target){
                 sum+=arr[j];
                 result.add(arr[j]);
               }
           }
         }
        }

由于某些原因,我不期待结果。我尝试浏览代码以挖掘出任何问题。但我找不到任何东西。请算法专家,指出我正确的方向!!

我得到的结果(与上面相同的输入)

[3, 3, 3, 3, 3]
[9, 3, 3]

2 个答案:

答案 0 :(得分:1)

你的解决方案是错误的,因为这是一种贪婪的方法。它决定你是否应该添加一个数字,因为此时添加它不会违反总和。

但是,这种贪婪的方法不起作用,只有以下数组的简单示例:[1,9,6,5]sum=11

请注意,对于您在外部循环中选择的任何元素,接下来您将向当前集添加1。但这将使你无法获得5+6的总和。
选择5后,开始添加数字,从“1”开始,然后添加。一旦添加 - 您永远无法获得正确的解决方案。

另请注意:您的双循环方法最多可生成O(n^2)个不同的子集,但可能存在指数级的子集 - 因此必定存在错误。

如果要获得总和为给定总和的所有可能子集,可以使用递归解决方案。

在每一步“猜测”当前元素是否在集合中,并针对较小问题递归两个选项 - 如果数据在集合中,或者不在集合中。

这是一个简单的java代码:

public static void getAllSubsets(int[] elements, int sum) {
    getAllSubsets(elements, 0, sum, new Stack<Integer>());
}
private static void getAllSubsets(int[] elements, int i, int sum, Stack<Integer> currentSol) { 
    //stop clauses:
    if (sum == 0 && i == elements.length) System.out.println(currentSol);
    //if elements must be positive, you can trim search here if sum became negative
    if (i == elements.length) return;
    //"guess" the current element in the list:
    currentSol.add(elements[i]);
    getAllSubsets(elements, i+1, sum-elements[i], currentSol);
    //"guess" the current element is not in the list:
    currentSol.pop();
    getAllSubsets(elements, i+1, sum, currentSol);
}

请注意,如果您要查找所有子集,则可能存在指数级别 - 因此需要一个低效且指数化的时间解决方案。

如果您正在寻找是否存在这样的集合,或者只找到一个这样的集合,那么使用动态编程可以更有效地完成。 This thread解释了如何做到这一点的逻辑。
注意问题仍然是NP-Hard,“高效”解决方案实际上只是伪多项式。

答案 1 :(得分:0)

我认为您之前方法中的主要问题是,仅根据输入数组执行循环不会涵盖与目标值匹配的所有数字组合。例如,如果您的主循环位于ith,并且在遍历辅助循环中的jth元素之后,基于您通过第i个元素收集的内容的未来组合将永远不会包含{{1一个人了。直观地说,这个算法将通过彼此靠近的数字收集所有可见的组合,但彼此相距不远。

我写了一个迭代方法来处理这个通过C ++的子集求和问题(对不起,手头没有java环境:P),这个想法和recurrsive方法基本相同,这意味着你会记录所有现有的循环中每次迭代期间的数字组合。我有一个jth用于记录所有遇到的组合,其值小于目标,vector<vector> intermediate用于记录总和等于目标的所有组合。

详细说明以内联方式记录:

vector<vector> final

刚刚写好了,如果有任何我认为不好的角落情况,请耐心等待。希望这会有所帮助:)