The "Bulls and Cows" problem is a classic code-breaking game. You are given two strings of equal length: secret
and guess
. Both strings consist of digits only. Your task is to determine:
secret
and guess
.
secret
and guess
, but in different positions.
Each digit from secret
and guess
can only be counted once as either a bull or a cow. Digits cannot be reused for multiple matches.
The final answer should be returned as a string in the format "xAyB"
, where x
is the number of bulls and y
is the number of cows.
Constraints:
secret
and guess
have the same length.secret
and guess
is in the range '0' to '9'.At first glance, this problem looks like a simple comparison between two strings. The challenge, however, is accurately counting bulls and cows without double-counting any digit.
The brute-force approach would involve checking every digit in guess
against every digit in secret
, but this would be inefficient and error-prone, especially with repeated digits.
To optimize, we can:
secret
and guess
.Let's break down the solution step by step:
bulls = 0
and cows = 0
.secret
and guess
.i
in secret
and guess
.secret[i] == guess[i]
, increment bulls
. These are exact matches.secret[i]
and guess[i]
in their respective counters.secret
and guess
(from the unmatched digits)."{bulls}A{cows}B"
.We use arrays (or hash maps) for digit counts because they allow O(1) lookup and update, which is efficient given the digit range.
Example: secret = "1807"
, guess = "7810"
1
vs 7
— not a bull; increment counts.8
vs 8
— bull! (bulls = 1
).0
vs 1
— not a bull; increment counts.7
vs 0
— not a bull; increment counts.
After this pass: bulls = 1
.
Unmatched counts:
secret
: 1 (once), 0 (once), 7 (once)guess
: 7 (once), 1 (once), 0 (once)"1A3B"
Brute-force Approach:
guess
could be compared with every digit in secret
.The optimized approach is efficient and scalable, as it uses constant space and runs in linear time.
The Bulls and Cows problem challenges us to carefully count exact and partial matches between two digit strings, without reusing any digit. By separating the process into two passes—first for bulls, then for cows—and using frequency counters, we create a solution that is both simple and efficient. This approach highlights the power of breaking a problem into manageable steps and using the right data structure for the task.
class Solution:
def getHint(self, secret: str, guess: str) -> str:
bulls = 0
cows = 0
from collections import Counter
secret_count = [0] * 10
guess_count = [0] * 10
for s, g in zip(secret, guess):
if s == g:
bulls += 1
else:
secret_count[int(s)] += 1
guess_count[int(g)] += 1
for i in range(10):
cows += min(secret_count[i], guess_count[i])
return f"{bulls}A{cows}B"
class Solution {
public:
string getHint(string secret, string guess) {
int bulls = 0, cows = 0;
int secret_count[10] = {0}, guess_count[10] = {0};
for (int i = 0; i < secret.size(); ++i) {
if (secret[i] == guess[i]) {
++bulls;
} else {
++secret_count[secret[i] - '0'];
++guess_count[guess[i] - '0'];
}
}
for (int i = 0; i < 10; ++i) {
cows += min(secret_count[i], guess_count[i]);
}
return to_string(bulls) + "A" + to_string(cows) + "B";
}
};
class Solution {
public String getHint(String secret, String guess) {
int bulls = 0, cows = 0;
int[] secretCount = new int[10];
int[] guessCount = new int[10];
for (int i = 0; i < secret.length(); i++) {
if (secret.charAt(i) == guess.charAt(i)) {
bulls++;
} else {
secretCount[secret.charAt(i) - '0']++;
guessCount[guess.charAt(i) - '0']++;
}
}
for (int i = 0; i < 10; i++) {
cows += Math.min(secretCount[i], guessCount[i]);
}
return bulls + "A" + cows + "B";
}
}
var getHint = function(secret, guess) {
let bulls = 0, cows = 0;
let secretCount = Array(10).fill(0);
let guessCount = Array(10).fill(0);
for (let i = 0; i < secret.length; i++) {
if (secret[i] === guess[i]) {
bulls++;
} else {
secretCount[+secret[i]]++;
guessCount[+guess[i]]++;
}
}
for (let i = 0; i < 10; i++) {
cows += Math.min(secretCount[i], guessCount[i]);
}
return `${bulls}A${cows}B`;
};