- package glassball;
- import java.util.List;
- import java.util.ArrayList;
- /**
- * Google interview question
- */
- public class GlassBallPuzzle
- {
- // first index is on glassballs, second is on sum.
- // The sum is used to compare with the total floors of the building and
- // then determine the last element in the previous sum, which is used
- // as the starting point of the floor array.
- private int[][] recursiveSums;
- // the top selection of floors to try. This is not essential, but
- // helpful for testing.
- private int[][] floors;
- private int numOfGlassballs;
- private int numOfFloors;
- public GlassBallPuzzle(int numOfGlassballs, int numOfFloors)
- {
- this.numOfGlassballs = numOfGlassballs;
- this.numOfFloors = numOfFloors;
- generateSumArrays();
- generateFloorArrays();
- }
- public int getMinimalTrials()
- {
- // since all worst paths should have the same number, we pick up any
- // worst path and should have the same number of trials. This path
- // has the broken floor at level 1.
- int len = recursiveSums[numOfGlassballs - 1].length;
- int minimalTrials = recursiveSums[0][len-1] - 1;
- // add trials.
- minimalTrials += getTrials(numOfGlassballs, len);
- return minimalTrials;
- }
- public int[] getOptimalTrialsAndBalls()
- {
- int minimalTrials = numOfFloors;
- int minimalBalls = numOfGlassballs;
- for (int nballs=1; nballs<=numOfGlassballs; nballs++)
- {
- int len = recursiveSums[nballs - 1].length;
- int newTrials = recursiveSums[0][len-1] - 1;
- // add trials.
- newTrials += getTrials(nballs, len);
- if (newTrials < minimalTrials)
- {
- minimalTrials = newTrials;
- minimalBalls = nballs;
- }
- }
- return new int[] { minimalTrials, minimalBalls};
- }
- private int getTrials(int balls, int index)
- {
- // to move across the arrays for balls.
- int ret=0;
- //
- for (int i=0; i<balls; i++)
- {
- // otherwise, there is no need to try.
- if (recursiveSums[i][index-1] < numOfFloors)
- ret++;
- }
- return ret;
- }
- public int[][] getRecursiveSums() { return recursiveSums; }
- private void generateSumArrays()
- {
- recursiveSums = new int[numOfGlassballs][];
- // initialize the first array with 1, 2, 3, 4, ...
- recursiveSums[0] = new int[this.numOfFloors];
- for (int j=0; j<numOfFloors; j++)
- {
- recursiveSums[0][j] = j + 1;
- }
- // now recursively generate the rest of arrays by summation
- for (int i=1; i<numOfGlassballs; i++)
- {
- // since the size is unknown, so we use a list.
- List sums = new ArrayList();
- for (int j=0; j<numOfFloors; j++)
- {
- int total = 0;
- for (int k=0; k<=j; k++) total += recursiveSums[i-1][k];
- sums.add(new Integer(total));
- if (total >= numOfFloors) break;
- }
- recursiveSums[i] = new int[sums.size()];
- for (int j=0; j<sums.size(); j++)
- {
- recursiveSums[i][j] = ((Integer)sums.get(j)).intValue();
- }
- }
- }
- private void generateFloorArrays()
- {
- floors = new int[numOfGlassballs][];
- // If there is one ball, we have to start from floor 1, 2, etc.
- floors[0] = recursiveSums[0];
- // The floors starts from the end of sums series.
- for (int i=1; i<numOfGlassballs; i++)
- {
- floors[i] = new int[recursiveSums[i].length];
- int total=0;
- for (int j=0; j<recursiveSums[i].length; j++)
- {
- total += recursiveSums[i-1][recursiveSums[i].length - 1 - j];
- floors[i][j] = total;
- }
- }
- }
- // could be moved to somewhere else.
- public void printAllResults()
- {
- for (int i=0; i<numOfGlassballs; i++)
- {
- printIntermediateSteps(i);
- }
- }
- private void printIntermediateSteps(int ballIndex)
- {
- System.out.print("sum series for " + (ballIndex + 1) + " balls=");
- int[] segments = recursiveSums[ballIndex];
- for (int i=0; i<segments.length; i++)
- {
- System.out.print(segments[i] + ", ");
- }
- System.out.println();
- System.out.print("floors series for " + (ballIndex + 1) + " balls=");
- segments = floors[ballIndex];
- for (int i=0; i<segments.length; i++)
- {
- System.out.print(segments[i] + ", ");
- }
- System.out.println();
- }
- }
The generateSumArrays() does the forward sum-ups, e.g., (1), (2), ..., this is the main functionality. There is also a floors field that holds the results from backward sum-ups, (A), (B), .... However, this field is not really used in computation, because the
starting point is dynamic, not fixed like in this field. So this is merely for information and debug. A similar method to
compute this backward sum is in another class.
Another functionality of this class is to answer the questions posted in the above Chinese paragraph. For a given height of the build, i.e., the number of floors is fixed, how many balls are optimal, e.g., if we are given a building with 10 floors, 50 balls won't reduce the minimal trials, in fact maybe it takes the same number of trials as 10 balls. These are in the first 3 methods. The basic logic is that since the worst case trials are evenly distributed, we just find one worst path of floors to count the trials.
The test cases is
- package glassball;
- import junit.framework.TestCase;
- /**
- * Testcases for minimal trials.
- */
- public class GlassBallPuzzleTest extends TestCase
- {
- public void test2Balls()
- {
- GlassBallPuzzle puzzle = new GlassBallPuzzle(2, 100);
- puzzle.printAllResults();
- System.out.println("minimal trials=" + puzzle.getMinimalTrials());
- assertTrue(puzzle.getMinimalTrials() == 14);
- }
- public void test3Balls()
- {
- GlassBallPuzzle puzzle = new GlassBallPuzzle(3, 100);
- puzzle.printAllResults();
- System.out.println("minimal trials=" + puzzle.getMinimalTrials());
- assertTrue(puzzle.getMinimalTrials() == 9);
- }
- public void test4Balls()
- {
- GlassBallPuzzle puzzle = new GlassBallPuzzle(4, 100);
- puzzle.printAllResults();
- System.out.println("minimal trials=" + puzzle.getMinimalTrials());
- assertTrue(puzzle.getMinimalTrials() == 8);
- int[] optimals = puzzle.getOptimalTrialsAndBalls();
- System.out.println("optimal trials and balls=" + optimals[0] + ", " + optimals[1]);
- }
- public void test5Balls()
- {
- GlassBallPuzzle puzzle = new GlassBallPuzzle(5, 100);
- puzzle.printAllResults();
- System.out.println("minimal trials=" + puzzle.getMinimalTrials());
- assertTrue(puzzle.getMinimalTrials() == 8);
- }
- public void test6Balls()
- {
- GlassBallPuzzle puzzle = new GlassBallPuzzle(6, 100);
- puzzle.printAllResults();
- System.out.println("minimal trials=" + puzzle.getMinimalTrials());
- assertTrue(puzzle.getMinimalTrials() == 8);
- }
- public void test50Balls()
- {
- GlassBallPuzzle puzzle = new GlassBallPuzzle(50, 200);
- puzzle.printAllResults();
- System.out.println("minimal trials=" + puzzle.getMinimalTrials());
- assertTrue(puzzle.getMinimalTrials() == 20);
- }
- }
The method test4Balls() prints out the optimal number which are verified by test5Balls() and test6Balls().