【问题标题】:find all subsets that sum to x - using an initial code找到总和为 x 的所有子集 - 使用初始代码
【发布时间】:2013-09-04 22:03:15
【问题描述】:

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

查找总和为 '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);
}

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

【问题讨论】:

    标签: algorithm recursion subset subset-sum


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

    现在开始打印总和

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

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


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

    【讨论】:

    • 谢谢,杜克林。您的 cmets 总是很有帮助。
    • 我已经根据您的反馈回答了这个问题。如果您想提出任何改进建议(如果有),请告诉我。谢谢!
    【解决方案2】:

    这是有效的代码:

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

    【讨论】:

      【解决方案3】:

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

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

      在您的情况下,您不会产生任何输出(返回类型为void),而是将结果存储在静态变量中。这意味着很难准确说出调用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;
      

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

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

      【讨论】:

        【解决方案4】:

        基于这里的 cmets/suggestions,我已经能够以这种方式获得此问题的解决方案:

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

        main方法如下所示:

        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]]
        

        【讨论】:

          【解决方案5】:

          k 从 0 移动到 len() - 1 的 Python 实现:

          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
          

          【讨论】:

            猜你喜欢
            • 2013-08-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-05-07
            • 1970-01-01
            相关资源
            最近更新 更多