299. Bulls and Cows
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 bullsy
is the number of cows
Note that both secret
and guess
may contain duplicate digits.
For example:
- If
secret = "1807"
andguess = "7810"
, the hint would be"1A3B"
:- Bull: The digit
8
is in the correct position (index 1) - Cows: The digits
1
,0
, and7
exist in the secret but are in wrong positions
- Bull: The digit
The solution works by:
- Iterating through both strings simultaneously to count exact matches (bulls)
- For non-matching positions, counting the frequency of each digit in both strings
- For cows, finding the minimum count of each digit that appears in both non-matching portions
- Returning the result in the required format
"xAyB"
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 2
s in non-bull positions and the guess has three 2
s 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:
- First pass: identify all bulls by comparing positions directly
- During the same pass: for non-matching positions, collect the digits into separate frequency counters
- 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
- 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 andcnt2
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 3
s and the guess has 3 unmatched 3
s, 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 EvaluatorExample 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 1
s in the secret (positions 0 and 1) and three 1
s 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 1
s 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"
What are the most two important steps in writing a depth first search function? (Select 2)
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!