【问题标题】:Find all subsets of a set that sum up to n查找集合中总和为 n 的所有子集
【发布时间】:2014-01-02 16:10:22
【问题描述】:

这是我想出的代码:

static void findNumbers(int[] list, int index, int current, int goal, String result)
{ 
  if (list.length < index || current>goal)
          return;
   for (int i = index; i < list.length; i++) {
      if (current + list[i] == goal)   {
         System.out.println(result + " " + String.valueOf(list[i]));
       }
       else if (current + list[i] < goal) {
           findNumbers(list, i + 1, current + list[i], goal, result + " " + String.valueOf(list[i]));
        }
   }
}

调用它使用:

findNumbers(array, starting_index, current_sum_till_now, target_sum, "");

谁能帮我算出这段代码的时间复杂度,我相信它是指数级的。

解决此问题的最佳方法是什么?是否使用回溯?

【问题讨论】:

  • 也许O(2n*log(n)) ?
  • 订单没有常数,它是关于增长的。
  • 经典的 NP 完全决策问题之一Subset Sum 可简化为该问题,因此该问题是 NP 难的,您不太可能找到具有多项式时间复杂度的正确解决方案。
  • @Sarkae 我已经为您的问题添加了更好的解决方案。

标签: java algorithm time-complexity subset partitioning


【解决方案1】:

有人指出我做错了。我正在增加递归调用的复杂性,而我应该添加它们。所以C(N) = C(N-1) + C(N-2) + ...。这同样适用于C(N-1)C(N-2) 等。这意味着复杂性不是'O(N!)

这让我从另一个角度思考算法。它正在检查每一个可能的子集。既然有2^N - 1可能的子集(空子集不考虑),那么复杂度就是O(2^N),我认为这是你最初的赌注。

【讨论】:

  • 最坏的情况是一个包含 N 个零且目标为零的列表。在这种情况下,每个子集也是一个解。
  • @RaffaeleRossi 我认为您的复杂性分析虽然合理错误,但有点偏离,首先 C(N)!= N*C(N-1),它的 C(N) = C(N-1) +C(N-2)+C(N-3)...C(0) 然后 C(N-1) = C(N-2) + C(N-3)...C(0)因此 C(N) = 2*C(N-1) 因此 C(N) = O(2^N) 而不是 O(N!)
  • @VikramBhat 你是对的。我不应该增加复杂性。谢谢。
【解决方案2】:

您可以修改您的代码以遵循“如果一个数字是好的 - 添加它;忽略任何条件跳过当前数字”的原则。在这种情况下,代码将是:

static void findNumbers(int[] list, int index, int current, int goal, String result)
{ 
  if (list.length <= index || current>goal) // I've added the "=" which is missing in your code.
          return;
  if (current + list[index] == goal)   {
      System.out.println(result + " " + String.valueOf(list[i]));
  }
  else if (current + list[index] < goal) {
      findNumbers(list, index + 1, current + list[i], goal, result + " " + String.valueOf(list[i]));
  }
  findNumbers(list, index + 1, current, goal, result);
}

在这种情况下,复杂度将是 O(2^n),这对于 n=&gt;5O(n!) 更好。 正如所指出的,如果对数组进行排序,则复杂性会降低。这意味着您可以将第二个递归调用放在else if 中,因为您将确定后面的所有数字都大于当前的list[index],这意味着跳过这个值是没有用的,因为这个调用的所有后续分支都不会生成任何有效的子集。

在这种情况下,最坏的情况是O(2^l),其中l 是大于您的目标并且在您的数组中的数字的索引,或者n 如果这样的数字不存在。

电话应该是:findNumbers(list,0,0,goal,"")

【讨论】:

  • 我认为你错过了 for 循环,因为变量 i 没有声明
  • @Sarkar 你本可以查看第二个递归调用,它是index 而不是i,没有更正。
【解决方案3】:

正如刚刚指出的那样,它比 N^2 差,实际上它看起来像 O(N!)。您可以节省一点,因为您可以提前退出某些循环,但节省的程度取决于消除可能性的速度。

就您将遇到的更优化的解决方案而言,这是递归的绝佳案例,因为任何基于循环的构造都将是可怕的。您可以通过预先对数据进行排序来节省一些时间,以便首先使用较大的值,从而更快地达到目标(这基本上会立即消除列表中大于目标的任何内容)。消除过大的条目后,我不确定它是否会直接有所帮助,因为您仍然需要将所有内容与所有内容进行比较,但它可能会改善处理器分支预测。

【讨论】:

  • 这不是O(N^2),因为他递归调用同一个函数两次以上(提供的列表包含超过 2 个条目)
【解决方案4】:

这是一种使用动态编程和背包类比的方法:-

  1. 对集合进行升序排序

  2. 评估子集直到list[i] &lt;= N

  3. 求解背包容量为 N 的袋子以及价值和重量为list[i]的物品@

  4. 如果最终背包容量 N == 最大利润,则至少存在一个解决方案子集。

  5. 使用成本矩阵回溯背包的所有解决方案并获得所有解决方案子集。

时间复杂度: O(|S|*N + K) |S|- length of set and K is number of subsets. 这是一个伪多项式时间算法。

注意:问题是 NP-hard 尚未发现多项式时间算法。

编辑:-从布尔矩阵回溯解决方案

void retrace(int n,boolean[] solution,int target) {

   if(n>=0) {

        if(table[target][n-1]) {

            solution[n] = false;
            retrace(n-1,solution,target); 
        }

       if(table[target-numbers[n]][n-1]) {

            solution[n] = true;
            retrace(n-1,solution,target-numbers[n]);
       }
   }

   else {
       printf("\nsubset:-\n");
       for(int i=0;i<solution.length;i++) {

            if(solution[i]) {
                printf(number[i]+" ");
            }
       }

   }


}



  Call : - retrace(numbers.length-1,new boolean[numbers.length],target);

【讨论】:

  • 这个回溯是我卡住的地方。我已经形成了一个布尔矩阵,它告诉您是否可以找到总和为 n 的集合的子集。 ideone.com/OlOG16 但是我似乎无法回溯原始子集本身。任何实施将不胜感激。
  • 这个 sn-p 中的 n 和 solution[] 到底是什么?
  • Solution 只是存储子集解决方案的布尔数组,如果 solution[n] = true 表示第 n 个元素包含在子集中。 n 是可以在解决方案中的数字元素,最初是项目总数
  • 表格矩阵的形式为 boolean [][] table = new boolean [target + 1] [numbers.length + 1] ;因此 if(table[n-1][target]) 行给出了异常。数字数组也一直运行到 n 所以这一步 for(int i=1;i
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多