5

I have an array lets say [1, 2, 3, 4]. I have to check if an element or any combination of the elements sums to a specific number.

Examples

  1. 5, 1 + 4 = 5 and 2 + 3 = 5.
  2. 6, 1 + 2 + 3 = 6 and 2 + 4 = 6

On way could be to create a power-set of the array, as in this answer, and loop iterate through it. But thats not a good idea because if the number of elements i.e. n increases the power-set will become memory extensive. For that matter a better way would be to create subsets/subarrays of specific lengths and iterate through them one by one.

Lets say k is the length of the subarray then

  • k = 2 should give me [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
  • k = 3 should give me[[1, 2, 3], [1, 2, 4], [2, 3, 4]]

Now the question is that how would I go about creating the subarrays/subsets of specific length like above?

Adeel Miraj
  • 2,472
  • 3
  • 22
  • 32
  • what is the max n value? – hasan Oct 15 '18 at 08:18
  • 1
    That is the [“subset sum problem“](https://en.wikipedia.org/wiki/Subset_sum_problem) and is more efficiently solved with dynamic programming, instead of generating all subsets. – Martin R Oct 15 '18 at 08:29
  • @MartinR Exactly, thats what I was going to point to. but the PO went to the subset direction. – hasan Oct 15 '18 at 08:31
  • 2
    Note also that generating all subsets of size 0, 1, 2, 3, ... N is the *same* as generating the power set. – Martin R Oct 15 '18 at 08:32
  • @hasan `n` can be any value. If subsets approach is not appropriate then I'm open to a better approach. – Adeel Miraj Oct 15 '18 at 08:37
  • @Adeel you can check the wiki link added by MartinR – hasan Oct 15 '18 at 08:38
  • @MartinR I'm not going to create all the subsets. I'll create subsets unless I find the sum equal to the given number. – Adeel Miraj Oct 15 '18 at 08:38
  • 2
    Just as an example: a 100-element array has 100891344545564193334812497256 subarrays of size 50. – Martin R Oct 15 '18 at 08:38
  • Thats indeed a big number. Let me have a look at the wiki link that you have shared. I'll get back if I need anything else. – Adeel Miraj Oct 15 '18 at 08:41
  • Could you add more clarifications: **1)** Are all the elements strictly positive? **2)** Are the elements of the array unique? **3)** If zero is the desired sum, would an empty array be the answer? – ielyamani Oct 15 '18 at 20:20
  • 1) all the elements are positive. 2) the elements of the array may repeat. 3) in case of zero an empty array can be the answer. – Adeel Miraj Oct 15 '18 at 20:25
  • @Carpsen90 as martin mentioned the sub arrays approach will not be efficient either as size of the array increases. Subset sum is the way to go. I’m just finding it hard to implement. – Adeel Miraj Oct 15 '18 at 20:30
  • @Adeel Yes I know, it's a classic https://www.geeksforgeeks.org/perfect-sum-problem-print-subsets-given-sum/ – ielyamani Oct 15 '18 at 20:31

2 Answers2

4

This a variant of subset sum problem, or more generally, the Knapsack problem. The following solution supposes that:

  • All elements of the initial array are strictly positive,
  • The initial array may contain repeating elements,
  • If a sum can't be reached, the output is an empty array.

Let's starts with an example: let's create a dynamic table in which we'll try to find all the ways to get 5 by adding elements from [1, 2, 3, 4]:

dynamic table

In this table, the rows represent the elements of the array, in an ascending order, plus 0. The columns go from 0 to the sum 5.

In each cell, we ask ourselves, can we get to the title of this column, by adding one or more of the titles of the current and previous rows.

The number of solutions is the count of cells having true in them. In this case, two solutions:

1)

3_2

The green cell is true, so the current line is the last element from the solution. In this case, 3 is part of the solution. So the problem of finding a subarray which sum is 5, becomes finding a subarray which sum is 5 - 3. Which is 2. This is represented by the purple arrow 1: Go five columns to the left, and 1 row up.

In arrow 2, we look for the subset that has made it possible to have a partial sum of 2. In this case, we get two thanks to the 2 element. So following arrow 2 we go one row up, and two to the left.

With arrow 3 we reach the first cell in the first column, corresponding to 5 - 3 - 2, which is 0.

2)

Another path we could take starts from the red cell:

4_1

As you can see, the problem of making 5 out of [1, 2, 3, 4], becomes a new smaller problem of making 1 from [1, 2, 3], and then 1 out of [1, 2], and finally 1 out of `1.


Let's create and fill the dynamic table:

var dynamicTable: [[Bool]] =
    Array(repeating: Array(repeating: false, count: sum + 1),
          count: array.count + 1)

//All of the elements of the first column are true
//since we can always make a zero sum out of not elements
for i in 0...array.count {
    dynamicTable[i][0] = true
}

for row in 1...array.count {
    for column in 1...sum {
        if column < array[row - 1] {
            dynamicTable[row][column] = dynamicTable[row - 1][column]
        } else {
            if dynamicTable[row - 1][column] {
                dynamicTable[row][column] = true
            } else {
                dynamicTable[row][column] = dynamicTable[row - 1][column - array[row - 1]]
            }
        }
    }
}

Let's find all the paths leading to the sum:

var solutions = [[Int]]()

func getSubArraysWithTheSum(arr: [Int], row: Int, currentSum: Int, currentSolution: [Int]) {

    //The following block will be executed when
    //we reach the first cell in the first column
    if row == 0,
        currentSum == 0
    {
        solutions.append(currentSolution)
        //notice the return to exit the scope
        return
    }

    //The following block will be executed if
    //the current cell is NOT used to reach the sum
    if dynamicTable[row - 1][currentSum]
    {
        getSubArraysWithTheSum(arr: arr,
                               row: row - 1,
                               currentSum: currentSum,
                               currentSolution: currentSolution)
    }

    //The following block will be executed if
    //the current cell IS used to reach the sum
    if currentSum >= arr[row - 1],
        dynamicTable[row - 1][currentSum - arr[row - 1]]
    {
        getSubArraysWithTheSum(arr: arr,
                               row: row - 1,
                               currentSum: currentSum - arr[row - 1],
                               currentSolution: currentSolution + [arr[row - 1]])
    }
}

The whole function looks like this:

func getSubArrays(from array: [Int], withSum sum: Int) -> [[Int]] {

    guard array.allSatisfy({ $0 > 0 }) else {
        fatalError("All the elements of the array must be strictly positive")
    }

    guard array.count > 0, sum > 0 else {
        return []
    }

    var solutions = [[Int]]()
    var dynamicTable: [[Bool]] =
        Array(repeating: Array(repeating: false, count: sum + 1),
              count: array.count + 1)

    //All of the elements of the first column are true
    //since we can always make a zero sum out of not elements
    for i in 0...array.count {
        dynamicTable[i][0] = true
    }

    for row in 1...array.count {
        for column in 1...sum {
            if column < array[row - 1] {
                dynamicTable[row][column] = dynamicTable[row - 1][column]
            } else {
                if dynamicTable[row - 1][column] {
                    dynamicTable[row][column] = true
                } else {
                    dynamicTable[row][column] = dynamicTable[row - 1][column - array[row - 1]]
                }
            }
        }
    }

    func getSubArraysWithTheSum(arr: [Int], row: Int, currentSum: Int, currentSolution: [Int]) {

        //The following block will be executed when
        //we reach the first cell in the first column
        if row == 0,
            currentSum == 0
        {
            solutions.append(currentSolution)
            return
        }

        //The following block will be executed if
        //the current cell is NOT used to reach the sum
        if dynamicTable[row - 1][currentSum]
        {
            getSubArraysWithTheSum(arr: arr,
                                   row: row - 1,
                                   currentSum: currentSum,
                                   currentSolution: currentSolution)
        }

        //The following block will be executed if
        //the current cell IS used to reach the sum
        if currentSum >= arr[row - 1],
            dynamicTable[row - 1][currentSum - arr[row - 1]]
        {
            getSubArraysWithTheSum(arr: arr,
                                   row: row - 1,
                                   currentSum: currentSum - arr[row - 1],
                                   currentSolution: currentSolution + [arr[row - 1]])
        }
    }

    getSubArraysWithTheSum(arr: array, row: array.count , currentSum: sum, currentSolution: [])

    return solutions
}

Here are some test cases:

getSubArrays(from: [3, 1, 4, 2], withSum: 5)        //[[3, 2], [4, 1]]
getSubArrays(from: [1, 2, 2, 4], withSum: 3)        //[[2, 1], [2, 1]]
getSubArrays(from: [7, 3, 4, 5, 6, 1], withSum: 9)  //[[5, 3, 1], [5, 4], [6, 3]]
getSubArrays(from: [3], withSum: 3)                 //[[3]]
getSubArrays(from: [5], withSum: 10)                //[]
getSubArrays(from: [1, 2], withSum: 0)              //[]
getSubArrays(from: [], withSum: 4)                  //[]

This solution has been inspired by Sumit Ghosh's contribution here. A thorough explanation of how the dynamic table is constructed can be found in this video.

ielyamani
  • 17,807
  • 10
  • 55
  • 90
0

This is kind of subset sum problem.

For positive integers it might be solved using dynamic programming with complexity O(length * sum)

Make array A with length (sum + 1), filled with zeros, set A[0] = 1

For every source value v traverse array A from A[sum] down to A[v], checking if A[i-v] non-zero. If yes, mark A[i] cell with A[i-v] + 1 (number steps (values) to reach this cell).

If A[sum] is non-zero and contains combination with needed number of steps after all, this sum might be composed from array elements.

If you need to track also elements, add als their values in A[i] cells to retrieve the subset.

MBo
  • 77,366
  • 5
  • 53
  • 86