Graph · 34 of 43
Water Jug problem using BFS
With a 4 cup jug and a 3 cup jug and a tap, find the fewest fills, empties and pours that leave exactly 2 cups in one jug.
- Simple way
- time O(6^(A * B))
room O(A * B) - Clever way
- time O(A * B)
room O(A * B)
The problem, simply
You have two jugs with no marks on them. One holds A cups, the other holds B cups. You may do three kinds of moves: fill a jug to the top from the tap, empty a jug onto the ground, or pour one jug into the other until the first is empty or the second is full.
Give back the fewest moves that leave exactly C cups in either jug, or -1 if it can never happen.
A story to remember it
Picture it
Part of the map for jugs of 4 and 3 cups. Each dot is (big jug, small jug). Arrows show the move that first reaches each spot. The badges show how many moves that took. (4,2) holds 2 cups after 4 moves.
- looking now
- answer
Examples
Jugs of 4 and 3, want 2
- In
A = 4, B = 3, C = 2- Out
4
Fill the 3, pour it into the 4, fill the 3 again, pour into the 4 until it is full. The 3 cup jug now holds 2.
The movie puzzle
- In
A = 3, B = 5, C = 4- Out
6
Fill the 5, pour into the 3 (2 left), empty the 3, pour the 2 in, fill the 5, top up the 3. The 5 cup jug holds 4.
Never possible
- In
A = 2, B = 6, C = 5- Out
-1
Every amount you can ever make is a multiple of 2, because both jugs are. 5 is odd.
Too big
- In
A = 1, B = 2, C = 3- Out
-1
Neither jug can hold 3 cups at all.
Way 1: the simple way
Try every sequence of moves, depth first, like a kid trying random buttons and backing up.
- From the current pair, try all six moves.
- Do not revisit a pair already on the current path, or you could loop forever.
- When a jug holds
C, keep the number of moves if it is the best so far. - Stop a path once it can no longer beat the best.
class JugsDFS {
static int best;
public static int minSteps(int capA, int capB, int target) {
best = Integer.MAX_VALUE;
go(0, 0, capA, capB, target, 0, new boolean[capA + 1][capB + 1]);
return best == Integer.MAX_VALUE ? -1 : best;
}
static void go(int a, int b, int capA, int capB, int target, int steps, boolean[][] onPath) {
if (a == target || b == target) { best = Math.min(best, steps); return; }
if (steps + 1 >= best || onPath[a][b]) return;
onPath[a][b] = true;
int pourAB = Math.min(a, capB - b), pourBA = Math.min(b, capA - a);
int[][] moves = {
{capA, b}, {a, capB}, // fill a jug
{0, b}, {a, 0}, // empty a jug
{a - pourAB, b + pourAB}, // pour A into B
{a + pourBA, b - pourBA} // pour B into A
};
for (int[] m : moves) go(m[0], m[1], capA, capB, target, steps + 1, onPath);
onPath[a][b] = false; // free this pair for other paths
}
}How long it takes: the path marks are removed when backing up, so the same pair is explored again and again along different paths. With up to (A + 1) * (B + 1) pairs and 6 moves each, the worst case is exponential, roughly O(6^(A * B)). Extra room: the marks and the recursion, O(A * B).
Way 2: the clever way
Use the ripples. Every move costs 1, so breadth first search over the pairs finds the fewest moves. Each pair is visited once, ever.
- If
Cis 0, answer 0. IfCis bigger than both jugs, answer-1. - Put (0, 0) in a waiting line (a queue) with 0 moves.
- Take a pair from the front. Make all six moves. For each new pair you have not seen: write down its move count (one more), and if it holds
Cin either jug, that count is the answer. Otherwise add it to the line. - If the line empties, answer
-1.
Ripples spread over the map of jug pairs
01 / 07
Start with both jugs empty, (0,0), after 0 moves. It is the only pair in the waiting line.
- looking now
- seen
- answer
- path
- just added
- ignored
class JugsBFS {
public static int minSteps(int capA, int capB, int target) {
if (target == 0) return 0;
if (target > Math.max(capA, capB)) return -1; // no jug is big enough
int[][] steps = new int[capA + 1][capB + 1];
for (int[] row : steps) Arrays.fill(row, -1);
steps[0][0] = 0;
Queue<int[]> line = new ArrayDeque<>();
line.add(new int[]{0, 0});
while (!line.isEmpty()) {
int[] s = line.poll();
int a = s[0], b = s[1];
int pourAB = Math.min(a, capB - b), pourBA = Math.min(b, capA - a);
int[][] moves = {
{capA, b}, {a, capB}, // fill a jug
{0, b}, {a, 0}, // empty a jug
{a - pourAB, b + pourAB}, // pour A into B until A is empty or B is full
{a + pourBA, b - pourBA} // pour B into A
};
for (int[] m : moves) {
if (steps[m[0]][m[1]] != -1) continue; // seen this pair already
steps[m[0]][m[1]] = steps[a][b] + 1;
if (m[0] == target || m[1] == target) return steps[m[0]][m[1]];
line.add(m);
}
}
return -1; // the ripples never reached C
}
}How long it takes: there are at most (A + 1) * (B + 1) pairs, each handled once with 6 moves, O(A * B). Extra room: the step table and the line, O(A * B).
Watch out for
- Mark a pair as seen when it joins the line, not when it leaves, so it never enters twice.
- Some versions only count
Cin a particular jug, or allowCas the total of both jugs. Read the question and change the target test. - A string key like
"3,0"in a hash set works but is slow. A 2D array indexed by the two amounts is faster. - Huge jugs (say a billion cups) make the table too big. Use the math shortcut below.
What interviewers ask next
- When is it possible at all? Exactly when
C <= max(A, B)andCis a multiple ofgcd(A, B). This comes from Bezout's identity: every reachable amount is a mix of whole jugs. - Fewest moves for huge jugs? Only two strategies can be best: always fill
Aand pour intoB, or always fillBand pour intoA. Simulate both (each takes at most aboutA + Bmoves) and take the smaller count. - Three jugs, with a fixed amount of water and no tap (the classic 8, 5, 3 puzzle)? The same search over triples of amounts works.
- Print the moves, not just the count. Store which pair and which move first reached each pair, then walk back.
Remember this
Grown-up words: the pairs of amounts are states, the moves are edges, the map is the state graph, the ripples are BFS levels, the waiting line is a queue, and the "multiple of gcd" rule is Bezout's identity.