Taro Logo

Water and Jug Problem

Medium
Asked by:
Profile picture
Profile picture
Profile picture
Profile picture
+2
11 views
Topics:
ArraysDynamic ProgrammingRecursionGreedy AlgorithmsBit Manipulation

You are given two jugs with capacities x liters and y liters. You have an infinite water supply. Return whether the total amount of water in both jugs may reach target using the following operations:

  • Fill either jug completely with water.
  • Completely empty either jug.
  • Pour water from one jug into another until the receiving jug is full, or the transferring jug is empty.

Example 1:

Input: x = 3, y = 5, target = 4

Output: true

Explanation:

Follow these steps to reach a total of 4 liters:

  1. Fill the 5-liter jug (0, 5).
  2. Pour from the 5-liter jug into the 3-liter jug, leaving 2 liters (3, 2).
  3. Empty the 3-liter jug (0, 2).
  4. Transfer the 2 liters from the 5-liter jug to the 3-liter jug (2, 0).
  5. Fill the 5-liter jug again (2, 5).
  6. Pour from the 5-liter jug into the 3-liter jug until the 3-liter jug is full. This leaves 4 liters in the 5-liter jug (3, 4).
  7. Empty the 3-liter jug. Now, you have exactly 4 liters in the 5-liter jug (0, 4).

Reference: The Die Hard example.

Example 2:

Input: x = 2, y = 6, target = 5

Output: false

Example 3:

Input: x = 1, y = 2, target = 3

Output: true

Explanation: Fill both jugs. The total amount of water in both jugs is equal to 3 now.

Constraints:

  • 1 <= x, y, target <= 103

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the possible ranges for jug1Capacity, jug2Capacity, and targetCapacity? Are they all non-negative integers?
  2. If targetCapacity is zero, should I return true if both jugs are initially empty, or false?
  3. If targetCapacity is greater than the sum of jug1Capacity and jug2Capacity, should I return false immediately?
  4. Is it possible that jug1Capacity or jug2Capacity could be zero?
  5. If there are multiple ways to reach the targetCapacity, do I need to find the most efficient way, or is any valid solution acceptable?

Brute Force Solution

Approach

The brute force approach to the Water and Jug problem is like trying every single combination of filling, emptying, and pouring water between the jugs. We essentially simulate all possible actions until we find a combination that gives us the target amount of water in one of the jugs.

Here's how the algorithm would work step-by-step:

  1. Start with both jugs empty.
  2. Consider all possible actions: filling jug A completely, filling jug B completely, emptying jug A, emptying jug B, pouring from jug A to jug B until jug B is full or jug A is empty, and pouring from jug B to jug A until jug A is full or jug B is empty.
  3. After each action, check if either jug contains the target amount of water. If so, we've found a solution.
  4. If not, repeat step 2 from the new state (the new amounts of water in each jug), exploring all possible actions from there.
  5. Keep track of all the states (combinations of water levels in the jugs) you've visited to avoid repeating the same actions over and over.
  6. Continue exploring possibilities until either you find a combination that gives the target amount, or you've explored every single possible combination of actions without finding a solution, in which case there is no solution.

Code Implementation

def can_measure_water_brute_force(jug_a_capacity, jug_b_capacity, target_capacity):
    visited_states = set()
    queue = [(0, 0)]

    while queue:
        current_state = queue.pop(0)
        jug_a_amount, jug_b_amount = current_state

        # Check if we've reached the target
        if jug_a_amount == target_capacity or jug_b_amount == target_capacity or jug_a_amount + jug_b_amount == target_capacity:
            return True

        # Avoid infinite loops by tracking states
        if current_state in visited_states:
            continue

        visited_states.add(current_state)

        # Generate all possible next states
        next_states = [
            (jug_a_capacity, jug_b_amount),  # Fill jug A
            (jug_a_amount, jug_b_capacity),  # Fill jug B
            (0, jug_b_amount),              # Empty jug A
            (jug_a_amount, 0),              # Empty jug B
        ]

        # Pour jug A to jug B
        pour_amount = min(jug_a_amount, jug_b_capacity - jug_b_amount)
        next_states.append((jug_a_amount - pour_amount, jug_b_amount + pour_amount))

        # Pour jug B to jug A
        pour_amount = min(jug_b_amount, jug_a_capacity - jug_a_amount)
        next_states.append((jug_a_amount + pour_amount, jug_b_amount - pour_amount))

        # Add the next states to the queue for processing
        queue.extend(next_states)

    return False

Big(O) Analysis

Time Complexity
O(capacityA * capacityB)The brute force approach explores all possible states of water levels in the two jugs. Since jug A can hold a maximum of capacityA units of water and jug B can hold a maximum of capacityB units, there are at most (capacityA + 1) * (capacityB + 1) possible states. The algorithm essentially performs a breadth-first search or depth-first search through these states. In the worst case, it might visit all possible states before finding the target or determining that it's impossible. Therefore, the time complexity is proportional to the total number of possible states, which is O(capacityA * capacityB).
Space Complexity
O(capacityA * capacityB)The algorithm keeps track of all visited states to avoid redundant calculations. Each state is represented by a unique combination of water levels in jug A and jug B. The maximum number of such states is the product of the capacities of the two jugs since jug A can have values from 0 to capacityA and jug B can have values from 0 to capacityB. Therefore, the space complexity is proportional to capacityA * capacityB, which we denote as O(capacityA * capacityB).

Optimal Solution

Approach

The most efficient way to solve the water and jug problem isn't about trying random pours. It's about understanding the mathematical relationship between the jug sizes and whether you can actually measure the target amount of water. We determine if a solution is even possible, and then find a way to achieve the solution through a combination of filling, emptying, and transferring water between the jugs.

Here's how the algorithm would work step-by-step:

  1. First, figure out if it's even possible to measure the target amount. You can only measure amounts that are multiples of the greatest common divisor (GCD) of the two jug sizes. For instance, if the jugs are sizes 3 and 5, and the target is 4, first find the GCD of 3 and 5, which is 1. Since 4 is a multiple of 1, the target is achievable.
  2. If it's not possible according to the GCD rule, stop, because there's no solution.
  3. If it is possible, then start by emptying one of the jugs.
  4. Now, fill one of the jugs completely.
  5. Pour water from the full jug into the empty one until the empty jug is full or the full jug is empty.
  6. If you've reached the target amount in either jug, you're done.
  7. If not, empty one of the jugs again, or fill the empty one from the other, repeating the transfer and empty/fill steps until you reach your goal amount.
  8. The key is to alternate between filling a jug, pouring into the other, and emptying a jug as needed to reach the final target.

Code Implementation

def can_measure(jug1_capacity, jug2_capacity, target_capacity):

    def gcd(a, b):
        while b:
            a, b = b, a % b
        return a

    # Impossible if target exceeds combined capacity.
    if target_capacity > jug1_capacity + jug2_capacity:
        return False

    # Check if target is multiple of GCD.
    greatest_common_divisor = gcd(jug1_capacity, jug2_capacity)

    if target_capacity % greatest_common_divisor != 0:
        return False

    # At this point the target is potentially achievable.
    return True

Big(O) Analysis

Time Complexity
O(log(min(jug1Capacity, jug2Capacity)))The time complexity is dominated by the Euclidean algorithm used to calculate the greatest common divisor (GCD) of the two jug capacities. The Euclidean algorithm repeatedly applies the modulo operation until the remainder is zero. The number of steps required is logarithmic with respect to the smaller of the two jug capacities because, in each step, the numbers are reduced by at least half. The subsequent steps involving filling, emptying, and transferring water are bounded by the number of steps required to compute the GCD, as the target is reachable only if it is a multiple of GCD, thus relating these operations to GCD computation.
Space Complexity
O(1)The provided algorithm primarily relies on arithmetic operations and comparisons related to the jug sizes and target amount. It doesn't explicitly create any auxiliary data structures like arrays, lists, or hash maps to store intermediate states or track visited configurations. The operations involve filling, emptying, and transferring water, but these actions are conceptual and don't translate into auxiliary memory allocation. Therefore, the space complexity remains constant, independent of the jug sizes or target amount, resulting in O(1) space complexity.

Edge Cases

CaseHow to Handle
targetCapacity is greater than the sum of jug1Capacity and jug2CapacityReturn false immediately since it's impossible to measure more than the total capacity of both jugs.
jug1Capacity or jug2Capacity is zero, and targetCapacity is greater than the other jug's capacityReturn false because it is impossible to measure more than the other jug's capacity when one jug has zero capacity.
jug1Capacity, jug2Capacity, and targetCapacity are all zeroReturn true, as the target of 0 is achievable with empty jugs.
jug1Capacity or jug2Capacity is zero, and targetCapacity is a multiple of the other jug's capacityCheck if targetCapacity is 0 or a multiple of the non-zero jug's capacity; if so return true, otherwise false.
jug1Capacity equals jug2Capacity, and targetCapacity is not a multiple of jug1CapacityReturn false if targetCapacity is not a multiple of the equal jug capacities since the gcd will be the jug capacity, which must divide the target.
targetCapacity is equal to either jug1Capacity or jug2CapacityReturn true immediately, as the target is directly achievable.
jug1Capacity and jug2Capacity are relatively prime (GCD is 1)If the target capacity is less than or equal to the sum of the two jugs, return true, as any amount up to the combined capacity is achievable.
Integer overflow can occur when calculating intermediate water amounts (e.g., pouring)Use appropriate data types (e.g., long) to avoid overflow issues during calculations or use iterative approach to prevent stack overflow due to recursion.