Finding shortest combinations in array/sequence that equals sum

拜拜、爱过 提交于 2019-12-23 09:30:50

问题


I'm totally stuck and have no idea how to go about solving this. Let's say I've an array

arr = [1, 4, 5, 10]

and a number

n = 8

I need shortest sequence from within arr which equals n. So for example following sequences within arr equals n

c1 = 5,1,1,1
c2 = 4,4
c3= 1,1,1,1,1,1,1,1

So in above case, our answer is c2 because it's shortest sequences in arr that equals sum.

I'm not sure what's the simplest way of finding a solution to above? Any ideas, or help will be really appreciated.

Thanks!

Edited:

  • Fixed the array
  • Array will possibly have postive values only.
  • I'm not sure how subset problem fixes this, probably due to my own ignorance. Does sub-set algorithm always give the shortest sequence that equals sum? For example, will subset problem identify c2 as the answer in above scenario?

回答1:


As has been pointed before this is the minimum change coin problem, typically solved with dynamic programming. Here's a Python implementation solved in time complexity O(nC) and space complexity O(C), where n is the number of coins and C the required amount of money:

def min_change(V, C):
    table, solution = min_change_table(V, C)
    num_coins, coins = table[-1], []
    if num_coins == float('inf'):
        return []
    while C > 0:
        coins.append(V[solution[C]])
        C -= V[solution[C]]
    return coins

def min_change_table(V, C):
    m, n = C+1, len(V)
    table, solution = [0] * m, [0] * m
    for i in xrange(1, m):
        minNum, minIdx = float('inf'), -1
        for j in xrange(n):
            if V[j] <= i and 1 + table[i - V[j]] < minNum:
                minNum = 1 + table[i - V[j]]
                minIdx = j
        table[i] = minNum
        solution[i] = minIdx
    return (table, solution)

In the above functions V is the list of possible coins and C the required amount of money. Now when you call the min_change function the output is as expected:

min_change([1,4,5,10], 8)
> [4, 4]



回答2:


For the benefit of people who find this question in future -

As Oscar Lopez and Priyank Bhatnagar, have pointed out, this is the coin change (change-giving, change-making) problem.

In general, the dynamic programming solution they have proposed is the optimal solution - both in terms of (provably!) always producing the required sum using the fewest items, and in terms of execution speed. If your basis numbers are arbitrary, then use the dynamic programming solution.

If your basis numbers are "nice", however, a simpler greedy algorithm will do.

For example, the Australian currency system uses denominations of $100, $50, $20, $10, $5, $2, $1, $0.50, $0.20, $0.10, $0.05. Optimal change can be given for any amount by repeatedly giving the largest unit of change possible until the remaining amount is zero (or less than five cents.)

Here's an instructive implementation of the greedy algorithm, illustrating the concept.

def greedy_give_change (denominations, amount):        
    # Sort from largest to smallest
    denominations = sorted(denominations, reverse=True)

    # number of each note/coin given
    change_given = list()

    for d in denominations:
        while amount > d:
            change_given.append(d)
            amount -= d

    return change_given

australian_coins = [100, 50, 20, 10, 5, 2, 1, 0.50, 0.20, 0.10, 0.05]
change = greedy_give_change(australian_coins, 313.37)
print (change)           # [100, 100, 100, 10, 2, 1, 0.2, 0.1, 0.05]
print (sum(change))      # 313.35

For the specific example in the original post (denominations = [1, 4, 5, 10] and amount = 8) the greedy solution is not optimal - it will give [5, 1, 1, 1]. But the greedy solution is much faster and simpler than the dynamic programming solution, so if you can use it, you should!




回答3:


This is problem is known as Minimum coin change problem.

You can solve it by using dynamic programming. Here is the pseudo code :

Set MinCoin[i] equal to Infinity for all of i
MinCoin[0] = 0

For i = 1 to N // The number N
For j = 0 to M - 1 // M denominations given
// Number i is broken into i-Value[j] for which we already know the answer
// And we update if it gives us lesser value than previous known.
   If (Value[j] <= i and MinCoin[i-Value[j]]+1 < MinCoin[i])
       MinCoin[i] = MinCoin[i-Value[j]]+1

Output MinCoin[N]



回答4:


This is an variant of subset-sum problem. In your problem, you can pick an item several times. You still can use a similar idea to solve this problem by using the dynamic prorgamming technique. The basic idea is to design a function F(k, j), such that F(k, j) = 1 means that there is a sequence from arr whose sum is j and length is k.

Formally, the base case is that F(k, 1) = 1, if there exists an i, such that arr[i] = k. For inductive case, F(k, j) = 1, if there exists an i, such that arr[i] = m, and F(k-1, j-m) = 1.

The smallest k with F(k, n) = 1 is the length of the shortest sequence you want.

By using the dynamic programming technique, you can compute function F without using recursion. By tracking additional information for every F(k, j), you also can reconstruct the shortest sequence.




回答5:


What you're trying to solve is a variant of the coin change problem. Here you're looking for smallest amount of change, or the minimum amount of coins that sum up to a given amount.

Consider a simple case where your array is

c = [1, 2, 3]

you write 5 as a combination of elements from C and want to know what is the shortest such combination. Here C is the set of coin values and 5 is the amount for which you want to get change.

Let's write down all possible combinations:

1 + 1 + 1 + 1 + 1
1 + 1 + 1 + 2
1 + 2 + 2
1 + 1 + 3
2 + 3

Note that two combinations are the same up to re-ordering, so for instance 2 + 3 = 3 + 2.

Here there is an awesome result that's not obvious at first sight but it's very easy to prove. If you have any sequence of coins/values that is a sequence of minimum length that sums up to a given amount, no matter how you split this sequence the two parts will also be sequences of minimum length for the respective amounts.

For instance if c[3] + c[1] + c[2] + c[7] + c[2] + c[3] add up to S and we know that 6 is the minimal length of any sequence of elements from c that add up to S then if you split

                              |
S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3]
                              |

you have that 4 is the minimal length for sequences that add up to c[3] + c[1] + c[2] + c[7] and 2 the minimal length for sequences that add up to c[2] + c[3].

                              |
S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3] 
                              |
  =        S_left             +     S_right

How to prove this? By contradiction, assume that the length of S_left is not optimal, that is there's a shorter sequence that adds up to S_left. But then we could write S as a sum of this shorter sequence and S_right, thus contradicting the fact that the length of S is minimal. □

Since this is true no matter how you split the sequence, you can use this result to build a recursive algorithm that follows the principles of dynamic programming paradigm (solving smaller problems while possibly skipping computations that won't be used, memoization or keeping track of computed values, and finally combining the results).

Because of this property of maintaining optimality for subproblems, the coins problem is also said to "exhibit optimal substructure".

OK, so in the small example above this is how we would go about solving the problem with a dynamic programming approach: assume we want to find the shortest sequence of elements from c = [1, 2, 3] for writing the sum 5. We solve the subproblems obtained by subtracting one coin: 5 - 1, 5 - 2, and 5 - 3, we take the smallest solution of these subproblems and add 1 (the missing coin).

So we can write something like

shortest_seq_length([1, 2, 3], 5) = 
    min( shortest_seq_length([1, 2, 3], 5-1),
         shortest_seq_length([1, 2, 3], 5-2),
         shortest_seq_length([1, 2, 3], 5-3)
        ) + 1

It is convenient to write the algorithm bottom-up, starting from smaller values of the sums that can be saved and used to form bigger sums. We just solve the problem for all possible values starting from 1 and going up to the desired sum.

Here's the code in Python:

def shortest_seq_length(c, S):
    res = {0: 0} # res contains computed results res[i] = shortest_seq_length(c, i)
    for i in range(1, S+1):
        res[i] = min([res[i-x] for x in c if x<=i]) + 1 
    return res[S]

Now this works except for the cases when we cannot fill the memoization structure for all values of i. This is the case when we don't have the value 1 in c, so for instance we cannot form the sum 1 if c = [2, 5] and with the above function we get

shortest_seq_length([2, 3], 5)
# ValueError: min() arg is an empty sequence

So to take care of this issue one could for instance use a try/catch:

def shortest_seq_length(c, S):
    res = {0: 0} # res is the dictionary containing results for each sum res[i] = shortest_seq_length(c, i)
    for i in range(1, S+1):
        try:
            res[i] = min([res[i-x] for x in c if x<=i and res[i-x] is not None]) +1
        except:
            res[i] = None # takes care of error when [res[i-x] for x in c if x<=i] is empty
    return res[S]

Try it out:

print(shortest_seq_length([2, 3], 5))
# 2
print(shortest_seq_length([1, 5, 10, 25], 37))
# 4
print(shortest_seq_length([1, 5, 10], 30))
# 3
print(shortest_seq_length([1, 5, 10], 25))
# 3
print(shortest_seq_length([1, 5, 10], 29))
# 7
print(shortest_seq_length([5, 10], 9))
# None

One small improvement to the algorithm is to skip the step of computing the minimum when the sum is equal to one of the values/coins, but this can be done better if we write a loop to compute the minimum. Thsi imrovement however doesn't improve the overall complexity that's O(mS) where m = len(c).



来源:https://stackoverflow.com/questions/9964289/finding-shortest-combinations-in-array-sequence-that-equals-sum

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!