0

我正在尝试建立一个问题,以解决另一个类似的问题...下面给出的是用于查找总和为特定值的子集总数的代码,我正在尝试修改代码以便我可以返回所有总和为该值的子集(而不是找到计数)。

查找总和为“sum”的 suibset 总数的代码:

 /**
 * method to return number of sets with a given sum.
 **/
public static int count = 0;
public static void countSubsetSum2(int arr[], int k, int sum) {
    if(sum == 0) {
        count++;
        return;
    }
    if(sum != 0 && k == 0) {
        return;
    }
    if(sum < arr[k - 1]) {
        countSubsetSum2(arr, k-1, sum);
    }
    countSubsetSum2(arr, k-1, sum - arr[k-1]);
    countSubsetSum2(arr, k-1, sum);
}

有人可以对此代码提出一些更改,使其返回子集而不是子集计数吗?

4

5 回答 5

1

这是有效的代码:

import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;

public class subset{
    public static int count = 0;
    public static List list = new LinkedList();
    public static void countSubsetSum2(int arr[], int k, int sum) {
        if(sum <= 0 || k < 0) {
            count++;
            return;
        }
        if(sum == arr[k]) {
            System.out.print(arr[k]);
            for(Iterator i = list.iterator(); i.hasNext();)
                System.out.print("\t" + i.next());
            System.out.println();
        }
        list.add(arr[k]);
        countSubsetSum2(arr, k-1, sum - arr[k]);
        list.remove(list.size() - 1);
        countSubsetSum2(arr, k-1, sum);
    }

    public static void main(String[] args)
    {
        int [] array = {1, 4, 5, 6};
        countSubsetSum2(array, 3, 10);
    }
}
于 2013-09-05T01:27:26.330 回答
1

首先,您那里的代码似乎并没有实际工作(我在输入[1,2,3, ..., 10]上测试了它的总和3和它的输出128)。

为了让它工作,首先请注意您以非常非正统的方式实现了该算法。数学函数接受输入并产生输出。(可以说)最优雅的编程函数也应该接受输入并产生输出,因为这样我们就可以像推理数学一样推理它们。

在您的情况下,您不会产生任何输出(返回类型为void),而是将结果存储在静态变量中。这意味着很难准确说出call的含义countSubsetSum2。特别是,如果你多次调用它会发生什么?它每次都会做不同的事情(因为count变量将具有不同的起始值!)相反,如果您编写countSubsetSum2它以使其返回一个值,那么您可以将其行为定义为:返回总和为countSubsetSum2的输入的子集数。然后您可以尝试证明您的实现为何符合该规范。arr[0...k]sum

我没有做最好的解释工作,但我认为更自然的写法是:

// Algorithm stops once k is the least element in the array
if (k == 0) {
    if (sum == 0 || sum == arr[k]) {
        // Either we can sum to "sum"
        return 1;
    }
    else {
        // Or we can't sum to "sum"
        return 0;
    }   
}   

// Otherwise, let's recursively see if we can sum to "sum"

// Any valid subset either includes arr[k]
return countSubsetSum2(arr, k-1, sum - arr[k]) +
// Or it doesn't
countSubsetSum2(arr, k-1, sum);

如上所述,这个函数接受一个输入并输出一个我们可以定义并在数学上证明为真的值(警告:它通常不是一个证明,因为不幸的是在大多数编程语言中都有疯狂的边缘情况)。

无论如何,回到你的问题。上面代码的问题是它不存储任何数据......它只是返回计数。相反,让我们在生成它们的同时生成实际的子集。特别是,当我说Any valid subset either includes arr[k]我的意思是......我们正在生成的子集包括arr[k]; 所以添加它。下面我假设你上面写的代码是java-ish。希望这是有道理的:

// Algorithm stops once k is the least element in the array
if (k == 0) {
    if (sum == 0 || sum == arr[k]) {
        // Either we can sum to "sum" using just arr[0]
        // So return a list of all of the subsets that sum to "sum"
        // There are actually a few edge cases here, so we need to be careful
        List<Set<int>> ret = new List<Set<int>>();

        // First consider if the singleton containing arr[k] could equal sum 
        if (sum == arr[k])
        {   
            Set<int> subSet = new Subset<int>();
            subSet.Add(arr[k]);
            ret.Add(subSet);
        }   

        // Now consider the empty set 
        if (sum == 0)
        {   
            Set<int> subSet = new Subset<int>();
            ret.Add(subSet);
        }   

        return ret;
    }   
    else {
        // Or we can't sum to "sum" using just arr[0]
        // So return a list of all of the subsets that sum to "sum". None 
        // (given our inputs!)
        List<Set<int>> ret = new List<Set<int>>();
        return ret;
    }
}

// Otherwise, let's recursively generate subsets summing to "sum"

// Any valid subset either includes arr[k]
List<Set<int>> subsetsThatNeedKthElement = genSubsetSum(arr, k-1, sum - arr[k]);
// Or it doesn't
List<Set<int>> completeSubsets = genSubsetSum(arr, k-1, sum);

// Note that subsetsThatNeedKthElement only sum to "sum" - arr[k]... so we need to add
// arr[k] to each of those subsets to create subsets which sum to "sum"
// On the other hand, completeSubsets contains subsets which already sum to "sum"
// so they're "complete"

// Initialize it with the completed subsets
List<Set<int>> ret = new List<Set<int>>(completeSubsets);
// Now augment the incomplete subsets and add them to the final list
foreach (Set<int> subset in subsetsThatNeedKthElement)
{
    subset.Add(arr[k]);
    ret.Add(subset);
}

return ret;

代码中的所有注释都非常混乱;但关键是这个实现总是返回它指定返回的内容(从 arr[0] 到 arr[k] 的整数集列表,总和为传入的任何总和)。

仅供参考,还有另一种方法是“自下而上”(即不使用递归),它应该更高效。如果你以这种方式实现它,那么你需要以静态状态存储额外的数据(“记忆表”)......这有点难看但很实用。但是,当您以这种方式实现它时,您需要有一种更聪明的方式来生成子集。尝试后,请随时在单独的帖子中提出该问题。

于 2013-09-05T04:53:40.220 回答
1

首先,您的代码不正确。

由于以下几行,该函数在每一步都使用不包括和包括当前元素1的总和进行递归,并继续移动到下一个元素:

countSubsetSum2(arr, k-1, sum - arr[k-1]);
countSubsetSum2(arr, k-1, sum);

但是还有这个:

if(sum < arr[k - 1]) {
    countSubsetSum2(arr, k-1, sum);
}

这导致它在某些情况下递归两次,总和不包括当前元素(它不应该这样做)。

本质上,您只需要删除该 if 语句。

如果所有元素都是正数 和sum - arr[k-1] < 0,我们会继续前进,但我们永远不会得到 0 的总和,因为总和不会增加,因此我们会做很多不必要的工作。因此,如果元素都是正数,我们可以if(arr[k - 1] <= sum)在第一次调用中添加检查以提高运行时间。如果元素不都是正数,则代码不会找到所有总和。

现在开始打印总和

如果您很好地理解了代码,那么将其更改为打印总和应该很容易。我建议您进一步了解它 - 手动跟踪程序将执行的操作,然后跟踪您希望程序执行的操作。

以及解决实际问题的提示:注意到countSubsetSum2(arr, k-1, sum - arr[k-1]);包含当前元素的总和递归(以及其他递归调用不包括当前元素的总和递归),你应该做什么应该变得清楚。


1:嗯,从技术上讲,它是相反的(我们从目标总和开始减少到 0,而不是从 0 开始并增加到总和),但同样的想法是存在的。

于 2013-09-05T08:43:21.423 回答
0

基于此处的评论/建议,我能够以这种方式获得此问题的解决方案:

public static int counter = 0;
public static List<List<Integer>> lists = new ArrayList<>();
public static void getSubsetCountThatSumToTargetValue(int[] arr, int k, int targetSum, List<Integer> list) {
    if(targetSum == 0) {
        counter++;
        lists.add(list);
        return;
    }

    if(k <= 0) {
        return;
    }

    getSubsetCountThatSumToTargetValue(arr, k - 1, targetSum, list);

    List<Integer> appendedlist = new ArrayList<>();
    appendedlist.addAll(list);
    appendedlist.add(arr[k - 1]);
    getSubsetCountThatSumToTargetValue(arr, k - 1, targetSum - arr[k - 1], appendedlist);
}

主要方法如下所示:

public static void main(String[] args) {

    int[] arr = {1, 2, 3, 4, 5};
    SubSetSum.getSubsetCountThatSumToTargetValue(arr, 5, 9, new ArrayList<Integer>());
    System.out.println("Result count: " + counter);
    System.out.println("lists: " + lists);

}

输出:

Result: 3
lists: [[4, 3, 2], [5, 3, 1], [5, 4]]
于 2013-09-07T06:39:13.350 回答
0

一个 Python 实现,其中 k 从 0 移动到 len() - 1:

import functools
def sum_of_subsets( numbers, sum_original ):

  def _sum_of_subsets( list, k, sum ):
    if sum < 0 or k == len( numbers ):
      return

    if ( sum == numbers[ k ] ):
      expression = functools.reduce( lambda result, num: str( num ) if len( result ) == 0 else \
                                                          "%s + %d" % ( result, num ),
                           sorted( list + [ numbers[ k ]] ),
                           '' )
      print "%d = %s" % ( sum_original, expression )
      return

    list.append( numbers[ k ] )
    _sum_of_subsets( list, k + 1, sum - numbers[ k ])

    list.pop( -1 )
    _sum_of_subsets( list, k + 1, sum )

  _sum_of_subsets( [], 0, sum_original )

...

sum_of_subsets( [ 8, 6, 3, 4, 2, 5, 7, 1, 9, 11, 10, 13, 12, 14, 15 ], 15 )

...

15 = 1 + 6 + 8
15 = 3 + 4 + 8
15 = 1 + 2 + 4 + 8
15 = 2 + 5 + 8
15 = 7 + 8
15 = 2 + 3 + 4 + 6
15 = 1 + 3 + 5 + 6
15 = 4 + 5 + 6
15 = 2 + 6 + 7
15 = 6 + 9
15 = 1 + 2 + 3 + 4 + 5
15 = 1 + 3 + 4 + 7
15 = 1 + 2 + 3 + 9
15 = 2 + 3 + 10
15 = 3 + 5 + 7
15 = 1 + 3 + 11
15 = 3 + 12
15 = 2 + 4 + 9
15 = 1 + 4 + 10
15 = 4 + 11
15 = 1 + 2 + 5 + 7
15 = 1 + 2 + 12
15 = 2 + 13
15 = 1 + 5 + 9
15 = 5 + 10
15 = 1 + 14
15 = 15
于 2014-07-06T23:11:12.913 回答