7

During a 45 minute technical interview with Google, I was asked a Leaper Graph problem. I wrote working code, but later was declined the job offer because I lacked Data structure knowledge. I'm wondering what I could have done better.

The problem was as following: "Given an N sized board, and told that a piece can jump i positions horizontally (left or right) and j positions vertically (up or down) (I.e, sort of like a horse in chess), can the leaper reach every spot on the board?"

I wrote the following algorithm. It recursively finds out if every position on the board is reachable by marking all spots on the graph that were visited. If it was not reachable, then at least one field was false and the function would return false.

      static boolean reachable(int i, int j, int n) {
        boolean grid[][] = new boolean[n][n];
        reachableHelper(0, 0, grid, i, j, n - 1);
        for (int x = 0; x < n; x++) {
          for (int y = 0; y < n; y++) {
            if (!grid[x][y]) {
              return false;
            }
          }
        }
        return true;
      }

      static void reachableHelper(int x, int y, boolean[][] grid, int i, int j, int max) {
        if (x > max || y > max || x < 0 || y < 0 || grid[x][y]) {
          return;
        }
        grid[x][y] = true;
        int i2 = i;
        int j2 = j;
        for (int a = 0; a < 2; a++) {
          for (int b = 0; b < 2; b++) {
            reachableHelper(x + i2, y + j2, grid, i, j, max);
            reachableHelper(x + j2, y + i2, grid, i, j, max);
            i2 = -i2;
          }
          j2 = -j2;
        }
      }

Now, later it was pointed out that the optimal solution would be to implement Donald Knuth's co-prime implementation: http://arxiv.org/pdf/math/9411240v1.pdf Is this something that one should be able to figure out on a 45 minute technical interview??

Besides the above, is there anything I could have done better?

edit:
- I enquired about starting position. I was told starting at 0,0 is fine.

edit2 Based on feedback, I wrote a while-loop with queue approach. The recursive approach runs into a stack-overflow when n = 85. However, the while loop with queue method below works up to ~n = 30,000. (after that it runs into heap-issues with memory exceeding GB's). If you know how to optimize further, please let me know.

    static boolean isReachableLoop(int i, int j, int n) {
        boolean [][] grid = new boolean [n][n];

        LinkedList<Point> queue = new LinkedList<Point>();
        queue.add(new Point(0,0)); // starting position. 

        int nodesVisited = 0;
        while (queue.size() != 0) {
          Point pos = queue.removeFirst();

          if (pos.x >= 0 &&  pos.y >= 0 && pos.x < n && pos.y < n) {
            if (!grid[pos.x][pos.y]) {
              grid[pos.x][pos.y] = true;
              nodesVisited++;
              int i2 = i;
              int j2 = j;
              for (int a = 0; a < 2; a++) {
                for (int b = 0; b < 2; b++) {
                  queue.add(new Point(pos.x+i2, pos.y+j2));
                  queue.add(new Point(pos.x+j2, pos.y+i2));
                  i2 = -i2;
                }
                j2 = -j2;
              }
            }
          }
        }
        if (nodesVisited == (n * n)) {
          return true;
        } else {
          return false;
        }
      }
Leo Ufimtsev
  • 4,670
  • 4
  • 31
  • 43
  • 1
    Are you given a starting position? Or do you have to discover the optimal position yourself? – smac89 Nov 20 '15 at 22:35
  • 1
    I asked the interviewer about that. I was told 0x0 was a legitamate position. – Leo Ufimtsev Nov 20 '15 at 22:37
  • 3
    If you can reach everywhere on the board from *any* position, then you can reach everywhere on the board from *every* position, so starting position doesn't matter – Matt Timmermans Nov 21 '15 at 03:35
  • Good point. Perhaps that got me some minus points. – Leo Ufimtsev Nov 21 '15 at 03:35
  • Why not use bfs or dfs ? – Saeid Nov 21 '15 at 03:46
  • 2
    But the above is basically a recursive depth first search already, no? – Leo Ufimtsev Nov 21 '15 at 03:47
  • 1
    Yes it is. Note that recursive dfs sometimes leads to stackoverflow. – Saeid Nov 21 '15 at 10:37
  • "can jump i positions horizontally (left or right) and j positions vertically (up or down)" -- I think you haven't written this condition correctly, since your code (and the Knuth paper) talks about a piece that can *also* move j positions horizontally and i positions vertically. – j_random_hacker Nov 21 '15 at 16:08
  • It's possible I didn't formulate it clear enough, this is how it was given to me. In case of ambiguity, refer to Knuth's paper. The basic idea is that it must jump (+-) i and at the same time (+-) j spots. – Leo Ufimtsev Nov 21 '15 at 16:10

2 Answers2

3

I ask a lot of interview questions like this. I don't think you would be expected to figure out the coprime method during the interview, but I would have docked you for using O(n^2) stack space -- especially since you passed all those parameters to each recursive call instead of using an object.

I would have asked you about that, and expected you to come up with a BFS or DFS using a stack or queue on the heap. If you failed on that, I might have a complaint like "lacked data structure knowledge".

I would also have asked questions to make sure you knew what you were doing when you allocated that 2D array.

If you were really good, I would ask you if you can use the symmetry of the problem to reduce your search space. You really only have to search a J*J-sized grid (assuming J>=i).

It's important to remember that the interviewer isn't just looking at your answer. He's looking at the way you solve problems and what tools you have in your brain that you can bring to bear on a solution.

Edit: thinking about this some more, there are lots of incremental steps on the way to the coprime method that you might also come up with. Nobody will expect that, but it would be impressive!

Matt Timmermans
  • 36,921
  • 2
  • 27
  • 59
  • 1
    I see. Thank you for your response. – Leo Ufimtsev Nov 21 '15 at 04:13
  • I added a while-loop + queue approach. (see edit 2). Would you approve of such code? – Leo Ufimtsev Nov 21 '15 at 16:13
  • 1
    It doesn't have any serious problems. ArrayDeque for BFS or ArrayList for DFS is more efficient than LinkedList, and you'd use 80% less memory if you checked grid[][] before putting the points in the queue. – Matt Timmermans Nov 21 '15 at 18:28
  • how come ArrayList would be more efficient? ArrayList is a dynamic array, adding removing elements would mean a lot of shifting where as LinkedList are individual elements pieced together? Good point on the grid[][] checking, thank you. – Leo Ufimtsev Nov 22 '15 at 00:41
  • 1
    Memory-wise, an ArrayList will cost between 8 and 16 bytes per entry on a 64-bit machine, not counting the Point objects themselves. That's the cost of between 1 and 2 references, since the backing array will be between 1/2 full and completely full. A LinkedList will take at least 32 bytes per entry, for the object header (because each one takes a separate node), the next and prev links, and the Point reference. – Matt Timmermans Nov 22 '15 at 01:37
  • 1
    Time-wise, adding to an ArrayList is also *significantly faster*. Including all the shifting, adding to an array list involves setting 2 references on average, since all that shifting will copy each item only 1 time on average (how that works is another common interview question). Adding to a LinkedList means allocating a new object and fixing up 4 references. Cache locality is also much worse with a linked list. Oh, NOTE that an ArrayList is for DFS, which means you add and remove from the end. For BFS you would use an ArrayDeque, becuase you remove from the front. – Matt Timmermans Nov 22 '15 at 01:38
  • Thank you for explanation. – Leo Ufimtsev Nov 22 '15 at 15:04
2

I'm sorry, I feel like I'm missing something.

If you can only go up or down by i and left or right by j, then a case (x,y) is reachable from a start case (a,b) if there are integers m and n so that

a + m*i = x

b + n*j = y

That is, everything is false for a square board where n > 1.

If you meant more like a knight in chess, and you can go up/down by i and left/right by j OR up/down by j and left/right by i, you can use the same technique. It just becomes 2 equations to solve:

a + m * i + n * j = x

b + o * i + p * j = y

If there are no integers m, n, o and p that satisfy those equations, you can't reach that point.

Community
  • 1
  • 1
Leherenn
  • 460
  • 3
  • 15
  • The latter, more like a knight that can jump to 8 different positions. The question is not about reaching x,y, but if every spot on the board can be reached thou. Also, in my case I had to write specific code rather than write a math equation? (Thoughts?) – Leo Ufimtsev Nov 21 '15 at 00:45
  • If you can check whether a spot can be reached, you only then have to loop over every spot and do the check each time. There are some more or less obvious optimizations that can be added like using the symmetry of the problem to reduce the search space. – Leherenn Nov 21 '15 at 12:41
  • The fact that the board has only finite width puts another condition on your integers m and n (and o and p). And in the second ("knight-like") case, there are also the constraints that m=p and n=o. – j_random_hacker Nov 25 '15 at 12:37