题目描述
一个人设定一组四码的数字作为谜底,另一方猜。
每猜一个数,出数者就要根据这个数字给出提示,提示以XAYB形式呈现,直到猜中位置。
其中X表示位置正确的数的个数(数字正确且位置正确),而Y表示数字正确而位置不对的数的个数。
例如,当谜底为8123,而猜谜者猜1052时,出题者必须提示0A2B。
例如,当谜底为5637,而猜谜者才4931时,出题者必须提示1A0B。
当前已知N组猜谜者猜的数字与提示,如果答案确定,请输出答案,不确定则输出NA。
输入描述
第一行输入一个正整数,0<N < 100。
接下来N行,每一行包含一个猜测的数字与提示结果。
输出描述
输出最后的答案,答案不确定则输出NA。
** 示例1
输入
6
4815 1A1B
5716 0A1B
7842 0A1B
4901 0A0B
8585 3A0B
8555 2A1B
输出
3585
思路
- 初始化候选集:生成0000~9999所有4位数字(含前导0),覆盖所有可能谜底;
- 强约束优先剪枝:
- 若存在“4A0B”线索,直接确定谜底为该猜测数,直接输出;
- 若存在“0A0B”线索,排除所有包含该猜测数数字的候选,大幅缩小范围;
- 数字总数粗筛:利用线索“total=A+B”(数字正确总个数),通过统计数字频率计算候选与猜测数的公共数字总数,排除总数不匹配的候选;
- 精准校验A/B:对剩余候选数,逐一计算与所有线索的A(位置+数字均对)和B(数字对、位置错),保留完全匹配的候选;
- 结果判定:有效候选数唯一则输出该数,否则输出NA。
代码
function solution() {
const N = Number(readline());
const records = [];
for (let i = 1; i <= N; i++) {
const [guess, hint] = readline().split(' ');
const a = parseInt(hint[0], 10);
const b = parseInt(hint[2], 10); // 假设hint格式为XAYB,如"1A1B"
records.push({ guess, a, b, total: a + b });
}
// 1. 初始化候选集:0000~9999(补前导0)
let candidates = [];
for (let num = 0; num <= 9999; num++) {
candidates.push(num.toString().padStart(4, '0'));
}
// 2. 利用强约束剪枝(优先处理0A0B和4A0B)
for (const { guess, a, b } of records) {
// 若存在4A0B,直接确定谜底
if (a === 4 && b === 0) {
console.log(guess);
return;
}
// 处理0A0B:排除所有包含guess中数字的候选
if (a === 0 && b === 0) {
const guessDigits = new Set(guess.split(''));
candidates = candidates.filter(secret => {
return !secret.split('').some(d => guessDigits.has(d));
});
if (candidates.length === 0) break; // 提前退出
}
}
// 3. 用剩余记录进一步筛选候选(基于total = a + b)
for (const { guess, total } of records) {
if (total === 0) continue; // 已处理过0A0B
const guessDigits = guess.split('');
const guessCount = getDigitCount(guessDigits);
candidates = candidates.filter(secret => {
const secretDigits = secret.split('');
const secretCount = getDigitCount(secretDigits);
// 计算公共数字总数(不考虑位置)
let common = 0;
for (const d of Object.keys(secretCount)) {
if (guessCount[d]) {
common += Math.min(secretCount[d], guessCount[d]);
}
}
return common === total;
});
if (candidates.length === 0) break;
}
// 4. 验证剩余候选是否符合所有A和B的约束
const valid = [];
for (const secret of candidates) {
let ok = true;
for (const { guess, a, b } of records) {
const [calcA, calcB] = calculateAB(secret, guess);
if (calcA !== a || calcB !== b) {
ok = false;
break;
}
}
if (ok) valid.push(secret);
}
// 输出结果
console.log(valid.length === 1 ? valid[0] : 'NA');
}
// 计算两个数字的A和B
function calculateAB(secret, guess) {
let a = 0;
const sDigits = secret.split(''), gDigits = guess.split('');
// 计算A(位置和数字均正确)
for (let i = 0; i < 4; i++) {
if (sDigits[i] === gDigits[i]) a++;
}
// 计算B(数字正确位置错误)
const sCount = getDigitCount(sDigits.filter((d, i) => d !== gDigits[i]));
const gCount = getDigitCount(gDigits.filter((d, i) => d !== sDigits[i]));
let b = 0;
for (const d of Object.keys(sCount)) {
if (gCount[d]) b += Math.min(sCount[d], gCount[d]);
}
return [a, b];
}
// 统计数字出现次数
function getDigitCount(digits) {
const count = {};
for (const d of digits) {
count[d] = (count[d] || 0) + 1;
}
return count;
}
// 测试用例
const cases = [
`6
4815 1A1B
5716 0A1B
7842 0A1B
4901 0A0B
8585 3A0B
8555 2A1B`
];
let caseIndex = 0, lineIndex = 0;
const readline = (() => {
let lines = [];
return () => {
if (!lineIndex) lines = cases[caseIndex].trim().split('\n').map(l => l.trim());
return lines[lineIndex++];
};
})();
cases.forEach((_, i) => {
caseIndex = i;
lineIndex = 0;
solution();
console.log('-------');
});
1892

被折叠的 条评论
为什么被折叠?



