Facebook Pixel

299. Bulls and Cows

MediumHash TableStringCounting
Leetcode Link

Problem Description

You are playing the Bulls and Cows game with your friend. In this game, you write down a secret number and ask your friend to guess it. When your friend makes a guess, you provide a hint containing two pieces of information:

  • Bulls: The number of digits in the guess that are in the correct position (matching both the digit value and its position in the secret number).
  • Cows: The number of digits in the guess that exist in the secret number but are in the wrong position. These are non-bull digits that could be rearranged to become bulls.

Given two strings secret (your secret number) and guess (your friend's guess), you need to return a hint in the format "xAyB", where:

  • x is the number of bulls
  • y is the number of cows

Note that both secret and guess may contain duplicate digits.

For example:

  • If secret = "1807" and guess = "7810", the hint would be "1A3B":
    • Bull: The digit 8 is in the correct position (index 1)
    • Cows: The digits 1, 0, and 7 exist in the secret but are in wrong positions

The solution works by:

  1. Iterating through both strings simultaneously to count exact matches (bulls)
  2. For non-matching positions, counting the frequency of each digit in both strings
  3. For cows, finding the minimum count of each digit that appears in both non-matching portions
  4. Returning the result in the required format "xAyB"
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is to handle bulls and cows separately since they have different matching criteria.

First, let's think about bulls - these are the easiest to identify. We just need to compare characters at the same positions in both strings. If secret[i] == guess[i], we have a bull.

For cows, the challenge is slightly more complex. We need to find digits that exist in both strings but are in wrong positions. The crucial observation is that we should only consider digits that weren't already counted as bulls.

Consider this example: if secret = "1123" and guess = "1222", the first position gives us a bull (both have 1). For the remaining positions, we need to count how many times each digit appears in the non-bull positions of both strings.

Why use counters? Because a digit can only be matched once. If the secret has two 2s in non-bull positions and the guess has three 2s in non-bull positions, we can only match two of them as cows. This is why we take the minimum count - min(cnt1[digit], cnt2[digit]) - for each digit.

The approach naturally emerges:

  1. First pass: identify all bulls by comparing positions directly
  2. During the same pass: for non-matching positions, collect the digits into separate frequency counters
  3. Second pass: for each unique digit in our counters, the number of cows for that digit is the minimum of its frequency in both counters
  4. Sum up all the cows and format the result

This two-counter approach elegantly handles duplicates and ensures we don't double-count any digit that's already been identified as a bull.

Solution Approach

The implementation uses a counting strategy with two hash maps (Counter objects in Python) to track unmatched digits.

Step 1: Initialize data structures

  • Create two counters: cnt1 for the secret number and cnt2 for the guess
  • Initialize x = 0 to track the number of bulls

Step 2: Single pass through both strings We use zip(secret, guess) to iterate through both strings simultaneously:

for a, b in zip(secret, guess):
    if a == b:
        x += 1  # Found a bull
    else:
        cnt1[a] += 1  # Count this digit in secret
        cnt2[b] += 1  # Count this digit in guess

When digits match at the same position (a == b), we increment the bull counter. Otherwise, we record the unmatched digits in their respective counters. This ensures bulls are excluded from the cow calculation.

Step 3: Calculate cows After processing all positions, we calculate cows by finding overlapping digits in the counters:

y = sum(min(cnt1[c], cnt2[c]) for c in cnt1)

For each digit c that appears in cnt1, we check how many times it also appears in cnt2. The minimum of these two counts gives us the number of cows for that digit. We sum these minimums across all digits.

Why min(cnt1[c], cnt2[c])? If the secret has 2 unmatched 3s and the guess has 3 unmatched 3s, only 2 can be cows (limited by the secret's count).

Step 4: Format the result

return f"{x}A{y}B"

The time complexity is O(n) where n is the length of the strings, as we make one pass through the strings and then iterate through at most 10 unique digits. The space complexity is O(1) since the counters can store at most 10 different digits (0-9).

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 the solution with secret = "1123" and guess = "0111".

Step 1: Initialize

  • x = 0 (bulls counter)
  • cnt1 = {} (for secret's unmatched digits)
  • cnt2 = {} (for guess's unmatched digits)

Step 2: Process each position

Position 0: secret[0] = '1', guess[0] = '0'

  • Not equal → no bull
  • Add to counters: cnt1['1'] = 1, cnt2['0'] = 1

Position 1: secret[1] = '1', guess[1] = '1'

  • Equal → found a bull!
  • x = 1

Position 2: secret[2] = '2', guess[2] = '1'

  • Not equal → no bull
  • Add to counters: cnt1['2'] = 1, cnt2['1'] = 1

Position 3: secret[3] = '3', guess[3] = '1'

  • Not equal → no bull
  • Add to counters: cnt1['3'] = 1, cnt2['1'] = 2

After processing all positions:

  • Bulls: x = 1
  • cnt1 = {'1': 1, '2': 1, '3': 1}
  • cnt2 = {'0': 1, '1': 2}

Step 3: Calculate cows

For each digit in cnt1:

  • Digit '1': min(cnt1['1'], cnt2['1']) = min(1, 2) = 1 cow
  • Digit '2': min(cnt1['2'], cnt2['2']) = min(1, 0) = 0 cows
  • Digit '3': min(cnt1['3'], cnt2['3']) = min(1, 0) = 0 cows

Total cows: y = 1 + 0 + 0 = 1

Step 4: Format result Return "1A1B" (1 bull, 1 cow)

The bull is the '1' at position 1. The cow is the '1' from secret's position 0 that matches with one of the '1's in guess's positions 2 or 3.

Solution Implementation

1from collections import Counter
2from typing import Dict
3
4class Solution:
5    def getHint(self, secret: str, guess: str) -> str:
6        """
7        Calculate bulls and cows for the Bulls and Cows game.
8        Bulls: digits that are correct in both value and position
9        Cows: digits that are correct in value but wrong in position
10      
11        Args:
12            secret: The secret number as a string
13            guess: The guess number as a string
14          
15        Returns:
16            A hint string in format "xAyB" where x is bulls and y is cows
17        """
18        # Initialize counters for unmatched digits in secret and guess
19        secret_counter: Dict[str, int] = Counter()
20        guess_counter: Dict[str, int] = Counter()
21      
22        # Count bulls (exact matches)
23        bulls = 0
24      
25        # Iterate through both strings simultaneously
26        for secret_digit, guess_digit in zip(secret, guess):
27            if secret_digit == guess_digit:
28                # Found a bull (exact match in position and value)
29                bulls += 1
30            else:
31                # Not a bull, store for cow calculation
32                secret_counter[secret_digit] += 1
33                guess_counter[guess_digit] += 1
34      
35        # Calculate cows by finding common unmatched digits
36        # The minimum count ensures we don't over-count
37        cows = sum(min(secret_counter[digit], guess_counter[digit]) 
38                   for digit in secret_counter)
39      
40        # Return result in the required format
41        return f"{bulls}A{cows}B"
42
1class Solution {
2    public String getHint(String secret, String guess) {
3        // Initialize counters for bulls (exact matches) and cows (wrong position matches)
4        int bulls = 0;
5        int cows = 0;
6      
7        // Arrays to count frequency of each digit (0-9) in non-matching positions
8        int[] secretDigitCount = new int[10];
9        int[] guessDigitCount = new int[10];
10      
11        // Iterate through both strings simultaneously
12        for (int i = 0; i < secret.length(); i++) {
13            // Convert characters to integer digits
14            int secretDigit = secret.charAt(i) - '0';
15            int guessDigit = guess.charAt(i) - '0';
16          
17            // Check if digits match at the same position (bull)
18            if (secretDigit == guessDigit) {
19                bulls++;
20            } else {
21                // If not a bull, count the digits for later cow calculation
22                secretDigitCount[secretDigit]++;
23                guessDigitCount[guessDigit]++;
24            }
25        }
26      
27        // Calculate cows by finding minimum occurrences of each digit in both arrays
28        for (int digit = 0; digit < 10; digit++) {
29            cows += Math.min(secretDigitCount[digit], guessDigitCount[digit]);
30        }
31      
32        // Format and return the result as "xAyB"
33        return String.format("%dA%dB", bulls, cows);
34    }
35}
36
1class Solution {
2public:
3    string getHint(string secret, string guess) {
4        // Initialize counters for bulls and cows
5        int bulls = 0;
6        int cows = 0;
7      
8        // Arrays to count frequency of digits (0-9) in non-matching positions
9        int secretDigitCount[10] = {0};
10        int guessDigitCount[10] = {0};
11      
12        // First pass: count bulls and track non-matching digits
13        for (int i = 0; i < secret.size(); ++i) {
14            int secretDigit = secret[i] - '0';
15            int guessDigit = guess[i] - '0';
16          
17            if (secretDigit == guessDigit) {
18                // Exact match (same digit at same position) - it's a bull
19                ++bulls;
20            } else {
21                // Not an exact match - count digits for potential cows
22                ++secretDigitCount[secretDigit];
23                ++guessDigitCount[guessDigit];
24            }
25        }
26      
27        // Second pass: calculate cows from non-matching digits
28        for (int digit = 0; digit < 10; ++digit) {
29            // Cows are the minimum count of each digit in both arrays
30            cows += min(secretDigitCount[digit], guessDigitCount[digit]);
31        }
32      
33        // Format and return the result as "xAyB"
34        return to_string(bulls) + "A" + to_string(cows) + "B";
35    }
36};
37
1/**
2 * Generates a hint for the Bulls and Cows game
3 * @param secret - The secret number as a string
4 * @param guess - The guessed number as a string
5 * @returns A hint in the format "xAyB" where x is bulls (correct digit and position) 
6 *          and y is cows (correct digit but wrong position)
7 */
8function getHint(secret: string, guess: string): string {
9    // Array to count frequency of each digit (0-9) in secret that are not bulls
10    const secretDigitCount: number[] = Array(10).fill(0);
11    // Array to count frequency of each digit (0-9) in guess that are not bulls
12    const guessDigitCount: number[] = Array(10).fill(0);
13  
14    // Count of bulls (correct digit in correct position)
15    let bulls: number = 0;
16  
17    // First pass: identify bulls and count non-bull digits
18    for (let i = 0; i < secret.length; ++i) {
19        if (secret[i] === guess[i]) {
20            // Found a bull (exact match)
21            ++bulls;
22        } else {
23            // Not a bull, increment digit counts for potential cows
24            ++secretDigitCount[+secret[i]];
25            ++guessDigitCount[+guess[i]];
26        }
27    }
28  
29    // Count of cows (correct digit in wrong position)
30    let cows: number = 0;
31  
32    // Second pass: calculate cows by finding minimum overlap of non-bull digits
33    for (let digit = 0; digit < 10; ++digit) {
34        // A digit can only be a cow if it appears in both secret and guess (as non-bulls)
35        cows += Math.min(secretDigitCount[digit], guessDigitCount[digit]);
36    }
37  
38    // Return the hint in "xAyB" format
39    return `${bulls}A${cows}B`;
40}
41

Time and Space Complexity

Time Complexity: O(n), where n is the length of the secret string (or guess string, as they have the same length). The algorithm iterates through both strings once using zip(secret, guess), which takes O(n) time. Then it iterates through the keys in cnt1 to calculate the sum of minimum counts, which takes at most O(10) time since there are only 10 possible digits (0-9). Therefore, the overall time complexity is O(n) + O(10) = O(n).

Space Complexity: O(|Σ|), where |Σ| is the size of the character set. In this problem, since we're dealing with digits only, |Σ| = 10. Both cnt1 and cnt2 are Counter objects that store at most 10 different digits (characters '0' through '9'). Each Counter will use O(10) space in the worst case. Therefore, the total space complexity is O(10) + O(10) = O(10) = O(|Σ|).

Common Pitfalls

Pitfall 1: Counting Bulls as Cows

The Problem: A common mistake is counting digits twice - once as a bull and again as a cow. For example, if secret = "1123" and guess = "1222", the first 1 is a bull. If we don't exclude it from further counting, we might incorrectly count it as a cow when comparing with other positions.

Why It Happens: Developers might try to count bulls and cows in separate passes through the strings, leading to double-counting:

# INCORRECT approach
bulls = sum(1 for i in range(len(secret)) if secret[i] == guess[i])
# Then trying to count cows without excluding bull positions
cows = sum(min(secret.count(d), guess.count(d)) for d in set(guess)) - bulls

The Solution: The provided code correctly handles this by only adding non-matching digits to the counters:

if secret_digit == guess_digit:
    bulls += 1  # Count as bull
else:
    # Only add to counters if NOT a bull
    secret_counter[secret_digit] += 1
    guess_counter[guess_digit] += 1

Pitfall 2: Incorrect Cow Calculation with Duplicates

The Problem: When dealing with duplicate digits, incorrectly calculating how many can be cows. For instance, if secret = "1123" and guess = "0111", there are two 1s in the secret (positions 0 and 1) and three 1s in the guess (positions 1, 2, 3). Position 1 gives us one bull. For cows, we have one remaining 1 in secret and two remaining 1s in guess, so only one can be a cow.

Why It Happens: Simply counting total occurrences without considering bulls first:

# INCORRECT: Doesn't account for bulls properly
total_matches = min(secret.count('1'), guess.count('1'))  # Would give 2
cows = total_matches - bulls  # Could give wrong result

The Solution: By using counters that only track unmatched digits and taking the minimum:

cows = sum(min(secret_counter[digit], guess_counter[digit]) 
           for digit in secret_counter)

This ensures we only count digits that weren't already bulls, and we don't over-count when one string has more of a digit than the other.

Pitfall 3: String Formatting Errors

The Problem: Returning the result in an incorrect format, such as using wrong separators or wrong order.

Common Mistakes:

# Wrong format examples:
return f"{x}Bulls{y}Cows"  # Wrong format
return f"{y}A{x}B"          # Wrong order
return str(x) + "A" + str(y) + "B"  # Works but less clean

The Solution: Use the exact format required:

return f"{bulls}A{cows}B"
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the most two important steps in writing a depth first search function? (Select 2)


Recommended Readings

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

Load More