diff --git a/src/Fire.java b/src/Fire.java index b968c51..d488a24 100644 --- a/src/Fire.java +++ b/src/Fire.java @@ -1,17 +1,22 @@ +import java.util.*; + public class Fire { /** * Returns how long it takes for all vulnerable trees to be set on fire if a * match is lit at a given location. * - * The forest is represented via a rectangular 2d char array where t represents a tree + * The forest is represented via a rectangular 2d char array where t represents + * a tree * and . represents an empty space. * - * At time 0, the tree at location [matchR, matchC] is set on fire. At every subsequent - * time step, any tree that is adjacent (up/down/left/right) to a burning tree is also - * set on fire. + * At time 0, the tree at location [matchR, matchC] is set on fire. At every + * subsequent + * time step, any tree that is adjacent (up/down/left/right) to a burning tree + * is also + * set on fire. * * - * EXAMPLE + * EXAMPLE * forest: * * t..tttt.t @@ -25,12 +30,16 @@ public class Fire { * Result: 8 * * Explanation: - * At time 0, the tree at (2, 6) is set on fire. At time 1, its adjacent trees also catch on fire - * At time 2, the trees adjacent to those catch as well. At time 8, the last tree to catch is at - * (0,6). In this example, there is one tree that never burns, so it is not included in the time calculation. + * At time 0, the tree at (2, 6) is set on fire. At time 1, its adjacent trees + * also catch on fire + * At time 2, the trees adjacent to those catch as well. At time 8, the last + * tree to catch is at + * (0,6). In this example, there is one tree that never burns, so it is not + * included in the time calculation. * * - * @param forest a 2d array where t represents a tree and . represents the ground + * @param forest a 2d array where t represents a tree and . represents the + * ground * @param matchR The row the match is lit at * @param matchC The column the match is lit at * @return the time at which the final tree to be incinerated starts burning @@ -38,6 +47,61 @@ public class Fire { public static int timeToBurn(char[][] forest, int matchR, int matchC) { // HINT: when adding to your BFS queue, you can include more information than // just a location. What other information might be useful? - return -1; + + // base cases + if (forest == null) + throw new NullPointerException("Forest 2D matrix cannot be null!"); + if (forest[matchR][matchC] != 't') + return 0; + + // initializing directions variable for more elegant code + int[][] directions = { + // up, down, right, left + { -1, 0 }, + { 1, 0 }, + { 0, 1 }, + { 0, -1 } + }; + // for readability + int ROW = forest.length; + int COL = forest[0].length; + // initialize visited to avoid looping and Queue structure + boolean[][] visited = new boolean[ROW][COL]; + Queue burningTree = new LinkedList<>(); + + // start position of the fire + // .offer() adds to the queue like .add() but won’t crash if the queue is full + burningTree.offer(new int[] { matchR, matchC, 0 }); + visited[matchR][matchC] = true; + // initialize counter + int maxStepCount = 0; + + // perform BFS to spread fire to all reachable trees + while (!burningTree.isEmpty()) { + int[] current = burningTree.poll(); + int curR = current[0]; + int curC = current[1]; + int time = current[2]; + + // Math.max compare (maxStepCount to current time) + maxStepCount = Math.max(maxStepCount, time); + + // iterate all cardinal directions + for (int[] direction : directions) { + int newR = curR + direction[0]; + int newC = curC + direction[1]; + + // edge cases: check the bounds, visited, treeless cells + if (newR < 0 || newC < 0 || newR >= forest.length || + newC >= forest[0].length || visited[newR][newC] || + forest[newR][newC] == '.') + continue; + + // edge cases passed -> mark visited, add to queue, increment time + visited[newR][newC] = true; + burningTree.offer(new int[] { newR, newC, time + 1 }); + } + } + return maxStepCount; } } \ No newline at end of file diff --git a/src/FireTest.java b/src/FireTest.java index b3b9085..fb790c2 100644 --- a/src/FireTest.java +++ b/src/FireTest.java @@ -3,21 +3,68 @@ import org.junit.jupiter.api.Test; public class FireTest { + @Test public void testTimeToBurnExample() { char[][] forest = { - {'t','.','.','t','t','t','t','.','t'}, - {'.','.','t','t','.','.','.','.','t'}, - {'.','.','t','t','t','t','t','t','t'}, - {'t','t','t','t','.','.','.','.','.'} + { 't', '.', '.', 't', 't', 't', 't', '.', 't' }, + { '.', '.', 't', 't', '.', '.', '.', '.', 't' }, + { '.', '.', 't', 't', 't', 't', 't', 't', 't' }, + { 't', 't', 't', 't', '.', '.', '.', '.', '.' } }; int matchR = 2; int matchC = 6; - int expected = 8; - int actual = Fire.timeToBurn(forest, matchR, matchC); + assertEquals(expected, Fire.timeToBurn(forest, matchR, matchC)); + } + + @Test + public void testSingleTree() { + char[][] forest = { + { 't' } + }; + assertEquals(0, Fire.timeToBurn(forest, 0, 0)); + } + + @Test + public void testNoBurnableTrees() { + char[][] forest = { + { '.', '.', '.' }, + { '.', '.', '.' }, + { '.', '.', '.' } + }; + assertEquals(0, Fire.timeToBurn(forest, 1, 1)); + } - assertEquals(expected, actual); + @Test + public void testAllConnected() { + char[][] forest = { + { 't', 't', 't' }, + { 't', 't', 't' }, + { 't', 't', 't' } + }; + int expected = 2; + assertEquals(expected, Fire.timeToBurn(forest, 1, 1)); + } + + @Test + public void testTreeInCorner() { + char[][] forest = { + { 't', 't', '.' }, + { 't', '.', '.' }, + { '.', '.', '.' } + }; + assertEquals(1, Fire.timeToBurn(forest, 0, 0)); + } + + @Test + public void testIsolatedTreeCluster() { + char[][] forest = { + { 't', '.', 't' }, + { '.', '.', '.' }, + { 't', 't', 't' } + }; + assertEquals(1, Fire.timeToBurn(forest, 2, 1)); } }