【问题标题】:Dynamic programming : perfect sum with negative numbers动态规划:负数的完美和
【发布时间】:2019-02-23 06:06:51
【问题描述】:

给定一个整数数组和一个总和,任务是打印给定数组的所有子集,其中总和等于给定总和。

Example: 
Input : arr[] = {1, 2, 3, 4, 5}
        sum = 10
Output : [4 3 2 1]  
         [5 3 2] 
         [5 4 1]

Input : arr[] = {-1, 2, 3, 4, 5}
        sum = 10
Output : [5 3 2] 
         [5 4 2 -1]

我已经在伪多项式时间内使用动态规划来做到这一点。这是子集和问题的扩展,它只负责决定是否存在这样的子集。我下面的解决方案适用于子集和问题的正数和负数。但是,如果数组包含负数,则无法正确打印子集。程序是-

import java.util.ArrayList;

// sum problem
class GFG {

    static boolean subset[][];

    // Returns true if there is a subset of
    // set[] with sun equal to given sum
    static boolean isSubsetSum(int set[],
                               int n, int sum) {
        // The value of subset[i][j] will be
        // true if there is a subset of
        // set[0..j-1] with sum equal to i
        subset = new boolean[n + 1][sum + 1];

        // Fill the subset table in botton
        // up manner
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= sum; j++) {
                if (j == 0) {
                    subset[i][j] = true;
                } else if (i <= 0 && sum >= 1)
                    subset[i][j] = false;
                else if (set[i - 1] > j)
                    subset[i][j] = subset[i - 1][j];
                else {
                    if (set[i - 1] >= 0)
                        subset[i][j] = subset[i - 1][j] || subset[i - 1][j - set[i - 1]];
                    else
                        subset[i][j] = subset[i - 1][j] || subset[i - 1][j + set[i - 1]];
                }
            }
        }

        // uncomment this code to print table
//        for (int i = 0; i <= sum; i++)
//        {
//        for (int j = 0; j <= n; j++)
//            System.out.println (subset[i][j]);
//        }

        return subset[n][sum];
    }

    /* Driver program to test above function */
    public static void main(String args[]) {
        int set[] = {1, 2, 3, 4, 5};
        int sum = 10;
        int n = set.length;
        if (isSubsetSum(set, n, sum) == true)
            System.out.println("Found a subset"
                    + " with given sum");
        else
            System.out.println("No subset with"
                    + " given sum");
        System.out.println("Done");
        ArrayList<Integer> list = new ArrayList<>();
        printSubsets(set, n, sum, list);
        System.out.println("Finished");
    }

    static void display(ArrayList<Integer> v) {
        System.out.println(v);
    }

    private static void printSubsets(int[] set, int i, int sum, ArrayList<Integer> list) {
        if (i == 0 && sum != 0 && subset[0][sum]) {
            list.add(set[i]);
            display(list);
            list.clear();
            return;
        }

        // If sum becomes 0
        if (i == 0 && sum == 0) {
            display(list);
            list.clear();
            return;
        }

        // If given sum can be achieved after ignoring
        // current element.
        if (subset[i - 1][sum]) {
            // Create a new vector to store path
            ArrayList<Integer> b = new ArrayList<>();
            b.addAll(list);
            printSubsets(set, i - 1, sum, b);
        }

        // If given sum can be achieved after considering
        // current element.

        if (sum >= set[i - 1] && subset[i - 1][sum - set[i - 1]]) {
            list.add(set[i - 1]);
            printSubsets(set, i - 1, sum - set[i - 1], list);
        }

    }   
} 

如何修改此代码使其也适用于负数?

【问题讨论】:

标签: java data-structures dynamic-programming subset-sum


【解决方案1】:

您的解决方案假定所有值都是正数,因此动态编程数组 subset 填充了 j 的正值,但您现在需要考虑负和。

你需要做的是改变j的循环限制来填充动态编程数组

for (int j = negative_sum; j <= positive_sum; j++)

其中negative_sum 是所有负值的总和,positive_sum 是所有正值的总和。

有关详细信息,请阅读子集求和问题here 的维基百科页面,其中解释了此步骤。

【讨论】:

    【解决方案2】:

    由于您必须打印(或生成)给定集合的所有可能子集(包含正整数和负整数),其总和等于 sum,你可以做的是:

    尝试将集合的每个位置表示为 0 和 1 的二进制表示,其中 1 表示该位置的元素被考虑,0 表示该位置的元素不被考虑。

    找出所有有 1 的位置的总和。如果这些值的总和正好等于给定的总和,则打印该子集。

    因此,总体时间复杂度为O(2 ^ n),其中n 是给定集合的长度。

    你可以看看下面的实现。

    import java.util.Arrays;
    
    public class PerfectSum {
    
    public static void printSubsets(int[] set, int n, int sum) {
         int totalSubSets = (1 << n);
         for (int i = 1; i < totalSubSets; ++i) { // loop over all possible subsets
             int curSum = 0;
             for (int j = n - 1; j >= 0; --j) {
                 if (((i >> j) & 1) > 0) { // if bit at jth position is 1 take that value
                    curSum +=set[j];
                 }
             }
             if (curSum == sum) { // valid subset found, then print it
                 for (int j = n - 1; j >= 0; --j) { // looping in reverse order to print set in decreasing order
                     if (((i >> j) & 1) > 0) { // if bit at jth position is 1 take that value
                         System.out.print(set[j] + " ");
                     }
                 }
                 System.out.println("");
             }
         }
    }
    
    public static void main(String[] args) {
        int set[] = {-1, 2, 3, 4, 5};
        Arrays.sort(set); // To print in non increasing order
        int sum = 10;
        int n = set.length;
        printSubsets(set, n, sum);
      }
    }
    

    【讨论】:

    • 感谢您的解决方案。但我认为这是一种蛮力,我们遍历所有这些以获得正确的子集。有什么方法可以使用动态编程(即伪多项式时间)来做到这一点?
    • 既然我们必须打印出所有可能的子集,我们不能在小于 O(2 ^ n) 的时间内完成。谢谢!!
    【解决方案3】:

    您可以将数组的最小负数减去整个集合,使数组中的数字为正数。然后应用动态规划。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-21
      • 1970-01-01
      • 2017-06-23
      • 2016-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多