We use cookies to personalize content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners. By clicking "Accept Cookies," scrolling this page, clicking a link or continuing to browse otherwise, you agree to the use of cookies. Privacy and Cookie Policies
You only have 3 free questions left (including this one).
But it doesn't have to end here! Sign up for the 7-day coding interview crash course and you'll get a free Interview Cake problem every week.
You're in!
You have a methodRand5() that generates a random integer from 1 to 5. Use it to write a methodRand7() that generates a random integer from 1 to 7.
Rand5() returns each integer with equal probability. Rand7() must also return each integer with equal probability.
Gotchas
Simply running Rand5() twice, adding the results, and taking a modulus won't give us an equal probability for each possible result.
Not convinced? Count the number of ways to get each possible result from 1..7.
Your method will have worst-case infinite runtime, because sometimes it will need to "try again."
However, at each "try" you only need to make two calls to Rand5(). If you're making 3 calls, you can do better.
We can get away with worst-case O(1) space. Does your answer have a non-constant space cost? If you're using recursion (and your language doesn't have tail-call optimization↴
Some compilers and interpreters will do what's called "tail call optimization" (TCO), where it can optimize some recursive methods to avoid building up a tall call stack. Python and Java decidedly do not use TCO. Some Ruby implementations do, but most don't. Scheme is one of the few languages that guarantee TCO in all implementations. In general, best not to assume your compiler/interpreter will do this work for you.
), you're potentially incurring a worst-case infinite space cost in the call stack.↴
Overview
The call stack is what a program uses to keep
track of method calls. The call stack is
made up of stack frames—one for
each method call.
For instance, say we called a method that
rolled two dice and printed the sum.
defroll_die():return random.randint(1,6)defroll_two_and_sum():
total =0
total += roll_die()
total += roll_die()print total
roll_two_and_sum()
We're still translating this code to C#. Here it is in Python 2.7:
First, our program calls RollTwoAndSum(). It goes
on the call stack:
RollTwoAndSum()
That function calls RollDie(), which gets pushed
on to the top of the call stack:
RollDie()
RollTwoAndSum()
Inside of RollDie(), we call random.randint().
Here's what our call stack looks like then:
random.randint()
RollDie()
RollTwoAndSum()
When random.randint() finishes, we return back to
RollDie() by removing
("popping") random.randint()'s stack frame.
RollDie()
RollTwoAndSum()
Same thing when RollDie() returns:
RollTwoAndSum()
We're not done yet! RollTwoAndSum()
calls RollDie()again:
RollDie()
RollTwoAndSum()
Which calls random.randint() again:
random.randint()
RollDie()
RollTwoAndSum()
random.randint() returns, then RollDie() returns,
putting us back in RollTwoAndSum():
RollTwoAndSum()
Which calls print():
print()
RollTwoAndSum()
What's stored in a stack frame?
What actually goes in a method's
stack frame?
A stack frame usually stores:
Local variables
Arguments passed into the method
Information about the caller's stack frame
The return address—what the program should do
after the function returns (i.e.: where it should "return
to"). This is usually somewhere in the middle of the caller's
code.
Some of the specifics vary between processor architectures. For
instance, AMD64 (64-bit x86) processors pass some arguments in
registers and some on the call stack. And, ARM processors (common
in phones) store the return address in a special register instead
of putting it on the call stack.
The Space Cost of Stack Frames
Each method call creates its own stack
frame, taking up space on the call stack. That's important
because it can impact the space complexity of an algorithm.
Especially when we use recursion.
For example, if we wanted to multiply all the numbers
between 1 and n,
we could use this recursive approach:
publicintProduct1ToN(int n){// We assume n >= 1return(n >1)?(n *Product1ToN(n -1)):1;}
What would the call stack look like
when n = 10?
First, Product1ToN() gets called
with n = 10:
Product1ToN() n = 10
This calls Product1ToN() with
n = 9.
Product1ToN() n = 9
Product1ToN() n = 10
Which calls Product1ToN()
with n = 8.
Product1ToN() n = 8
Product1ToN() n = 9
Product1ToN() n = 10
And so on until we get to n = 1.
Product1ToN() n = 1
Product1ToN() n = 2
Product1ToN() n = 3
Product1ToN() n = 4
Product1ToN() n = 5
Product1ToN() n = 6
Product1ToN() n = 7
Product1ToN() n = 8
Product1ToN() n = 9
Product1ToN() n = 10
Look at the size of all those stack frames! The entire call stack
takes up O(n) space. That's right—we
have an O(n) space cost even though
our method itself doesn't create any data
structures!
What if we'd used an iterative approach instead of a recursive one?
publicintProduct1ToN(int n){// We assume n >= 1int result =1;for(int num =1; num <= n; num++){
result *= num;}return result;}
This version takes a constant amount of space. At the beginning of the loop,
the call stack looks like this:
Product1ToN() n = 10, result = 1, num = 1
As we iterate through the loop, the local variables change, but we
stay in the same stack frame because we don't call any other
functions.
Product1ToN() n = 10, result = 2, num = 2
Product1ToN() n = 10, result = 6, num = 3
Product1ToN() n = 10, result = 24, num = 4
In general, even though the compiler or interpreter will take
care of managing the call stack for you, it's important to consider the
depth of the call stack when analyzing the space complexity of an
algorithm.
Be especially careful with recursive functions!
They can end up building huge call stacks.
What happens if we run out of space? It's a stack
overflow! In C#, the program will just crash and exit.
If the very last thing
a method does is call
another method, then its stack frame
might not be needed any more. The methodcould free up its stack frame before doing its final
call, saving space.
This is called tail call optimization
(TCO). If a recursive function is optimized with TCO, then it
may not end up with a big call stack.
In general, most languages don't provide TCO. Scheme
is one of the few languages that guarantee tail call
optimization. Some Ruby, C, and Javascript
implementations may do it. Python and Java decidedly
don't.
Breakdown
Start your free trial!
Log in or sign up with one click to get immediate access to 3 free mock interview questions
Actually, we don't support password-based login. Never have. Just the OAuth methods above. Why?
It's easy and quick. No "reset password" flow. No password to forget.
It lets us avoid storing passwords that hackers could access and use to try to log into our users' email or bank accounts.
It makes it harder for one person to share a paid Interview Cake account with multiple people.
“Interview Cake has been my go to for coding interview prep. It emphasizes both fundamentals and problem solving skills while still keeping the exercises interesting. 10/10 would recommend
—
Kyle