题目描述

✅ 843. 猜猜这个单词

image-20260929104947719

image-20260929104947892

题意分析

列表中的单词互不相同且长度都为 6,秘密词一定在其中。每次必须用原列表中的词调用 Master.guess,返回与秘密词在相同位置相同字符的数量;返回 6 才算实际猜中。每个用例的 allowedGuesses 在 10 到 30 之间,必须在对应额度内命中。

解法:候选分桶 + 反馈筛选

核心思路

[!blue]

用 cand 保存与此前所有反馈一致、仍可能是秘密词的单词,初始为完整列表。若本轮猜 g 得到反馈 m,真实答案一定满足 matchCount(g, w) = m,所以只保留这一类候选。旧集合已经符合过去的反馈,再与本次条件取交集,就能持续缩小范围而不误删真实答案。

还需要决定猜哪个词。对于候选 g,把所有当前候选按它们与 g 的位置匹配数 0..6 分成七个桶。实际反馈会指出秘密词属于哪个桶,未命中时就只能留下相应桶;因此用最大桶大小衡量这次猜测最坏会留下多少可能性,再选择这个数最小的词。

代码只从当前候选中选择猜词,所以猜测始终属于原列表,也保留直接命中的机会。自匹配结果为 6,桶数组必须有七项。若反馈不是 6,刚猜过的词就不会通过精确筛选,候选数至少减少一;若反馈为 6,立即结束。即使只剩一个候选,也必须实际调用查询,不能只推断出答案就返回。

精确筛选的正确性与选词策略的效果需要区分。最大桶最小只优化本轮的最坏剩余规模,不是在搜索完整的最优决策树,也不能保证每轮减半。题目接口没有传入查询额度,代码不写死十轮,而是继续筛选直到命中;这仍需依赖策略在用例额度内猜中,不能将候选最终会减少完等同于查询次数一定合格。

解题步骤

  1. 用全部单词初始化候选集合。
  2. 对每个可猜词统计七个反馈桶,选择最大桶最小者。
  3. 调用查询,返回六则立即结束。
  4. 仅保留与本轮反馈一致的词,继续选词。

代码实现

class Solution {
    public void findSecretWord(String[] wordlist, Master master) {
        List<String> cand = new ArrayList<>(Arrays.asList(wordlist));

        while (!cand.isEmpty()) {
            String guess = pickGuess(cand);
            int match = master.guess(guess);

            // 查询返回六才表示实际猜中,立即结束交互。
            if (match == 6) {
                return;
            }

            List<String> next = new ArrayList<>();

            for (String w : cand) {
                // 只保留与本轮实际反馈完全一致的候选,真实答案不会被排除。
                if (matchCount(guess, w) == match) {
                    next.add(w);
                }
            }

            cand = next;
        }
    }

    private String pickGuess(List<String> cand) {
        int bestScore = Integer.MAX_VALUE;
        String best = cand.get(0);

        for (String g : cand) {
            // 反馈范围包含零到六,自匹配的六也需要一个桶。
            int[] buckets = new int[7];

            for (String w : cand) {
                int m = matchCount(g, w);

                buckets[m]++;
            }

            int score = 0;

            for (int x : buckets) {
                score = Math.max(score, x);
            }

            // 选择最大反馈桶最小的候选,优化本轮最坏剩余规模。
            if (score < bestScore) {
                bestScore = score;
                best = g;
            }
        }

        return best;
    }

    private int matchCount(String a, String b) {
        int cnt = 0;

        for (int i = 0; i < a.length(); i++) {
            if (a.charAt(i) == b.charAt(i)) {
                cnt++;
            }
        }

        return cnt;
    }
}
func findSecretWord(wordlist []string, master *Master) {
    cand := make([]string, 0, len(wordlist))
    cand = append(cand, wordlist...)

    for len(cand) > 0 {
        guess := pickGuess(cand)
        match := master.Guess(guess)
        // 查询返回六才表示实际猜中,立即结束交互。
        if match == 6 {
            return
        }

        next := make([]string, 0, len(cand))
        for _, w := range cand {
            // 只保留与本轮实际反馈完全一致的候选,真实答案不会被排除。
            if matchCount(guess, w) == match {
                next = append(next, w)
            }
        }
        cand = next
    }
}

func pickGuess(cand []string) string {
    bestScore := 1<<31 - 1
    best := cand[0]

    for _, g := range cand {
        // 反馈范围包含零到六,自匹配的六也需要一个桶。
        buckets := make([]int, 7)
        for _, w := range cand {
            buckets[matchCount(g, w)]++
        }
        score := 0
        for _, x := range buckets {
            if x > score {
                score = x
            }
        }
        // 选择最大反馈桶最小的候选,优化本轮最坏剩余规模。
        if score < bestScore {
            bestScore = score
            best = g
        }
    }
    return best
}

func matchCount(a, b string) int {
    cnt := 0
    for i := 0; i < len(a); i++ {
        if a[i] == b[i] {
            cnt++
        }
    }
    return cnt
}

复杂度分析

  • 时间复杂度:设实际查询轮数为 Q、最多候选数为 N、词长为 L,时间上界 $O(QN^2L)$;本题 L 固定为六。
  • 空间复杂度:$O(N)$,前后候选列表及固定七桶。

关键点总结

[!green]

  • 匹配按相同位置计算,不是字符集合交集。
  • 每轮候选都要满足此前全部反馈。
  • 返回六表示真正命中,不能只推断候选唯一而不调用查询。

易错点总结

[!yellow]

  • 反馈过滤使用大于等于:保留与真实结果矛盾的词,可能重复猜测。
  • 桶数组只开六项:自匹配结果六会越界。
  • 只比较包含哪些字母:相同字母位于不同位置时不能算匹配。
  • 把最大桶最小误当作每轮减半保证:局部策略没有这样的固定收缩比例。

相似题目

题目 难度 关联与区别
299. 猜数字游戏 中等 同样用位置匹配数衡量猜测,本题只返回位置命中数,并据此排除不可能的秘密词。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2022/55973568
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!