
Programming Interview Question: Eight Queens - bcjordan
http://jetheis.com/blog/2013/12/01/programming-interview-question-eight-queens/
======
bluecalm
One more reason why this is terrible question: many people solved it in the
past as part of a programming course, many didn't. If you want to test which
school they went to you can look at the resume. If you want to see how they
handle writing solvers for simple games/puzzles under pressure of the
interview come up with one that is guaranteed (or almost) to be new for the
interviewee.

~~~
nu2ycombinator
This is best comment to tell about the current state of some of the
programming interview questions.

------
tzs
> I do have to say that as someone who sometimes conducts technical
> interviews, I'm not sure I would actually give this question to an
> interviewee, unless I was leaving him or her alone to work for a couple of
> hours, because of how long it would probably take to reach an answer

It can actually be done fairly quickly if you really only ask for 8x8 instead
of a general NxN solver, and the person realizes one key thing: computers have
gotten ridiculously fast, so that if you just bang out the first dumb thing
that comes to mind, you can probably be done and have the answers before the
people with the clever, general, efficient solutions get their solutions coded
or even designed.

For instance, this brute force, ridiculously ugly, un-clever, only-works-
for-8x8 solution took about 5 minutes or so to write, with half that spent
deciding how to number the diagonals and looking at a couple of squares on a
chessboard to see how rank/file maps to my diagonal numbering:

    
    
       #include <stdio.h>
    
       int main(void)
       {
           unsigned long pos;
    
           for (pos = 0; pos <= 077777777; ++pos)
           {
               unsigned long tmppos = pos;
               unsigned long taken = 0;
               int rank;
               int file;
    
               for (file = 0; file < 8; ++file)
               {
                   unsigned long mask;
    
                   rank = tmppos & 07;
    
                   mask = (1L << file) |       /* file */
                           (1L << (rank + 8)) |    /* rank */
                           (1L << (23 + file - rank))  /* diagonal */
                           | (1L << (45 - file - rank)); /* other diagonal */
                   if (taken & mask)
                       goto fail;
                   taken |= mask;
                   tmppos >>= 3;
               }
               printf("%lo\n", pos);
       fail:   ;
           }
           return 0;
       }
    

It takes under 0.5 seconds on my 2009 Mac Pro when compiled without
optimization, and about half that with optimization.

(To be completely honest, I rarely write C nowadays, so although it only took
a few minutes to write, the damn thing took half an hour to debug. I wrote "1
<<" instead of "1L <<" because I thought for some reason that int was 64 bit
nowadays).

~~~
ucarion
Not sure if you're aware of this, but the technique of representing a chess
board as a 64-bit bitmap is in fact the method used by pretty much all chess
engines these days. These 64-bit unsigned long longs are known as "bitboards".

[https://chessprogramming.wikispaces.com/Bitboards](https://chessprogramming.wikispaces.com/Bitboards)

------
coldcode
Never had a job where programming chess boards was a requirement. So unless
you have one don't ask this. It's not a programming question it's a puzzle.

~~~
VLM
That's the unfortunate part of it. You could have an interesting conversation
about strategy. Its an interesting puzzle because the ultimate crude brute
force implementation, if my math estimates are correct, is a "couple day"
project for a "several hundred" sized parallel cluster, but a slightly smarter
algo would be much quicker. There's only 64!/56! possible board configs for 8
pieces ignoring symmetry speedups, and a couple 1E9/second class machines
would only take a couple hundred machine*days.

The funniest thing about puzzle interviews as an observer is I can't stop
thinking ... "and if he gets the job he's going to spend 12 hour days writing
a boring frameworked CRUD app" ... "this poor noob probably thinks real on the
job programming is like solving chessboard puzzles all day long LOL".

The unfortunate part about the abstract puzzle fad is almost no one is paid to
solve abstract puzzles but no one is tested on real puzzles. "Yes I know quite
well traveling salesman is infeasible for 48 states, I'm asking you to solve
it for 5 sites because we only have 5 warehouses" etc.

~~~
Crito
Because there is such a straightforward though intractable brute force
solution, but several straightforward improvements that can be made on it, I
think this is actually a fairly reasonable interview question.

If they can't get the naive brute force solution in a few minutes, that
indicates something pretty bad. If they treat it as a permutation question and
get the n! brute force answer, that is much better (and is still something
that can be done in about the same amount of time. If you treat the problem as
numbered permutations then checking for diagonals even becomes non-tedious,
making it _easier_ to write out on a whiteboard than the naive brute force
solution).

If they get a proper solution in only a few minutes, you know that they either
have their act together, or that they have seen the problem before _and
remember the solution_. That in itself is a good indicator, though if they get
a proper solution right away there should be some follow-up questions to get a
handle on how exactly they were able to solve it that quickly.

~~~
waps
Plus even if they know the solution, that proves practical knowledge of search
strategies (having coded at least 1). Even if it only proves you know the
naive one, that's good.

Real world problems just aren't practical in interviews, you'll never get to
the point where their programming chops are really being tested.

------
yetanotherphd
I did this in about 20 minutes in Python in an interview.

The trick to solving things fast is to use the structure of the problem to
make your problem solving procedure less general. That is, you do premature
optimization, except instead of trading readability for runtime speed, your
are trading readability for program length.

With real problems, there is a similar tradeoff, so I think this is a useful
skill to demonstrate. But it does take practice to find the tradeoff that
works best for interview questions, which is not the same as for real life
coding.

In my solution, I use mutable state to reduce the amount of code. board is
mutated to avoid the boilerplate of copying objects. valid is mutated as a
kind of optimization, but the real reason is it makes the program simpler.

    
    
      def complete_board(board, l):
        """If possible, completes a board (whose first l rows
        have been filled with 1 queen per row).  Returns
        true if this is possible, false otherwise
    
        board is a list of ints representing the position
        (0-based) of each queen"""
    
        N = len(board)
    
        if l == N:
          return True
    
        # determine the valid positions for a queen in the r'th row
        valid = [True] * N
        for row, col in enumerate(board[:l]):
          valid[col] = False
          s = l - row
          if col + s < N:
            valid[col + s] = False
          if col - s >= 0:
            valid[col - s] = False
    
        for col, v in enumerate(valid):
          if v:
            board[l] = col
            if complete_board(board, l + 1):
              return True
        return False

------
pkaye
This question seems totally unreasonable for the length of any technical
interview I have taken or conducted. Most interviews run under an hour and
focus on multiple topics. Its rare you can focus on one question more than 10
minutes. The rule of thumb I use is that if I'm picking a question as an
interviewer, I make sure I can answer that question in 1/3 the time I plan to
give to the candidate. Given the pressure that they are under, I expect to owe
that that much.

~~~
kbenson
From the article:

 _I do have to say that as someone who sometimes conducts technical
interviews, I 'm not sure I would actually give this question to an
interviewee, unless I was leaving him or her alone to work for a couple of
hours, because of how long it would probably take to reach an answer._

So it appears they are aware of that problem. I'm of the opinion that some
"non-classical" problem is probably a better indication of skill and
reasoning. Work through a known problem you had at work (which the candidate
is hopefully not too familiar with, otherwise abort and try another), and see
how they reason out the problem, then ask for some code that might fix it.

------
zackbrown
I don't think it would be too difficult to find a candidate who could at least
talk through or whiteboard out an imperative solution to this. If a candidate
couldn't come up with at least an outline of a solution, I probably wouldn't
want to hire them.

This particular problem lends itself really well to functional or constraint
logic solutions, though (e.g.
[http://www.cs.nott.ac.uk/~rxq/files/8nQueenModel.pdf](http://www.cs.nott.ac.uk/~rxq/files/8nQueenModel.pdf))
-- I'd give bonus points to a candidate who could identify that this problem
is well suited to one of those toolsets instead of immediately whipping out
ruby, python, or js.

~~~
bluecalm
I'd give bonus points for pointing out some implementation decisions:

-when searching is it better to use copy-make or move/undo approach ? (copy make is faster on modern pc's, at least for things like 8Q's, it's also much easier to implement as you don't need any code for undoing moves)

-what is the best way to check if given move (placing the queen) is valid ? (you need three 0-1 arrays for rows and diagonals in both direction this a neat trick, very difficult to come up with but which speeds the whole thing immensely)

-how do you break out of the recursion if the task is changed to find one solution ?

I know all those things because I am a hobbyist who wrote a lot of solvers and
dabbled into chess programming. I am probably in top 0.1% of people if I get
question like this. I am incompetent when it comes to things needed in most
commercial applications as I've never worked on big commercial projects. This
question filters for people like me. I can't imagine how this a good thing for
anything practical (unless the task is to produce the best chess program or
something).

~~~
kenjackson
What is copy/make vs move/undo? I'm not familiar with that terminology...

~~~
bluecalm
When you search you have a choice of:

1)having only one board and make/undo moves on it. So every time you put a new
queen on the board you add it to the representation of the board and every
time you go back in recursion you need to do an undo before exiting the
function (so you new branch won't have queens placed on the board from
previous branch)

2)storing as many boards as many steps deep in the recursion you are (max 8 in
8 queens problem) so every time you go deeper into recursion you create new
board then you copy current state there and then you make a move (place a
queen) on this new board; this way when you go back in recursion you do
nothing as this new board is taken care of by stack pointer going back.

The cost of copy make is memory (you need memory for new board representation
for every step deeper into recursion) but it's usually not much of a problem.
The benefit is that you don't need to code undo logic (which in case of some
games is quite complicated) and undo is much faster which usually outweights
costs of copying the memory. Some top chess programs are move/undo and some
are copy/make, chess board representation usually takes about 200 bytes and
variations may go as deep as 100 half moves which means you may need like 20kb
additional memory for every thread doing searching. In case of 8 queens it's
of course trivial though.

Some more information (for chess, so way more advanced):

[https://chessprogramming.wikispaces.com/Copy-
Make](https://chessprogramming.wikispaces.com/Copy-Make)

[https://chessprogramming.wikispaces.com/Make+Move](https://chessprogramming.wikispaces.com/Make+Move)

[https://chessprogramming.wikispaces.com/Unmake+Move](https://chessprogramming.wikispaces.com/Unmake+Move)

~~~
kenjackson
Is this equivalent to a depth vs breadth first search? Sounds very similar...

------
bcjordan
> I do have to say that as someone who sometimes conducts technical
> interviews, I'm not sure I would actually give this question to an
> interviewee, unless I was leaving him or her alone to work for a couple of
> hours, because of how long it would probably take to reach an answer.

This is a great point that I realized that when I sat down to write up a
solution myself. The video-explanation-version of coding it up for the CFI
course ended up being 60 minutes long.

Suspect when it does get asked in an interview context they might only want to
see some of your problem solving process and algorithm devising, not a full
code-up.

------
thelucky41
This programming problem was what actually got me interested in compilers in
the first place.

When I was 16 and learning how to program in Java, there was an online
competition for high schoolers, and this was one of the introductory
programming questions. Your program had to execute in under a time limit (5
seconds, N=13), and I had come up with the best solution I could and was still
at 7 seconds. It took me the rest of the month spending hours a day reading on
speed optimizations in order to cut down every unnecessary instruction, and
remove every intermediate variable. Eventually, the program executed in 4.9
seconds.

While quite interesting, the problem only took an afternoon to implement the
algorithm. What differentiated my chops from my competitors was that I could
improve my program beyond the first implementation.

------
mike_mg
it is sort of confusing... do you really consider it a hard problem? I am
first year maths student at Uni Warsaw and we literally had this problem in
the first month, and solution was found by most people in just a few minutes.
It sometimes amazes me how people here at HN find some hard for me problems
easy and vice versa

~~~
camus2
Frankly i've been programming for a few years i would not be able to solve the
problem in 20 mins. Maybe in 3/4 hours but 20 mins no way. I could come with
an idea to solve it though.

But that's a very good exercice.

> I am first year maths student at Uni Warsaw

then Math is your major discipline. Please write down how you would express
the solution in a pure mathematical form.

~~~
mike_mg
Yeah that's a cool exercise and I try to do a lot of these!

I guess expressing it mathematically purely is already done in the code of the
original solution, because in a way program == proof. If you have some time,
here's a great read on this:
[http://www.maa.org/sites/default/files/pdf/upload_library/22...](http://www.maa.org/sites/default/files/pdf/upload_library/22/Ford/DonaldKnuth.pdf)

------
nilkn
> You can see how tricky this can be if you try to do it by hand (unless
> you're Emilie, who found a solution in just a few minutes).

I got it on my second try, though maybe I just got lucky.

~~~
Codhisattva
Not lucky - the answer is inferable. Exhaustive search is just the primitive
way.

~~~
VintageCool
Just place them like knights!

------
scott_s
Back in grad school, I took a configurable computing course where we were to
solve the N-Queens problem on an FPGA. My short write-ups are here:
[http://people.cs.vt.edu/~scschnei/ece5530/](http://people.cs.vt.edu/~scschnei/ece5530/)

This was a computer engineering course, and I was a computer science student
with no background in Verilog or VHDL. I think it took me about 30 hours to
get a working solution. However, my solution is very limited, and shows how
unfamiliar I was with thinking in hardware terms; other students in the course
produced solutions which did not have my limitations, and they did it with
much less effort. When I mentioned that to another student in the course, he
looked surprised. When I mentioned I was a CS student with no prior experience
with FPGAs, Verilog or VHDL, he said, oh, that's pretty good!

------
pmorici
Here is a solution I wrote when I was an undergrad TA that uses only bit
arrays and can check for a valid solution using bitwise logic operations. That
said what a horrible interview question. The hard part about the problem is
figuring out how to convert a x/y position on the board to a diagonal dx/dy
coordinate. Once you figure that out the coding is simple but I remember it
taking me a day of mulling over in the back of my head before the solution
came to me in my sleep.

[http://therandombit.blogspot.com/2008/09/n-queens-
implmented...](http://therandombit.blogspot.com/2008/09/n-queens-implmented-
with-bits.html)

------
tiedye
Everyone seems to forget that there are explicit solutions for the N queens
problem.

[http://en.wikipedia.org/wiki/Eight_queens_puzzle#Explicit_so...](http://en.wikipedia.org/wiki/Eight_queens_puzzle#Explicit_solutions)

------
bloaf
If I got that question, I'd probably spend most of my time worrying about
whether or not I should count solutions that are rotations/reflections of
other solutions as unique.

------
thedigitalengel
A quick solution involves starting at a corner, and placing each queen one
horizontal and two vertical (a constrained knight's move, basically) away from
the previous one. This works for an 8x8 board, but does not generalize to an
NxN board (it is easy to see that this won't work when N is divisible by 3).
Determining which N's it exactly works for is a math, not a programming
puzzle, however. :)

------
columbo
If I took this as a "Question to prepare yourself for an interview" and not a
"Question you might be asked during an interview" I think it's great.

As prep it's a concise problem you can do on your own time to get your brain
working over basic things that you may not have been thinking about lately.

As a question to actually give someone (or receive) during an interview? Seems
way to long.

~~~
vosper
I work with someone whose go-to question is the 8 Queens problem. I think it's
an interesting exercise, but like you said it's quite long - can easily
consume most of the interview. It's also very poorly aligned with the kind of
work we do and code we write (for example, I can't think of a single place in
our code using recursion) and so I'm not a fan of it as part of an interview.

Edit: typo

~~~
pmiller2
So, how do the candidates that pass this colleague's interview do in the
actual job?

------
mnbvcxza
This just boils down to populating an 8 element array with all the numbers
from 0 - 7, where abs(x[n] - x[m] != abs(n - m))

~~~
JoeAltmaier
Right! I did that solution in response to a Byte magazine contest years ago.
It ran in 20 minutes in Basic on an HP2000.

The tricky part is permuting the array. I used a recursive swap routine,
swapping to the right and testing if the array were valid to the left of i
then recursing i+1. If it fails you don't have to recurse, you can pop,
trimming the search tree drastically. There were 8 or 9 solutions if I
remember right, less if you eliminate rotations and mirrors. {edit: 12
solutions}

------
LukeWalsh
I really don't understand writing a program to solve this problem. Granted I
play chess casually but I had a solution before I even finished reading the
problem.

I think it's an interesting puzzle, but I don't think that makes it a good
interview question.

~~~
gamegoblin
Agreed. Knight's tour is much more of a "programming" sort of chess problem.

------
tel
I love the Haskell version of this because non-deterministic computation via
(depth-first) search is embodied by the list monad!

The preliminaries are that we represent a board position containing N queens
on an NxN board by a list of row positions in each of N columns.

    
    
        newtype Board = Board [Int] deriving ( Eq, Show )
        emptySolution = Board []
    

Then, given any particular NxN board with M queens in first M columns that is
a solution to M-queens, we can generate the list of possible new boards with
(M+1) queens. We try all possible row positions for the new column excepting
the rows where queens already exist.

    
    
        attempts :: Int -> Board -> [Board]
        attempts n (Board queens) = 
          map (\r -> Board (r : queens)) ([0 .. n - 1] \\ queens)
    

And we prune out the ones where the new queen has a diagonal conflict.

    
    
        valid :: Board -> Bool
        valid (Board []) = True
        valid (Board (q:cols)) = 
          and $ map (\(n, c) -> abs (q - c) /= n) (zip [1..] cols)
    
          -- this is a bit clever, as we walk back through
          -- the previous columns, if the absolute difference
          -- between the row position of the new queen and
          -- an old queen is ever the count of the column 
          -- we're in, then it must be in a diagonal line
          -- from the new queen
    

All of the above is pretty standard, but the fun part is using the list monad
to structure a search. Each bind (<-) in do-notation can be thought of as non-
deterministically setting a particular choice from the right side to the name
on the left

    
    
        queens :: Int -> [Board]
        queens n = search n [emptySolution] where
          search 0 boards = boards -- no more queens to add
          search k boards = do
            board <- search (k - 1) boards -- find a smaller solution
            filter valid (attempts board)  -- try all possible extensions,
                                           -- remove the bad ones
    
        >>> length (queens 8)
        92
    

Finally, the pattern of `search` is really common, it's just iterating the
generation of attempted solutions. We can write it with the built-in function
`Control.Monad.foldM` or another common utility combinator, `iterateM`

    
    
        iterateM :: Monad m => (a -> m a) -> a -> [m a]
        iterateM f x = iterate (f =<<) (return x)
    
        queens n = iterateM (filter valid . attempts n) emptySolution !! n
    

Finally, it's worth noting that there's nothing special about the list monad's
implementation. It's easy to rewrite it without using the typeclass.

    
    
        -- this is the list monad definition
        instance Monad [] where
          return a = [a]
          xs >>= f = concat (map f xs)
    
        iterateList :: (a -> [a]) -> a -> [[a]]
        iterateList f x = iterate (concat . map f) [x]

------
atmosx
I am very interested in finding out Emily's approach on this.

~~~
Jtsummers
How I'd approach it by hand, demonstrated on a 4x4 grid (other than 1x1,
smallest grid with a solution).

I'll go column by column, noting symmetry that also means I only need to check
half the rows for the first queen, and with symmetry and the squares she
eliminates I'll have fewer options to consider on each subsequent column.

    
    
      +---+---+---+---+
      | Q | X | X | X |
      +---+---+---+---+
      |   | X |   |   |
      +---+---+---+---+
      |   |   | X |   |
      +---+---+---+---+
      |   |   |   | X |
      +---+---+---+---+
    

First queen placed, add the second we only have two options so I'll try the
top one.

    
    
      +---+---+---+---+
      | Q | X | X | X |
      +---+---+---+---+
      |   | X | X |   |
      +---+---+---+---+
      |   | Q | X | X |
      +---+---+---+---+
      |   |   | X | X |
      +---+---+---+---+
    
    

Oops, gotta move the second queen:

    
    
      +---+---+---+---+
      | Q | X | X | X |
      +---+---+---+---+
      |   | X | Q | X |
      +---+---+---+---+
      |   |   | X | X |
      +---+---+---+---+
      |   | Q | X | X |
      +---+---+---+---+
    

Uh oh, can't place the 4th. Moving the first queen since we've exhausted all
placements of the 3rd and 2nd queen with the 1st in the upper corner.

    
    
      +---+---+---+---+
      |   | X |   |   |
      +---+---+---+---+
      | Q | X | X | X |
      +---+---+---+---+
      |   | X |   |   |
      +---+---+---+---+
      |   |   | X |   |
      +---+---+---+---+
    
      +---+---+---+---+
      |   | X | Q | X |
      +---+---+---+---+
      | Q | X | X | X |
      +---+---+---+---+
      |   | X | X | Q |
      +---+---+---+---+
      |   | Q | X | X |
      +---+---+---+---+
    

I placed the remainder at once because there was only one legal placement in
each column.

Extending this approach to 8x8 isn't difficult if the challenge is merely to
find _a_ solution.

EDIT: I found it in 3 attempts on the 8x8. Going to the column with the most
constraints (fewest options) to minimize my branching.

~~~
Crito
How I'd do it by hand:

Each row must have a queen, and each row must have the queen in a different
position. Therefore there are 8 rows and we just need to find a correct
permutation of them.

Number each row 0 through 7. Generate permutations of these numbers.

To check for diagonal collisions between permutation indices x and y:
_abs(perm[x]-perm[y]) == abs(x-y)_

(In other words, if rows 2 and 4 cannot be two places apart, rows 6 and 7
cannot be one place apart.)

Check for diagonal collisions as you're writing out possible valid
permutations by hand. Doesn't take long to generate solutions since you don't
have to actually draw or mentally picture a board to work out diagonal
collisions. You just need to keep 8 single digit numbers in your head.

------
JSno
haskell solution:

import Control.Monad

queens n = foldM (\y _ -> [ x : y | x <\- [1..n], safe x y 1]) [] [1..n] safe
x [] n = True safe x (c:y) n = and [ x /= c , x /= c + n , x /= c - n , safe x
y (n+1)]

main = mapM_ print $ queens 8

\----
[http://en.wikibooks.org/wiki/Algorithm_Implementation/Miscel...](http://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/N-Queens)

~~~
thirsteh
Your indentation broke:

    
    
        import Control.Monad
     
        queens n = foldM (\y _ -> [ x : y | x <- [1..n], safe x y 1]) [] [1..n]
    
        safe x [] n = True
        safe x (c:y) n = and [ x /= c, x /= c + n, x /= c - n, safe x y (n+1)]
     
        main = mapM_ print $ queens 8

~~~
JoeAltmaier
Hm. And maybe that's why Haskell isn't catching on. That looks little better
than terminal scripting languages. Its concise, sure. But maybe that's not as
helpful as you might think, in communicating a problem solution.

~~~
thirsteh
Haskell _is_ catching on. It's extremely popular in academia, and it's growing
on e.g. Github:
[http://ec2-54-224-80-201.compute-1.amazonaws.com:8888/langua...](http://ec2-54-224-80-201.compute-1.amazonaws.com:8888/languages_visualization/71c6f3773a3cbe2aa440949bc906bd4d/index.html)

But you're right, examples like this are extremely poor marketing for Haskell.
It's really not how you'd write most Haskell programs. A lot of the Haskell
code on sites like Rosetta Code is deliberately terse to show off.

I changed the indentation a little bit, to make it more clear:

    
    
        queens n = foldM
                   (\y _ -> [x:y | x <- [1..n], safe x y 1])
                   []
                   [1..n]
        
        safe x []    n = True
        safe x (c:y) n = and [ x /= c
                             , x /= c + n
                             , x /= c - n
                             , safe x y (n+1)
                             ]
        
        main = mapM_ print $ queens 8
    

'queens' is essentially a for loop that concatenates the elements between 1..n
that are safe, and 'safe' is a function that returns true if it's given an
empty list, or if the input satisfies all of the predicates in the other list
(notice the recursion that's possible because of laziness.) The function 'and'
that it calls takes a list of bools and returns true if all of them are true.
'main' prints each list that results from running 'queens 8'

There is some syntax and convention you have to learn, e.g. what the _ in
mapM_ means, what a list comprehension is, or what : means, etc. but once you
have it's a fairly elegant solution (compare to the code snippets from
imperative languages.)

------
seivan
I could solve it AND write it in a readable fashion. Your code is unreadable,
you'd fail my test.

What the actual fuck ((if b[r]?[c] then c else -1) for c in [0..n-1]).filter
(c) -> c >= 0

I am aware of what the line is doing, I just have no idea what b & c are. Best
guess; Board & Column.

------
kofq
My brother had created fastest solution ( both in time and space complexity )
for this problem.

