Matching and cancelling
Whatever was opened last has to be closed first. Brackets and colliding asteroids are both that rule, and a stack is the structure that follows it.
The last one opened is the first one closed
If you're checking whether a string of brackets is balanced, the thing I'd want you to notice is that a
closing bracket can only ever match the most recent opening bracket that's still open. In {[()]}
the ) belongs to the ( immediately before it, and once that pair is gone the
] belongs to the [, and so on outwards. So you keep the opening brackets that haven't
been closed yet on a stack, and when a closing bracket arrives you compare it with the top.
There are only three ways it can go wrong, and you'll want to check all of them in an interview. The closing
bracket can arrive when the stack is empty (nothing to close), it can be the wrong shape for the top (a
] on top of a (), or the string can finish while something is still open.
Balanced brackets
balanced(s)
- Start with an empty stack.
- For each character: if it opens, push it. If it closes and the stack is empty or the top is a different shape, return false. Otherwise pop.
- At the end return true only if the stack is empty.
I've seen a lot of people forget the last check and return true for a string like the one in the trace below. The stack still has a bracket on it at the end, so the answer's false.
Checking "{[()]}(", which closes everything except the last bracket.
-
Step 1 / 8s{0i[1(2)3]4}5(6stack{0open1
'{' opens a bracket. Push it; it waits for its partner.
-
Step 2 / 8s{0[1i(2)3]4}5(6stack{0[1open2
'[' opens a bracket. Push it; it waits for its partner.
-
Step 3 / 8s{0[1(2i)3]4}5(6stack{0[1(2open3
'(' opens a bracket. Push it; it waits for its partner.
-
Step 4 / 8s{0[1(2)3i]4}5(6stack{0[1open2
')' closes '(', which is on top of the stack. Pop it.
-
Step 5 / 8s{0[1(2)3]4i}5(6stack{0open1
']' closes '[', which is on top of the stack. Pop it.
-
Step 6 / 8s{0[1(2)3]4}5i(6stackempty0open0
'}' closes '{', which is on top of the stack. Pop it.
-
Step 7 / 8s{0[1(2)3]4}5(6istack(0open1
'(' opens a bracket. Push it; it waits for its partner.
-
Step 8 / 8stack(0answerfalse
The string is finished but 1 bracket is still open. Unbalanced.
Cancelling instead of matching
The asteroid problem looks quite different at first, but it's the same structure. Each asteroid moves right (positive) or left (negative) at the same speed, and two of them only ever meet when a right-mover is followed by a left-mover. Consequently the asteroids moving right are waiting for something to hit them, and the one that arrives first is always going to hit the most recent of them. That's exactly the bracket rule again with a collision where the match used to be.
When a left-mover arrives you compare it with the top of the stack. If the top is smaller it explodes and the left-mover carries on to the next one, if they're the same size they both explode, and if the top is bigger the left-mover's gone. A left-mover that empties the stack (or finds another left-mover on top) survives for good, because nothing to its left can ever catch it.
Every asteroid goes onto the stack once and comes off at most once, so the inner loop that keeps popping doesn't make it quadratic. The same goes for the brackets.
Asteroids [10,2,-5,3,-3,-12], where the 12 moving left clears everything.
-
Step 1 / 10asteroids10021-5233-34-125stack
I'd go left to right and keep a stack of the asteroids that haven't exploded yet.
-
Step 2 / 10asteroids10021-5233-34-125stack100
10 is moving right, and nothing behind it can ever catch it up, so it goes on the stack.
-
Step 3 / 10asteroids10021-5233-34-125stack10021
2 is another right-mover, so it goes on top and it's the first thing the next left-mover will hit.
-
Step 4 / 10asteroids10021-5233-34-125stack100
-5 hits the 2, and since 2 is the smaller of the two it explodes while -5 keeps on going.
-
Step 5 / 10asteroids10021-5233-34-125stack100
This time -5 runs into 10, which is bigger, so it's -5 that goes.
-
Step 6 / 10asteroids10021-5233-34-125stack10031
3 is another right-mover, so it goes on top and it's the first thing the next left-mover will hit.
-
Step 7 / 10asteroids10021-5233-34-125stack100
-3 and 3 are the same size so they take each other out.
-
Step 8 / 10asteroids10021-5233-34-125stack
-12 hits the 10, and since 10 is the smaller of the two it explodes while -12 keeps on going.
-
Step 9 / 10asteroids10021-5233-34-125stack-120
Nothing's left in front of -12 that moves towards it, so it goes on the stack and won't hit anything again.
-
Step 10 / 10stack-120
What's left, from bottom to top, is [-12].