📜  子集总和问题| DP-25

📅  最后修改于: 2021-04-29 16:50:55             🧑  作者: Mango

给定一组非负整数和一个值sum ,确定给定集合的子集是否等于sum等于给定sum

例子:

Input: set[] = {3, 34, 4, 12, 5, 2}, sum = 9
Output: True  
There is a subset (4, 5) with sum 9.

Input: set[] = {3, 34, 4, 12, 5, 2}, sum = 30
Output: False
There is no subset that add up to 30.

方法1递归。
方法:对于递归方法,我们将考虑两种情况。

  1. 考虑最后一个元素,现在所需的总和=目标总和-“最后一个”元素的值和元素数=总元素数– 1
  2. 保留“最后一个”元素,现在所需的总和=目标总和,元素数=元素总数– 1

以下是isSubsetSum()问题的递归公式。

isSubsetSum(set, n, sum) 
= isSubsetSum(set, n-1, sum) || 
  isSubsetSum(set, n-1, sum-set[n-1])
Base Cases:
isSubsetSum(set, n, sum) = false, if sum > 0 and n == 0
isSubsetSum(set, n, sum) = true, if sum == 0 

让我们看一下上述方法的模拟:

set[]={3, 4, 5, 2}
sum=9
(x, y)= 'x' is the left number of elements,
'y' is the required sum
  
              (4, 9)
             {True}
           /        \  
        (3, 6)       (3, 9)
               
        /    \        /   \ 
     (2, 2)  (2, 6)   (2, 5)  (2, 9)
     {True}  
     /   \ 
  (1, -3) (1, 2)  
{False}  {True} 
         /    \
       (0, 0)  (0, 2)
       {True} {False}      

C++
// A recursive solution for subset sum problem
#include 
 
// Returns true if there is a subset
// of set[] with sum equal to given sum
bool isSubsetSum(int set[], int n, int sum)
{
    // Base Cases
    if (sum == 0)
        return true;
    if (n == 0)
        return false;
 
    // If last element is greater than sum,
    // then ignore it
    if (set[n - 1] > sum)
        return isSubsetSum(set, n - 1, sum);
 
    /* else, check if sum can be obtained by any
of the following:
      (a) including the last element
      (b) excluding the last element   */
    return isSubsetSum(set, n - 1, sum)
           || isSubsetSum(set, n - 1, sum - set[n - 1]);
}
 
// Driver code
int main()
{
    int set[] = { 3, 34, 4, 12, 5, 2 };
    int sum = 9;
    int n = sizeof(set) / sizeof(set[0]);
    if (isSubsetSum(set, n, sum) == true)
        printf("Found a subset with given sum");
    else
        printf("No subset with given sum");
    return 0;
}


Java
// A recursive solution for subset sum
// problem
class GFG {
 
    // Returns true if there is a subset
    // of set[] with sum equal to given sum
    static boolean isSubsetSum(int set[],
                               int n, int sum)
    {
        // Base Cases
        if (sum == 0)
            return true;
        if (n == 0)
            return false;
 
        // If last element is greater than
        // sum, then ignore it
        if (set[n - 1] > sum)
            return isSubsetSum(set, n - 1, sum);
 
        /* else, check if sum can be obtained
        by any of the following
            (a) including the last element
            (b) excluding the last element */
        return isSubsetSum(set, n - 1, sum)
            || isSubsetSum(set, n - 1, sum - set[n - 1]);
    }
 
    /* Driver code */
    public static void main(String args[])
    {
        int set[] = { 3, 34, 4, 12, 5, 2 };
        int sum = 9;
        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");
    }
}
 
/* This code is contributed by Rajat Mishra */


Python3
# A recursive solution for subset sum
# problem
 
# Returns true if there is a subset
# of set[] with sun equal to given sum
 
 
def isSubsetSum(set, n, sum):
 
    # Base Cases
    if (sum == 0):
        return True
    if (n == 0):
        return False
 
    # If last element is greater than
    # sum, then ignore it
    if (set[n - 1] > sum):
        return isSubsetSum(set, n - 1, sum)
 
    # else, check if sum can be obtained
    # by any of the following
    # (a) including the last element
    # (b) excluding the last element
    return isSubsetSum(
        set, n-1, sum) or isSubsetSum(
        set, n-1, sum-set[n-1])
 
 
# Driver code
set = [3, 34, 4, 12, 5, 2]
sum = 9
n = len(set)
if (isSubsetSum(set, n, sum) == True):
    print("Found a subset with given sum")
else:
    print("No subset with given sum")
 
# This code is contributed by Nikita Tiwari.


C#
// A recursive solution for subset sum problem
using System;
 
class GFG {
    // Returns true if there is a subset of set[] with sum
    // equal to given sum
    static bool isSubsetSum(int[] set, int n, int sum)
    {
        // Base Cases
        if (sum == 0)
            return true;
        if (n == 0)
            return false;
 
        // If last element is greater than sum,
        // then ignore it
        if (set[n - 1] > sum)
            return isSubsetSum(set, n - 1, sum);
 
        /* else, check if sum can be obtained
        by any of the following
        (a) including the last element
        (b) excluding the last element */
        return isSubsetSum(set, n - 1, sum)
          || isSubsetSum(set, n - 1, sum - set[n - 1]);
    }
 
    // Driver code
    public static void Main()
    {
        int[] set = { 3, 34, 4, 12, 5, 2 };
        int sum = 9;
        int n = set.Length;
        if (isSubsetSum(set, n, sum) == true)
            Console.WriteLine("Found a subset with given sum");
        else
            Console.WriteLine("No subset with given sum");
    }
}
 
// This code is contributed by Sam007


PHP
 $sum)
        return isSubsetSum($set, $n - 1, $sum);
     
    /* else, check if sum can be
       obtained by any of the following
        (a) including the last element
        (b) excluding the last element */
    return isSubsetSum($set, $n - 1, $sum) ||
        isSubsetSum($set, $n - 1,
                    $sum - $set[$n - 1]);
}
 
// Driver Code
$set = array(3, 34, 4, 12, 5, 2);
$sum = 9;
$n = 6;
 
if (isSubsetSum($set, $n, $sum) == true)
    echo"Found a subset with given sum";
else
    echo "No subset with given sum";
     
// This code is contributed by Anuj_67
?>


C++
// A Dynamic Programming solution
// for subset sum problem
#include 
 
// Returns true if there is a subset of set[]
// with sun equal to given sum
bool 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
    bool subset[n + 1][sum + 1];
 
    // If sum is 0, then answer is true
    for (int i = 0; i <= n; i++)
        subset[i][0] = true;
 
    // If sum is not 0 and set is empty,
    // then answer is false
    for (int i = 1; i <= sum; i++)
        subset[0][i] = false;
 
    // Fill the subset table in botton up manner
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            if (j < set[i - 1])
                subset[i][j] = subset[i - 1][j];
            if (j >= set[i - 1])
                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 <= n; i++)
     {
       for (int j = 0; j <= sum; j++)
          printf ("%4d", subset[i][j]);
       printf("\n");
     }*/
 
    return subset[n][sum];
}
 
// Driver code
int main()
{
    int set[] = { 3, 34, 4, 12, 5, 2 };
    int sum = 9;
    int n = sizeof(set) / sizeof(set[0]);
    if (isSubsetSum(set, n, sum) == true)
        printf("Found a subset with given sum");
    else
        printf("No subset with given sum");
    return 0;
}
// This code is contributed by Arjun Tyagi.


Java
// A Dynamic Programming solution for subset
// sum problem
class GFG {
 
    // 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
        boolean subset[][] = new boolean[sum + 1][n + 1];
 
        // If sum is 0, then answer is true
        for (int i = 0; i <= n; i++)
            subset[0][i] = true;
 
        // If sum is not 0 and set is empty,
        // then answer is false
        for (int i = 1; i <= sum; i++)
            subset[i][0] = false;
 
        // Fill the subset table in botton
        // up manner
        for (int i = 1; i <= sum; i++) {
            for (int j = 1; j <= n; j++) {
                subset[i][j] = subset[i][j - 1];
                if (i >= set[j - 1])
                    subset[i][j] = subset[i][j]
                                   || subset[i - set[j - 1]][j - 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[sum][n];
    }
 
    /* Driver code*/
    public static void main(String args[])
    {
        int set[] = { 3, 34, 4, 12, 5, 2 };
        int sum = 9;
        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");
    }
}
 
/* This code is contributed by Rajat Mishra */


Python3
# A Dynamic Programming solution for subset
# sum problem Returns true if there is a subset of
# set[] with sun equal to given sum
 
# Returns true if there is a subset of set[]
# with sun equal to given sum
def isSubsetSum(set, n, 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 =([[False for i in range(sum + 1)]
            for i in range(n + 1)])
     
    # If sum is 0, then answer is true
    for i in range(n + 1):
        subset[i][0] = True
         
    # If sum is not 0 and set is empty,
    # then answer is false
    for i in range(1, sum + 1):
         subset[0][i]= False
             
    # Fill the subset table in botton up manner
    for i in range(1, n + 1):
        for j in range(1, sum + 1):
            if j= set[i-1]:
                subset[i][j] = (subset[i-1][j] or
                                subset[i - 1][j-set[i-1]])
     
    # uncomment this code to print table
    # for i in range(n + 1):
    # for j in range(sum + 1):
    # print (subset[i][j], end =" ")
    # print()
    return subset[n][sum]
         
# Driver code
if __name__=='__main__':
    set = [3, 34, 4, 12, 5, 2]
    sum = 9
    n = len(set)
    if (isSubsetSum(set, n, sum) == True):
        print("Found a subset with given sum")
    else:
        print("No subset with given sum")
         
# This code is contributed by
# sahil shelangia.


C#
// A Dynamic Programming solution for
// subset sum problem
using System;
 
class GFG {
    // Returns true if there is a subset
    // of set[] with sun equal to given sum
    static bool 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
        bool[, ] subset = new bool[sum + 1, n + 1];
 
        // If sum is 0, then answer is true
        for (int i = 0; i <= n; i++)
            subset[0, i] = true;
 
        // If sum is not 0 and set is empty,
        // then answer is false
        for (int i = 1; i <= sum; i++)
            subset[i, 0] = false;
 
        // Fill the subset table in bottom up manner
        for (int i = 1; i <= sum; i++) {
            for (int j = 1; j <= n; j++) {
                subset[i, j] = subset[i, j - 1];
                if (i >= set[j - 1])
                    subset[i, j] = subset[i, j]
                                   || subset[i - set[j - 1], j - 1];
            }
        }
 
        return subset[sum, n];
    }
 
    // Driver code
    public static void Main()
    {
        int[] set = { 3, 34, 4, 12, 5, 2 };
        int sum = 9;
        int n = set.Length;
        if (isSubsetSum(set, n, sum) == true)
            Console.WriteLine("Found a subset with given sum");
        else
            Console.WriteLine("No subset with given sum");
    }
}
// This code is contributed by Sam007


PHP
= $set[$i-1])
                $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 <= n; i++)
    {
    for (int j = 0; j <= sum; j++)
        printf ("%4d", subset[i][j]);
    printf("n");
    }*/
 
    return $subset[$n][$sum];
}
 
// Driver code
$set = array(3, 34, 4, 12, 5, 2);
$sum = 9;
$n = count($set);
 
if (isSubsetSum($set, $n, $sum) == true)
    echo "Found a subset with given sum";
else
    echo "No subset with given sum";
 
// This code is contributed by anuj_67.
?>


C++
// CPP program for the above approach
#include 
using namespace std;
 
// Taking the matrix as globally
int tab[2000][2000];
 
// Check if possible subset with
// given sum is possible or not
int subsetSum(int a[], int n, int sum)
{
     
    // If the sum is zero it means
    // we got our expected sum
    if (sum == 0)
        return 1;
         
    if (n <= 0)
        return 0;
   
    // If the value is not -1 it means it
    // already call the function
    // with the same value.
    // it will save our from the repetation.
    if (tab[n - 1][sum] != -1)
        return tab[n - 1][sum];
   
    // if the value of a[n-1] is
    // greater than the sum.
    // we call for the next value
    if (a[n - 1] > sum)
        return tab[n - 1][sum] = subsetSum(a, n - 1, sum);
    else
    {
         
        // Here we do two calls because we
        // don't know which value is
        // full-fill our critaria
        // that's why we doing two calls
        return tab[n - 1][sum] = subsetSum(a, n - 1, sum) ||
                       subsetSum(a, n - 1, sum - a[n - 1]);
    }
}
 
// Driver Code
int main()
{
    // Storing the value -1 to the matrix
    memset(tab, -1, sizeof(tab));
    int n = 5;
    int a[] = {1, 5, 3, 7, 4};
    int sum = 12;
 
    if (subsetSum(a, n, sum))
    {
        cout << "YES" << endl;
    }
    else
        cout << "NO" << endl;
   
    /* This Code is Contributed by Ankit Kumar*/
}


输出
Found a subset with given sum

复杂度分析:在最坏的情况下,上述解决方案可能会尝试给定集合的所有子集。因此,上述解决方案的时间复杂度是指数级的。问题是实际上的NP完全(此问题没有已知的多项式时间解)。

方法2要解决伪多项式时间内的问题,请使用动态编程。
因此,我们将创建一个大小为(arr.size()+ 1)*(target + 1)的2D数组,类型为boolean 。如果存在来自A [0….i]的元素子集且总和=’j’ ,则状态DP [i] [j]为该问题的解决方法是:

if (A[i-1] > j)
DP[i][j] = DP[i-1][j]
else 
DP[i][j] = DP[i-1][j] OR DP[i-1][j-A[i-1]]
  1. 这意味着如果当前元素的值大于“当前总和值”,我们将复制先前情况的答案
  2. 如果当前的总和值大于’ith’元素,我们将看到以前的任何状态是否已经经历了sum =’j’或任何先前的状态都经历了值’j – A [i]’ ,这将解决我们的问题目的。

下面的模拟将阐明上述方法:

set[]={3, 4, 5, 2}
target=6
 
    0    1    2    3    4    5    6

0   T    F    F    F    F    F    F

3   T    F    F    T    F    F    F
     
4   T    F    F    T    T    F    F   
      
5   T    F    F    T    T    T    F

2   T    F    T    T    T    T    T

下面是上述方法的实现:

C++

// A Dynamic Programming solution
// for subset sum problem
#include 
 
// Returns true if there is a subset of set[]
// with sun equal to given sum
bool 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
    bool subset[n + 1][sum + 1];
 
    // If sum is 0, then answer is true
    for (int i = 0; i <= n; i++)
        subset[i][0] = true;
 
    // If sum is not 0 and set is empty,
    // then answer is false
    for (int i = 1; i <= sum; i++)
        subset[0][i] = false;
 
    // Fill the subset table in botton up manner
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= sum; j++) {
            if (j < set[i - 1])
                subset[i][j] = subset[i - 1][j];
            if (j >= set[i - 1])
                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 <= n; i++)
     {
       for (int j = 0; j <= sum; j++)
          printf ("%4d", subset[i][j]);
       printf("\n");
     }*/
 
    return subset[n][sum];
}
 
// Driver code
int main()
{
    int set[] = { 3, 34, 4, 12, 5, 2 };
    int sum = 9;
    int n = sizeof(set) / sizeof(set[0]);
    if (isSubsetSum(set, n, sum) == true)
        printf("Found a subset with given sum");
    else
        printf("No subset with given sum");
    return 0;
}
// This code is contributed by Arjun Tyagi.

Java

// A Dynamic Programming solution for subset
// sum problem
class GFG {
 
    // 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
        boolean subset[][] = new boolean[sum + 1][n + 1];
 
        // If sum is 0, then answer is true
        for (int i = 0; i <= n; i++)
            subset[0][i] = true;
 
        // If sum is not 0 and set is empty,
        // then answer is false
        for (int i = 1; i <= sum; i++)
            subset[i][0] = false;
 
        // Fill the subset table in botton
        // up manner
        for (int i = 1; i <= sum; i++) {
            for (int j = 1; j <= n; j++) {
                subset[i][j] = subset[i][j - 1];
                if (i >= set[j - 1])
                    subset[i][j] = subset[i][j]
                                   || subset[i - set[j - 1]][j - 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[sum][n];
    }
 
    /* Driver code*/
    public static void main(String args[])
    {
        int set[] = { 3, 34, 4, 12, 5, 2 };
        int sum = 9;
        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");
    }
}
 
/* This code is contributed by Rajat Mishra */

Python3

# A Dynamic Programming solution for subset
# sum problem Returns true if there is a subset of
# set[] with sun equal to given sum
 
# Returns true if there is a subset of set[]
# with sun equal to given sum
def isSubsetSum(set, n, 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 =([[False for i in range(sum + 1)]
            for i in range(n + 1)])
     
    # If sum is 0, then answer is true
    for i in range(n + 1):
        subset[i][0] = True
         
    # If sum is not 0 and set is empty,
    # then answer is false
    for i in range(1, sum + 1):
         subset[0][i]= False
             
    # Fill the subset table in botton up manner
    for i in range(1, n + 1):
        for j in range(1, sum + 1):
            if j= set[i-1]:
                subset[i][j] = (subset[i-1][j] or
                                subset[i - 1][j-set[i-1]])
     
    # uncomment this code to print table
    # for i in range(n + 1):
    # for j in range(sum + 1):
    # print (subset[i][j], end =" ")
    # print()
    return subset[n][sum]
         
# Driver code
if __name__=='__main__':
    set = [3, 34, 4, 12, 5, 2]
    sum = 9
    n = len(set)
    if (isSubsetSum(set, n, sum) == True):
        print("Found a subset with given sum")
    else:
        print("No subset with given sum")
         
# This code is contributed by
# sahil shelangia.

C#

// A Dynamic Programming solution for
// subset sum problem
using System;
 
class GFG {
    // Returns true if there is a subset
    // of set[] with sun equal to given sum
    static bool 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
        bool[, ] subset = new bool[sum + 1, n + 1];
 
        // If sum is 0, then answer is true
        for (int i = 0; i <= n; i++)
            subset[0, i] = true;
 
        // If sum is not 0 and set is empty,
        // then answer is false
        for (int i = 1; i <= sum; i++)
            subset[i, 0] = false;
 
        // Fill the subset table in bottom up manner
        for (int i = 1; i <= sum; i++) {
            for (int j = 1; j <= n; j++) {
                subset[i, j] = subset[i, j - 1];
                if (i >= set[j - 1])
                    subset[i, j] = subset[i, j]
                                   || subset[i - set[j - 1], j - 1];
            }
        }
 
        return subset[sum, n];
    }
 
    // Driver code
    public static void Main()
    {
        int[] set = { 3, 34, 4, 12, 5, 2 };
        int sum = 9;
        int n = set.Length;
        if (isSubsetSum(set, n, sum) == true)
            Console.WriteLine("Found a subset with given sum");
        else
            Console.WriteLine("No subset with given sum");
    }
}
// This code is contributed by Sam007

的PHP

= $set[$i-1])
                $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 <= n; i++)
    {
    for (int j = 0; j <= sum; j++)
        printf ("%4d", subset[i][j]);
    printf("n");
    }*/
 
    return $subset[$n][$sum];
}
 
// Driver code
$set = array(3, 34, 4, 12, 5, 2);
$sum = 9;
$n = count($set);
 
if (isSubsetSum($set, $n, $sum) == true)
    echo "Found a subset with given sum";
else
    echo "No subset with given sum";
 
// This code is contributed by anuj_67.
?>
输出
Found a subset with given sum

查找子集总和的记忆技术:

方法:

  1. 在此方法中,我们也遵循递归方法,但在此方法中,我们首先使用-1或任何负值初始化时使用了另一个2-D矩阵。
  2. 在这种方法中,我们避免了一些递归调用,而递归调用本身是重复的,这就是我们使用二维矩阵的原因。在此矩阵中,我们存储前一个调用值的值。

下面是上述方法的实现:

C++

// CPP program for the above approach
#include 
using namespace std;
 
// Taking the matrix as globally
int tab[2000][2000];
 
// Check if possible subset with
// given sum is possible or not
int subsetSum(int a[], int n, int sum)
{
     
    // If the sum is zero it means
    // we got our expected sum
    if (sum == 0)
        return 1;
         
    if (n <= 0)
        return 0;
   
    // If the value is not -1 it means it
    // already call the function
    // with the same value.
    // it will save our from the repetation.
    if (tab[n - 1][sum] != -1)
        return tab[n - 1][sum];
   
    // if the value of a[n-1] is
    // greater than the sum.
    // we call for the next value
    if (a[n - 1] > sum)
        return tab[n - 1][sum] = subsetSum(a, n - 1, sum);
    else
    {
         
        // Here we do two calls because we
        // don't know which value is
        // full-fill our critaria
        // that's why we doing two calls
        return tab[n - 1][sum] = subsetSum(a, n - 1, sum) ||
                       subsetSum(a, n - 1, sum - a[n - 1]);
    }
}
 
// Driver Code
int main()
{
    // Storing the value -1 to the matrix
    memset(tab, -1, sizeof(tab));
    int n = 5;
    int a[] = {1, 5, 3, 7, 4};
    int sum = 12;
 
    if (subsetSum(a, n, sum))
    {
        cout << "YES" << endl;
    }
    else
        cout << "NO" << endl;
   
    /* This Code is Contributed by Ankit Kumar*/
}
输出
YES

复杂度分析:

  • 时间复杂度: O(sum * n),其中sum是’目标和’,’n’是数组的大小。
  • 辅助空间: O(sum * n),因为二维数组的大小为sum * n。