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're working on a secret team solving coded transmissions.
Your team is scrambling to decipher a recent message, worried it's a plot to break into a major European National Cake Vault. The message has been mostly deciphered, but all the words are backward! Your colleagues have handed off the last step to you.
Write a methodreverseWords() that takes a message as an array of characters and reverses the order of the words in place.↴
An in-placemethod
modifies data structures or objects outside of its
own stack frame↴
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 Java. 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:
publicstaticintproduct1ToN(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?
publicstaticintproduct1ToN(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 Java, you'll get
a StackOverflowError.
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.
(i.e.: stored on
the process heap or in
the stack frame of a calling function). Because of this, the
changes made by the method remain after
the call completes.
In-place algorithms are sometimes called
destructive, since the original input is
"destroyed" (or modified) during
the method call.
Careful: "In-place" does not mean "without
creating any additional variables!" Rather, it means
"without creating a new copy of the input." In general, an
in-place method will only create
additional variables that are O(1) space.
An out-of-placemethod
doesn't make any changes that are visible to
other methods. Usually,
those methods copy any data structures or objects
before manipulating and changing them.
In many languages, primitive values (integers,
floating point numbers, or characters) are copied when passed as
arguments, and more complex data structures
(arrays, heaps, or hash tables) are
passed by
reference. In Java, arguments that are pointers can be modified in place.
Here are two methods that do the same
operation on an array, except one is
in-place and the other is out-of-place:
publicstaticvoidsquareArrayInPlace(int[] intArray){for(int i =0; i < intArray.length; i++){
intArray[i]*= intArray[i];}// NOTE: no need to return anything - we modified// intArray in place}publicstaticint[]squareArrayOutOfPlace(int[] intArray){// we allocate a new array with the length of the input arrayint[] squaredArray =newint[intArray.length];for(int i =0; i < intArray.length; i++){
squaredArray[i]=(int) Math.pow(intArray[i],2);}return squaredArray;}
Working in-place is a good way to save time and
space. An in-place algorithm avoids the cost of
initializing or copying data structures, and it usually has
an O(1) space cost.
But be careful: an in-place algorithm can cause side effects.
Your input is "destroyed" or "altered," which can affect
code outside of your method. For
example:
Generally, out-of-place algorithms are considered safer
because they avoid side effects. You should only use an
in-place algorithm if you're space constrained or
you're positive you don't need the original input
anymore, even for debugging.
Why an array of
characters instead of a string?
The goal of this
question is to practice manipulating strings
in place. Since we're modifying the
message, we need
a mutable↴
A mutable object can be changed after it's
created, and an immutable object can't.
In Java, everything (except for strings) is
mutable by default:
That said, if you're defining your own class, you
can make its objects immutable by making all
fields final
and private.
publicclassIntegerPair{privatefinalint x;privatefinalint y;IntegerPair(int x,int y){this.x = x;this.y = y;}}
IntegerPair p =newIntegerPair(5,10);
p.x =50;// Compilation error: cannot assign a value to a final variable
Java
Strings can be mutable or immutable depending
on the language.
Strings are immutable in Java.
Any time you change a string (e.g.: tacking on an extra character,
making it lowercase, swapping two characters),
you're actually creating a new and separate copy:
String first ="first";
System.out.println(first.hashCode());// prints something
first = first +"!";
System.out.println(first.hashCode());// different string, different hash code
Java
But in some other languages, like C++, strings
can be mutable, and we can modify them directly:
string testString("mutable?");
testString[7]='!';// testString is now "mutable!"
C++
If you want mutable strings in Java, you can
use a StringBuilder
object:
StringBuilder mutableString =newStringBuilder("mutable?");
mutableString.setCharAt(7,'!');// still the same object!// mutableString is now "mutable!"// convert to an immutable string
String immutableString = mutableString.toString();
Java
Or, you can convert the string to
an array
of characters, which will be mutable.
Mutable objects are nice because you can make
changes in-place, without allocating a new
object. But be careful—whenever you make an in-place change
to an object, all references to that object will now
reflect the change.
type
like an array,
instead of Java's
immutable strings.
When writing your method, assume the message contains only letters and spaces, and all words are separated by one space.
Gotchas
We can do this in O(1)
space. Remember, in place.↴
An in-placemethod
modifies data structures or objects outside of its
own stack frame↴
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 Java. 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:
publicstaticintproduct1ToN(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?
publicstaticintproduct1ToN(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 Java, you'll get
a StackOverflowError.
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.
(i.e.: stored on
the process heap or in
the stack frame of a calling function). Because of this, the
changes made by the method remain after
the call completes.
In-place algorithms are sometimes called
destructive, since the original input is
"destroyed" (or modified) during
the method call.
Careful: "In-place" does not mean "without
creating any additional variables!" Rather, it means
"without creating a new copy of the input." In general, an
in-place method will only create
additional variables that are O(1) space.
An out-of-placemethod
doesn't make any changes that are visible to
other methods. Usually,
those methods copy any data structures or objects
before manipulating and changing them.
In many languages, primitive values (integers,
floating point numbers, or characters) are copied when passed as
arguments, and more complex data structures
(arrays, heaps, or hash tables) are
passed by
reference. In Java, arguments that are pointers can be modified in place.
Here are two methods that do the same
operation on an array, except one is
in-place and the other is out-of-place:
publicstaticvoidsquareArrayInPlace(int[] intArray){for(int i =0; i < intArray.length; i++){
intArray[i]*= intArray[i];}// NOTE: no need to return anything - we modified// intArray in place}publicstaticint[]squareArrayOutOfPlace(int[] intArray){// we allocate a new array with the length of the input arrayint[] squaredArray =newint[intArray.length];for(int i =0; i < intArray.length; i++){
squaredArray[i]=(int) Math.pow(intArray[i],2);}return squaredArray;}
Working in-place is a good way to save time and
space. An in-place algorithm avoids the cost of
initializing or copying data structures, and it usually has
an O(1) space cost.
But be careful: an in-place algorithm can cause side effects.
Your input is "destroyed" or "altered," which can affect
code outside of your method. For
example:
Generally, out-of-place algorithms are considered safer
because they avoid side effects. You should only use an
in-place algorithm if you're space constrained or
you're positive you don't need the original input
anymore, even for debugging.
We can do this in O(n) time.
If you're swapping individual words one at a time, consider
what happens when the words are different
lengths. Isn't each swapO(n)
time in the worst case?
Breakdown
Start your free trial!
Log in or sign up with one click to get immediate access to 3 free mock interview questions