This is the "Minion's bored game" problem from Google's "Foobar challenge":
There you have it. Yet another pointless "bored" game created by the bored minions of Professor Boolean.
The game is a single player game, played on a board with \$n\$ squares in a horizontal row. The minion places a token on the left-most square and rolls a special three-sided die.
If the die rolls a "Left", the minion moves the token to a square one space to the left of where it is currently. If there is no square to the left, the game is invalid, and you start again.
If the die rolls a "Stay", the token stays where it is.
If the die rolls a "Right", the minion moves the token to a square, one space to the right of where it is currently. If there is no square to the right, the game is invalid and you start again.
The aim is to roll the dice exactly \$t\$ times, and be at the rightmost square on the last roll. If you land on the rightmost square before \$t\$ rolls are done then the only valid dice roll is to roll a "Stay". If you roll anything else, the game is invalid (i.e., you cannot move left or right from the rightmost square).
To make it more interesting, the minions have leaderboards (one for each \$n,t\$ pair) where each minion submits the game he just played: the sequence of dice rolls. If some minion has already submitted the exact same sequence, they cannot submit a new entry, so the entries in the leader-board correspond to unique games playable.
Since the minions refresh the leaderboards frequently on their mobile devices, as an infiltrating hacker, you are interested in knowing the maximum possible size a leaderboard can have.
Write a function
answer(t, n)
, which given the number of dice rolls \$t\$, and the number of squares in the board \$n\$, returns the possible number of unique games modulo 123454321. i.e. if the total number is \$S\$, then return the remainder upon dividing \$S\$ by 123454321, the remainder should be an integer between 0 and 123454320 (inclusive).\$n\$ and \$t\$ will be positive integers, no more than 1000. \$n\$ will be at least 2.
I'm new to dynamic programming, so the only solution I could implement without looking for hints was recursion + memoization. The code works on my own workstation and returns results really fast, but keeps failing in the challenge terminal. My hunch is that I miss some aspect of optimization that is obvious for a more experienced person. What could be improved about this code? Also, if possible, what would you normally do, if you want to convert this to bottom-up dynamic programming solution? Looking forward to your comments, and thank you in advance for your ideas!
from collections import defaultdict
import sys
sys.setrecursionlimit(5000)
def answer(t, n):
"""
t - number of steps, n - length of the board
"""
def isInvalid(remaining_steps, position, n):
"""
utility to check if route is invalid
"""
# went outside the board
if position < 1 or position > n:
return True
# didn't get to the end by the time steps ran out
elif remaining_steps == 0 and position != 1:
return True
# not enough steps left to finish anyway
elif position - remaining_steps > 1:
return True
def rec(remaining_steps, position):
## Check if game is valid
if isInvalid(remaining_steps, position, n):
return 0
## base condition
# if we are at the end and the steps are exhausted
if position == 1 and remaining_steps == 0:
return 1
# if leftmost cell is reached, the only possible moves are to stay, so that is one valid game
elif position == 1 and remaining_steps > 0:
return 1
else:
return (mrec(remaining_steps-1, position) + mrec(remaining_steps-1, position+1) + mrec(remaining_steps-1, position-1)) % 123454321
def mrec(remaining_steps, position):
"""
Use hash table for memoization.
"""
# I wonder if here I could get some sort of integer overflow issues?
q = "{0},{1}".format(remaining_steps, position)
if d.get(q):
return d.get(q)
else:
retval = rec(remaining_steps, position)
d[q] = retval
return retval
d = defaultdict()
return mrec(t, n)