Facebook Pixel

458. Poor Pigs

Problem Description

You have a certain number of buckets filled with liquid, and exactly one bucket contains poison. Your goal is to identify which bucket is poisonous using the minimum number of test pigs within a given time limit.

The testing process works as follows:

  1. Test Setup: You have minutesToTest total minutes available for testing, and each test round takes minutesToDie minutes to see results.

  2. Testing Rules:

    • You can select any number of live pigs for each test round
    • Each pig can drink from multiple buckets simultaneously
    • Multiple pigs can drink from the same bucket
    • Drinking takes no time - pigs consume all assigned buckets instantly
  3. Test Results: After waiting minutesToDie minutes:

    • Any pig that drank from the poisonous bucket will die
    • All other pigs survive
    • You cannot feed pigs during the waiting period
  4. Test Rounds: You can repeat this process multiple times as long as you have time remaining (minutesToTest allows for minutesToTest // minutesToDie rounds of testing).

The problem asks you to find the minimum number of pigs needed to guarantee you can identify the poisonous bucket within the time limit.

Example Logic: If you have 4 test rounds available (based on the time constraints), each pig can effectively be in one of 5 states after all rounds: died in round 1, died in round 2, died in round 3, died in round 4, or survived all rounds. This means each pig can distinguish between 5 different outcomes, so n pigs can distinguish between 5^n different buckets.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to think of this problem as an information theory problem. Each pig can provide us with information based on when it dies (or if it survives).

Consider what happens with one pig over the testing period:

  • If we have minutesToTest = 60 and minutesToDie = 15, we can conduct 60 / 15 = 4 test rounds
  • This single pig can die in round 1, round 2, round 3, round 4, or survive all rounds
  • That's 5 different outcomes (4 death times + 1 survival)

This means one pig can distinguish between 5 different buckets by assigning each bucket to a specific outcome. But what if we have more buckets?

With two pigs, we can think of it as a coordinate system:

  • Pig 1 has 5 possible states (dies in rounds 1-4 or survives)
  • Pig 2 also has 5 possible states
  • Combined, they create a 5×5 grid = 25 different combinations
  • Each combination can identify a unique bucket

This extends to multiple dimensions. With n pigs, where each pig has base possible states (number of test rounds + 1), we can identify base^n different buckets.

The formula becomes:

  • base = (minutesToTest // minutesToDie) + 1 (number of possible states per pig)
  • We need base^n >= buckets
  • Solving for n: we need the minimum n such that base^n >= buckets

The solution implements this by starting with p = 1 and repeatedly multiplying by base while counting iterations until p >= buckets. This gives us the minimum number of pigs needed.

Solution Approach

The implementation uses a simple iterative approach to find the minimum number of pigs needed:

  1. Calculate the base (number of states per pig):

    base = minutesToTest // minutesToDie + 1

    This represents how many different states each pig can have (dying in each round or surviving all rounds).

  2. Initialize variables:

    res, p = 0, 1
    • res: counts the number of pigs needed
    • p: tracks the total number of buckets we can test with res pigs (starts at 1)
  3. Iteratively find minimum pigs:

    while p < buckets:
        p *= base
        res += 1
    • Each iteration adds one more pig
    • Multiplying p by base represents adding another dimension to our testing space
    • When p >= buckets, we have enough pigs to test all buckets
  4. Return the result:

    return res

Example walkthrough:

  • If buckets = 25, minutesToDie = 15, minutesToTest = 60
  • base = 60 // 15 + 1 = 5
  • Iteration 1: p = 1 * 5 = 5, res = 1 (1 pig can test 5 buckets)
  • Iteration 2: p = 5 * 5 = 25, res = 2 (2 pigs can test 25 buckets)
  • Since p >= 25, we return res = 2

The algorithm essentially computes ⌈log_base(buckets)⌉, which is the minimum power of base needed to equal or exceed the number of buckets.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with 8 buckets, where minutesToDie = 15 and minutesToTest = 30.

Step 1: Calculate the base (states per pig)

  • We can conduct 30 // 15 = 2 test rounds
  • Each pig has 2 + 1 = 3 possible states:
    • Dies in round 1
    • Dies in round 2
    • Survives both rounds
  • So base = 3

Step 2: Determine minimum pigs needed

Starting with res = 0 (no pigs) and p = 1 (can test 1 bucket):

First iteration:

  • p = 1 < 8, so we need more pigs
  • Add a pig: p = 1 * 3 = 3, res = 1
  • With 1 pig, we can test 3 buckets

Second iteration:

  • p = 3 < 8, still need more pigs
  • Add another pig: p = 3 * 3 = 9, res = 2
  • With 2 pigs, we can test 9 buckets

Since p = 9 >= 8, we have enough capacity. We need 2 pigs.

Step 3: How the actual testing works

With 2 pigs and 8 buckets, we can arrange them in a 3×3 grid (using only 8 of the 9 positions):

        Pig 2 dies    Pig 2 dies    Pig 2
        round 1       round 2       survives
Pig 1   Bucket 1     Bucket 2      Bucket 3
round 1

Pig 1   Bucket 4     Bucket 5      Bucket 6
round 2

Pig 1   Bucket 7     Bucket 8      (unused)
survives

Testing protocol:

  • Round 1: Pig 1 drinks from buckets 1,2,3; Pig 2 drinks from buckets 1,4,7
  • Round 2: Pig 1 drinks from buckets 4,5,6; Pig 2 drinks from buckets 2,5,8

Based on when each pig dies (or survives), we can pinpoint the exact poisonous bucket. For example, if Pig 1 dies in round 2 and Pig 2 survives both rounds, the poison is in Bucket 6.

Solution Implementation

1class Solution:
2    def poorPigs(self, buckets: int, minutesToDie: int, minutesToTest: int) -> int:
3        # Calculate the number of test rounds possible
4        # Each pig can be tested (minutesToTest // minutesToDie) times
5        # Plus 1 for the case where the pig doesn't drink and survives
6        test_rounds = minutesToTest // minutesToDie + 1
7      
8        # Initialize the number of pigs needed
9        num_pigs = 0
10      
11        # Calculate the maximum number of buckets that can be tested
12        # with the current number of pigs
13        max_testable_buckets = 1
14      
15        # Keep adding pigs until we can test all buckets
16        # Each pig can distinguish between 'test_rounds' states
17        # N pigs can distinguish between test_rounds^N buckets
18        while max_testable_buckets < buckets:
19            max_testable_buckets *= test_rounds
20            num_pigs += 1
21      
22        return num_pigs
23
1class Solution {
2    public int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
3        // Calculate the number of testing rounds possible
4        // Each pig can be tested multiple times within the time limit
5        // Plus 1 because a pig can also represent the state of "not dying"
6        int testRounds = minutesToTest / minutesToDie + 1;
7      
8        // Initialize the number of pigs needed
9        int pigsNeeded = 0;
10      
11        // Calculate minimum pigs needed using logarithmic approach
12        // Each pig can distinguish 'testRounds' different states
13        // Total states that can be distinguished = testRounds^pigsNeeded
14        // We need testRounds^pigsNeeded >= buckets
15        for (int maxDistinguishableBuckets = 1; maxDistinguishableBuckets < buckets; maxDistinguishableBuckets *= testRounds) {
16            pigsNeeded++;
17        }
18      
19        return pigsNeeded;
20    }
21}
22
1class Solution {
2public:
3    int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
4        // Calculate the number of test rounds possible
5        // Each pig can be tested multiple times if it survives
6        // +1 because a pig can also represent the state of "not drinking in any round"
7        int testRounds = minutesToTest / minutesToDie + 1;
8      
9        // Calculate minimum number of pigs needed
10        int pigsNeeded = 0;
11      
12        // Each pig can distinguish 'testRounds' number of states
13        // With n pigs, we can distinguish testRounds^n different buckets
14        // We need testRounds^pigsNeeded >= buckets
15        for (int maxDistinguishableBuckets = 1; 
16             maxDistinguishableBuckets < buckets; 
17             maxDistinguishableBuckets *= testRounds) {
18            pigsNeeded++;
19        }
20      
21        return pigsNeeded;
22    }
23};
24
1function poorPigs(buckets: number, minutesToDie: number, minutesToTest: number): number {
2    // Calculate the number of test rounds possible
3    // Each pig can be tested multiple times if it survives
4    // +1 because a pig can also represent the state of "not drinking in any round"
5    const testRounds: number = Math.floor(minutesToTest / minutesToDie) + 1;
6  
7    // Initialize the counter for minimum number of pigs needed
8    let pigsNeeded: number = 0;
9  
10    // Each pig can distinguish 'testRounds' number of states
11    // With n pigs, we can distinguish testRounds^n different buckets
12    // We need testRounds^pigsNeeded >= buckets
13    // Keep multiplying until we can distinguish enough buckets
14    for (let maxDistinguishableBuckets: number = 1; 
15         maxDistinguishableBuckets < buckets; 
16         maxDistinguishableBuckets *= testRounds) {
17        pigsNeeded++;
18    }
19  
20    return pigsNeeded;
21}
22

Time and Space Complexity

Time Complexity: O(log_base(buckets)) where base = minutesToTest // minutesToDie + 1

The algorithm uses a while loop that continues until p >= buckets. In each iteration, p is multiplied by base, starting from 1. The loop terminates when p reaches or exceeds buckets. This means the loop runs approximately log_base(buckets) times, since we're essentially finding the smallest integer res such that base^res >= buckets.

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space. It maintains three variables: base, res, and p, regardless of the input size. No additional data structures like arrays or recursive call stacks are used, making the space complexity constant.

Common Pitfalls

1. Misunderstanding the Base Calculation

Pitfall: A common mistake is calculating the base as minutesToTest // minutesToDie without adding 1, forgetting that a pig surviving all rounds is also a valid state.

Incorrect Implementation:

# Wrong: Missing the "+1" for the survival state
test_rounds = minutesToTest // minutesToDie  # ❌

Why it's wrong: If you have 60 minutes total and each test takes 15 minutes, you can do 4 test rounds. But each pig has 5 possible states:

  • Dies in round 1
  • Dies in round 2
  • Dies in round 3
  • Dies in round 4
  • Survives all rounds (doesn't drink poison)

Solution: Always add 1 to account for the survival state:

test_rounds = minutesToTest // minutesToDie + 1  # ✓

2. Integer Overflow in Large Test Cases

Pitfall: When the number of buckets is very large or the base is small, the multiplication max_testable_buckets *= test_rounds might overflow in some languages (though Python handles big integers automatically).

Solution: Add an early termination check or use logarithms for very large numbers:

import math

def poorPigs(self, buckets: int, minutesToDie: int, minutesToTest: int) -> int:
    if buckets == 1:
        return 0
  
    test_rounds = minutesToTest // minutesToDie + 1
  
    # Using logarithm approach for mathematical precision
    return math.ceil(math.log(buckets) / math.log(test_rounds))

3. Edge Case: Single Bucket

Pitfall: Not handling the edge case where buckets = 1 properly. If there's only one bucket, it must be poisonous, so no pigs are needed.

Solution: Add an explicit check:

def poorPigs(self, buckets: int, minutesToDie: int, minutesToTest: int) -> int:
    if buckets == 1:
        return 0
  
    # Rest of the implementation...

4. Conceptual Misunderstanding: Sequential vs. Parallel Testing

Pitfall: Thinking that pigs must test buckets sequentially or that each pig can only test one bucket per round. This leads to vastly overestimating the number of pigs needed.

Wrong Mental Model: "Each pig tests one bucket at a time"

Correct Understanding: Each pig can drink from multiple buckets in a single round, and the combination of which rounds different pigs die in creates a unique "death pattern" that identifies the poisonous bucket. Think of it as an n-dimensional coordinate system where each pig represents a dimension.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More