【OD机试题笔记】猜数字

题目描述

一个人设定一组四码的数字作为谜底,另一方猜。

每猜一个数,出数者就要根据这个数字给出提示,提示以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

思路

  1. 初始化候选集:生成0000~9999所有4位数字(含前导0),覆盖所有可能谜底;
  2. 强约束优先剪枝:
    • 若存在“4A0B”线索,直接确定谜底为该猜测数,直接输出;
    • 若存在“0A0B”线索,排除所有包含该猜测数数字的候选,大幅缩小范围;
  3. 数字总数粗筛:利用线索“total=A+B”(数字正确总个数),通过统计数字频率计算候选与猜测数的公共数字总数,排除总数不匹配的候选;
  4. 精准校验A/B:对剩余候选数,逐一计算与所有线索的A(位置+数字均对)和B(数字对、位置错),保留完全匹配的候选;
  5. 结果判定:有效候选数唯一则输出该数,否则输出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('-------');
});

### 华为 OD 试题及解答 #### Java 题目解析 在华为 OD 考中,Java 是一种常见的编程语言。以下是一个关于二进制位操作的经典题目及其解决方案。 给定一个整数 `num`,计算其二进制表示中有多少个 `1` 的方法可以通过逐位检查实现[^2]。 以下是具体的代码示例: ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int num = in.nextInt(); // 输入数字 int count = 0; // 计数器初始化 for (int i = 0; i < 32; i++) { // 假设输入的是标准的 32 位整数 if ((num & 1) == 1) { // 如果当前最低位是 1,则增加计数 count++; } num = num >>> 1; // 将数字无符号右移一位 } System.out.println(count); // 输出最终的结果 } } ``` 此程序的核心在于利用按位与运算符 (`&`) 和无符号右移运算符 (`>>>`) 来逐步提取并统计二进制中的每一位是否为 `1`。 --- #### 字符串处理题目解析 另一个常见问题是字符串的操作,比如将字符串中的每个单词首字母大写化[^3]。下面是一段 Python 实现的例子: ```python while True: try: words = input().split() # 获取用户输入并分割成列表 result = [] for word in words: if len(word) > 0: # 确保单词长度大于零 capitalized_word = f"{word[0].upper()}{word[1:]}" result.append(capitalized_word) print(" ".join(result)) # 打印结果 except Exception as e: break # 捕获异常后退出循环 ``` 该脚本通过遍历每一个单词,并将其第一个字符转为大写字母来完成任务。 --- #### 准备建议 为了更好地应对华为 OD 试,可以采取如下策略: - **熟悉常用算法**:掌握基本的数据结构(数组、链表、堆栈等)以及经典算法(排序、查找等),这些知识点经常会在实际测试中被考察到。 - **多练习编码能力**:针对不同类型的题目进行专项训练,尤其是时间复杂度优化方面的技巧学习。 - **理解业务场景需求**:部分试题可能结合具体应用场景设计,因此除了技术层面外还需要考虑逻辑思维的应用。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值