简体   繁体   中英

Memoized to DP solution - Making Change

Recently I read a problem to practice DP. I wasn't able to come up with one, so I tried a recursive solution which I later modified to use memoization. The problem statement is as follows :-

Making Change. You are given n types of coin denominations of values v(1) < v(2) < ... < v(n) (all integers). Assume v(1) = 1, so you can always make change for any amount of money C. Give an algorithm which makes change for an amount of money C with as few coins as possible. [on problem set 4]

I got the question from here

My solution was as follows :-

def memoized_make_change(L, index, cost, d):
    if index == 0:
        return cost

    if (index, cost) in d:
        return d[(index, cost)]

    count = cost / L[index]
    val1 = memoized_make_change(L, index-1, cost%L[index], d) + count
    val2 = memoized_make_change(L, index-1, cost, d)

    x = min(val1, val2)
    d[(index, cost)] = x
    return x

This is how I've understood my solution to the problem. Assume that the denominations are stored in L in ascending order. As I iterate from the end to the beginning, I have a choice to either choose a denomination or not choose it. If I choose it, I then recurse to satisfy the remaining amount with lower denominations. If I do not choose it, I recurse to satisfy the current amount with lower denominations.

Either way, at a given function call, I find the best(lowest count) to satisfy a given amount.

Could I have some help in bridging the thought process from here onward to reach a DP solution? I'm not doing this as any HW, this is just for fun and practice. I don't really need any code either, just some help in explaining the thought process would be perfect.

[EDIT]

I recall reading that function calls are expensive and is the reason why bottom up(based on iteration) might be preferred. Is that possible for this problem?

Here is a general approach for converting memoized recursive solutions to "traditional" bottom-up DP ones, in cases where this is possible.

First, let's express our general "memoized recursive solution". Here, x represents all the parameters that change on each recursive call. We want this to be a tuple of positive integers - in your case, (index, cost) . I omit anything that's constant across the recursion (in your case, L ), and I suppose that I have a global cache . (But FWIW, in Python you should just use the lru_cache decorator from the standard library functools module rather than managing the cache yourself.)

To solve for(x):
    If x in cache: return cache[x]
    Handle base cases, i.e. where one or more components of x is zero
    Otherwise:
        Make one or more recursive calls
        Combine those results into `result`
        cache[x] = result 
        return result

The basic idea in dynamic programming is simply to evaluate the base cases first and work upward:

To solve for(x):
    For y starting at (0, 0, ...) and increasing towards x:
        Do all the stuff from above

However, two neat things happen when we arrange the code this way:

  1. As long as the order of y values is chosen properly (this is trivial when there's only one vector component, of course), we can arrange that the results for the recursive call are always in cache (ie we already calculated them earlier, because y had that value on a previous iteration of the loop). So instead of actually making the recursive call, we replace it directly with a cache lookup.

  2. Since every component of y will use consecutively increasing values, and will be placed in the cache in order, we can use a multidimensional array (nested list s, or else a Numpy array) to store the values instead of a dictionary.

So we get something like:

To solve for(x):
    cache = multidimensional array sized according to x
    for i in range(first component of x):
        for j in ...:
            (as many loops as needed; better yet use `itertools.product`)
            If this is a base case, write the appropriate value to cache
            Otherwise, compute "recursive" index values to use, look up
            the values, perform the computation and store the result
    return the appropriate ("last") value from cache

I suggest considering the relationship between the value you are constructing and the values you need for it.

In this case you are constructing a value for index, cost based on:

index-1 and cost
index-1 and cost%L[index]

What you are searching for is a way of iterating over the choices such that you will always have precalculated everything you need.

In this case you can simply change the code to the iterative approach:

for each choice of index 0 upwards:
    for each choice of cost:
        compute value corresponding to index,cost

In practice, I find that the iterative approach can be significantly faster (eg *4 perhaps) for simple problems as it avoids the overhead of function calls and checking the cache for preexisting values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM