- There's almost certainly an analytic solution to this problem-- something like (number of bottles) + (log (base exchange rate) number of bottles) + 1...but probably not quite that simple
- The naive solution would be to do something like:
- keep a running total of the number of bottles that have been drunk
- then, until there are fewer than
numExchange
bottles left:- exchange as many bottles as possible and add that to the total
- update the number of remaining bottles
- If
numExchange
was 1, we'd get an infinite loop, but it looks like this isn't allowed (2 <= numExchange <= 100
)
- I'll start by implementing the naive solution. Given the problem specification (maximum
numBottles
is 100, minimumnumExchange
is 2), I don't think we'll run out of compute time... - Updating the number of remaining bottles should work as follows:
- Exchange whatever we can. The number of bottles now in our collection is
availableBottles // numExchange
- Also track what's left over (
availableBottles % numExchange
)
- Exchange whatever we can. The number of bottles now in our collection is
class Solution:
def numWaterBottles(self, numBottles: int, numExchange: int) -> int:
drinkableTotal = numBottles
bottlesAvailable = numBottles
while bottlesAvailable >= numExchange:
nextExchange = bottlesAvailable // numExchange
drinkableTotal += nextExchange
bottlesAvailable %= numExchange
bottlesAvailable += nextExchange
return drinkableTotal
- All given test cases pass
- Other test cases:
numBottles = 100, numExchange = 2
: passesnumBottles = 100, numExchange = 3
: passesnumBottles = 100, numExchange = 4
: passesnumBottles = 48, numExchange = 7
: passes
- Seems ok...submitting...
Slow, but I'll take it-- solved!
- Let's see if we can come up with an analytic solution
- We can initially drink
numBottles
(let's write this as$B$ to simplify notation) - After that, every
numExchange
bottles (we'll write this as$E$ to simplify notation) turns into a full bottle (which can be added to the total) and then an empty bottle (which can be exchanged in the next round)
So the total drinkable number of bottles can be given by the series:
In other words:
- Start by drinking
$B$ bottles, which yields$B$ empty bottles - Those empties can be exchanged for
$\left\lfloor \frac{B}{E} \right\rfloor$ new full bottles - There are
$B \mod E$ additional empty bottles left after that exchange - In each subsequent round, we can repeat this same process (divide by
$E$ , take the floor, add this to the total number of drinks, add the remainder to the total number of empties)
Interestingly, we can see that
-
$B \mod E$ is always less than$E$ - Therefore
$(B \mod E) / E$ is always less than 1, which means we can get rid of it in the "floor" operation
So now we have
Doing some Googling, it looks like the series
We can take
$\frac{B}{1 - \frac{1}{E}} =
Now we can split the fraction:
Which simplifies to:
Finally(!!), we can turn this back into Python code:
class Solution:
def numWaterBottles(self, numBottles: int, numExchange: int) -> int:
return numBottles + (numBottles - 1) // (numExchange - 1)
- Test cases pass!!
- Checking a random additional test case:
numBottles = 48, numExchange = 7
(passes!) - I'm just gonna submit this instead of checking in more detail...
Woo! Go math 🎉!