Search code examples
javamicro-optimization

Are there any efficient micro-optimizations to find the number of unique grid paths?


I am trying to solve this CSES problem: Grid Paths. You are given a string of length 48, and you have to find the amount of paths such that you traverse all of the grid and end up at the lower left corner.

I believe I have pruned the search to the best of my ability, as according to this book: CP Handbook (Look in the pruning the search category), the best optimization for this type of problem is to prevent your path from closing yourself off, and I have already implemented this. The time limits for this specific problem are tight, and although I have basically solved this problem, I am still failing 1-2 test cases because my solution takes around 1.01 seconds instead of being below the 1 second time limit.

Finally, I just wanted to know if there were any cool micro-optimizations I could use to marginally enhance the speed of my java code, so I could actually pass all of the test cases for this problem.

import java.io.*;

public class GridPaths {
    public static class FastIO {
        InputStream dis;
        byte[] buffer = new byte[1 << 17];
        int pointer = 0;

        public FastIO(String fileName) throws Exception {
            dis = new FileInputStream(fileName);
        }

        public FastIO(InputStream is) {
            dis = is;
        }

        int nextInt() throws Exception {
            int ret = 0;
            byte b;
            do {
                b = nextByte();
            } while (b <= ' ');
            boolean negative = false;
            if (b == '-') {
                negative = true;
                b = nextByte();
            }
            while (b >= '0' && b <= '9') {
                ret = 10 * ret + b - '0';
                b = nextByte();
            }
            return (negative) ? -ret : ret;
        }

        long nextLong() throws Exception {
            long ret = 0;
            byte b;
            do {
                b = nextByte();
            } while (b <= ' ');
            boolean negative = false;
            if (b == '-') {
                negative = true;
                b = nextByte();
            }
            while (b >= '0' && b <= '9') {
                ret = 10 * ret + b - '0';
                b = nextByte();
            }
            return (negative) ? -ret : ret;
        }

        Integer[] readArray(int n) throws Exception {
            Integer[] a = new Integer[n];
            for (int i = 0; i < n; i++) a[i] = nextInt();
            return a;
        }

        byte nextByte() throws Exception {
            if (pointer == buffer.length) {
                dis.read(buffer, 0, buffer.length);
                pointer = 0;
            }
            return buffer[pointer++];
        }

        String next() throws Exception {
            StringBuilder ret = new StringBuilder();
            byte b;
            do {
                b = nextByte();
            } while (b <= ' ');
            while (b > ' ') {
                ret.appendCodePoint(b);
                b = nextByte();
            }
            return ret.toString();
        }
    }

    static char[] board;
    static boolean[][] visited = new boolean[7][7];
    static int ans = 0;

    public static boolean works(int i, int j) {
        //makes sure that current spot is on the 7x7 grid and is not visited
        return (i >= 0 && i<=6 && j>=0 && j<=6 && !visited[i][j]);
    }
    public static void solve(int i, int j, int steps) {
        if (i == 6 && j == 0) {
            if (steps == 48) ans++; //all spots of the grid have to be visited in order to be counted as part of the answer
            return;
        }
        visited[i][j] = true;
        //you are given ? characters in the input string, and those mean that you have to try out all 4 combinations (U,D,L,R)
        if (board[steps] == '?' || board[steps] == 'L') {
            //second condition of the second if statement checks if the spot directly ahead of the current spot is blocked, and if it is, the left and right spots cannot both be unvisited or else you will not continue searching 
            if (works(i,j-1) && !(!works(i,j-2) && works(i+1,j-1) && works(i-1,j-1))) {
                solve(i, j - 1, steps + 1);
            }
        }
        if (board[steps] == '?' || board[steps] == 'R') {
            if (works(i,j+1) && !(!works(i,j+2) && works(i+1,j+1) && works(i-1,j+1))) {
                solve(i, j + 1, steps + 1);
            }
        }
        if (board[steps] == '?' || board[steps] == 'U') {
            if (works(i-1,j) && !(!works(i-2,j) && works(i-1,j+1) && works(i-1,j-1))) {
                solve(i - 1, j, steps + 1);
            }
        }
        if (board[steps] == '?' || board[steps] == 'D') {
            if (works(i+1,j) && !(!works(i+2,j) && works(i+1,j+1) && works(i+1,j-1))) {
                solve(i + 1, j, steps + 1);
            }
        }
        visited[i][j] = false;

    }
    public static void main(String[] args) throws Exception {
        FastIO in = new FastIO(System.in);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
        board = in.next().toCharArray();
        solve(0,0,0);
        out.println(ans);

        out.close();
    }

}

Note: I am already using one of the fastest, if not the fastest, ways to receive input in Java, so I do not believe I can actually improve upon that.


Solution

  • I've been playing around with this. In addition to using a standard mechanism for reading the input file (which I suggested in a comment), you can gain a little time in the search alg itself by doing two things:

    1. Break the case board[steps] == '?' off from the other cases. So check for board[steps] == '?' first, and just try all four directions in that case. Otherwise (the else case for if (board[steps] == '?'), just check for U/D/L/R. Since for most steps, the character will be '?', you save having to make the U/D/L/R tests most of the time.

    2. Look up the character to be tested once, with c = board[steps],and then use c in each test instead of board[steps].

    Doing these two things saved about 5% it seems. I was doing 100 reps of the solve and timing with System.currentTimeMillis(). I know there are more accurate ways of timing, but this was good enough to see a definite improvement even though the times jumped around quite a bit trial to trial. The best I ever saw in each case was 3600 millis for 100 iterations as originally written vs 3400 millis with the improvements.

    My guess is that it's mostly the first change that matters. I'd expect the compiler to be doing the second already, but I didn't try the two optimizations independently.