1

I was given the following assignment:

There is n gram of mineral to be dug up on a certain asteroid. At the beginning there is only one robot at our disposal. Each robot can do one of two activities in a day (which take up the whole day):

  1. dig up 1 gram of mineral
  2. clone itself (the clone can only take action the next day)

Write a function that returns the minimum number of days needed to dig up at least n grams of mineral.

Of course, I immediately realised that this was a dynamic programming problem, but I have no idea how to solve the problem this way.

The only thing I managed to do was the naive recursive solution, which doesn't pass all the tests, which doesn't surprise me, because n can be up to a million:

def mining_rec(n, r = 1, d = 0):
    if n <= 0:
        return d
    if r >= n:
        return d + 1

    if r == 1:
        return min(mining_rec(n - 1, r, d + 1), mining_rec(n, r + 1, d + 1))
    else:
        for i in range(r + 1):
            mining_robots = i
            cloning_robots = r - i
            return mining_rec(n - mining_robots, r + cloning_robots, d + 1) 

Do you have any ideas on how this could be done using dynamic programming?

3
  • 2
    Dynamic programming is basically recursion combined with memoization. So add memoization.
    – mkrieger1
    Commented Jun 20 at 21:28
  • 1
    And remember, memoization is really easy in Python. Just from functools import cache at the top of the file, and then add @cache before the function you want memoized. Commented Jun 20 at 21:32
  • 1
    That return in your for loop cannot be right: your loop will only make one iteration.
    – trincot
    Commented Jun 21 at 8:32

1 Answer 1

1

First of all, your current code has a for loop that will always exit upon its first iteration. This happens to turn out fine (you're lucky), because the other iterations would never lead to a more optimal solution. In other words, when you don't have enough robots yet to mine the required minerals, it is never a bad idea to have all of them make clones.

Still, let's first fix the code to make it do what you initially intended:

In that case you should perform all iterations of the loop, and keep the minimum of the values you got from the recursive calls. Like so:

    return min(
        mining_rec(n - mining_robots, r + (r - mining_robots), d + 1)
        for mining_robots in range(r + 1)
    )

You don't actually need to deal with r==1 separately, as also in that case you can use the above code.

Then, to use memoization, use functools.cache:

@cache
def mining_rec(n, r = 1, d = 0):
    if n <= 0:
        return d
    if r >= n:
        return d + 1

    return min(
        mining_rec(n - mining_robots, r + (r - mining_robots), d + 1)
        for mining_robots in range(r + 1)
    )

Greedy solution

But as already hinted above, you don't really have to go through this tree of possible choices. You can take a greedy approach where you only dig up minerals in the last day, when you have enough robots. In all other cases you can choose to clone all available robots.

This means the number of robots you have will double each day until you have reached a number of robots that is not less than the number of grams you need of the mineral. In other words, you need to clone for a number of days that is approximately log2𝑛. More precisely it is ⌈log2𝑛⌉ and to that we have to add one day for the digging.

That leads to this code:

def mining_greedy(n):
    return 1 + (n - 1).bit_length() if n > 0 else 0
1
  • 1
    Thanks for the answer. I was also thinking about the tactic with cloning robots until there were enough, but I wasn't sure that would work in every possible case
    – PK96
    Commented Jun 21 at 14:23

Not the answer you're looking for? Browse other questions tagged or ask your own question.