Hard
题目描述
给定一个由唯一字符串组成的数组 words,其中 words[i] 是长度为六的字符串。words 中的一个单词被选作秘密单词。
你还会得到一个辅助对象 Master。你可以调用 Master.guess(word),其中 word 是一个长度为六的字符串,并且必须是来自 words 的。Master.guess(word) 返回:
- 如果
word不在words中,返回-1 - 一个整数,表示你的猜测与秘密单词的精确匹配数(值和位置都匹配)
每个测试用例都有一个参数 allowedGuesses,表示你最多可以调用 Master.guess(word) 的次数。
对于每个测试用例,你应该在不超过最大允许猜测次数的情况下调用 Master.guess 猜出秘密单词。你会得到:
- 如果你调用
Master.guess的次数超过allowedGuesses次或者你没有猜出秘密单词,返回 “Either you took too many guesses, or you did not find the secret word.” - 如果你在调用
Master.guess次数小于或等于allowedGuesses的情况下猜出了秘密单词,返回 “You guessed the secret word correctly.”
测试用例保证你可以用合理的策略(而不是暴力方法)猜出秘密单词。
示例 1:
输入:secret = "acckzz", words = ["acckzz","ccbazz","eiowzz","abcczz"], allowedGuesses = 10
输出:You guessed the secret word correctly.
解释:
master.guess("aaaaaa") 返回 -1,因为 "aaaaaa" 不在 words 中。
master.guess("acckzz") 返回 6,因为 "acckzz" 是秘密单词,有 6 个匹配。
master.guess("ccbazz") 返回 3,因为 "ccbazz" 有 3 个匹配。
master.guess("eiowzz") 返回 2,因为 "eiowzz" 有 2 个匹配。
master.guess("abcczz") 返回 4,因为 "abcczz" 有 4 个匹配。
我们调用了 5 次 master.guess,其中一次是秘密单词,所以通过测试用例。
示例 2:
输入:secret = "hamada", words = ["hamada","khaled"], allowedGuesses = 10
输出:You guessed the secret word correctly.
解释:因为有两个单词,你可以猜测两个。
提示:
1 <= words.length <= 100words[i].length == 6words[i]由小写英文字母组成words的所有字符串都是唯一的secret存在于words中10 <= allowedGuesses <= 30
解题思路
这是一道交互式题目,需要设计策略在有限次数内猜出秘密单词。关键思路是利用每次猜测的返回值来缩小候选范围。
核心策略:
- 随机选择策略:从当前候选单词中随机选择一个进行猜测
- 根据匹配数过滤:如果猜测单词与秘密单词有 k 个位置匹配,那么真正的秘密单词与这个猜测单词也必须有 k 个位置匹配
- 缩小候选范围:每次猜测后,保留那些与猜测单词有相同匹配数的候选单词
优化策略:
- 最小化最坏情况:选择能使候选集合分割最均匀的单词
- 避免相似单词:优先选择与已有候选单词差异较大的单词
算法流程:
- 维护一个候选单词列表,初始为所有单词
- 从候选列表中选择一个单词进行猜测
- 根据返回的匹配数,过滤掉不符合条件的候选单词
- 重复步骤2-3,直到找到秘密单词或候选列表为空
这种策略的关键在于每次猜测都能显著减少候选范围,通常在10次以内就能找到答案。
代码实现
class Solution {
public:
int getMatches(const string& word1, const string& word2) {
int matches = 0;
for (int i = 0; i < 6; i++) {
if (word1[i] == word2[i]) {
matches++;
}
}
return matches;
}
void findSecretWord(vector<string>& words, Master& master) {
vector<string> candidates = words;
for (int attempt = 0; attempt < 10 && !candidates.empty(); attempt++) {
// 随机选择一个候选单词
int idx = rand() % candidates.size();
string guess = candidates[idx];
int matches = master.guess(guess);
// 如果找到秘密单词,直接返回
if (matches == 6) {
return;
}
// 过滤候选单词,保留与guess有相同匹配数的单词
vector<string> newCandidates;
for (const string& word : candidates) {
if (getMatches(word, guess) == matches) {
newCandidates.push_back(word);
}
}
candidates = newCandidates;
}
}
};
class Solution:
def findSecretWord(self, words: List[str], master: 'Master') -> None:
import random
def get_matches(word1, word2):
return sum(c1 == c2 for c1, c2 in zip(word1, word2))
candidates = words[:]
for _ in range(10):
if not candidates:
break
# 随机选择一个候选单词
guess = random.choice(candidates)
matches = master.guess(guess)
# 如果找到秘密单词,直接返回
if matches == 6:
return
# 过滤候选单词,保留与guess有相同匹配数的单词
candidates = [word for word in candidates
if get_matches(word, guess) == matches]
public class Solution {
private int GetMatches(string word1, string word2) {
int matches = 0;
for (int i = 0; i < 6; i++) {
if (word1[i] == word2[i]) {
matches++;
}
}
return matches;
}
public void FindSecretWord(string[] words, Master master) {
var candidates = new List<string>(words);
var random = new Random();
for (int attempt = 0; attempt < 10 && candidates.Count > 0; attempt++) {
// 随机选择一个候选单词
int idx = random.Next(candidates.Count);
string guess = candidates[idx];
int matches = master.Guess(guess);
// 如果找到秘密单词,直接返回
if (matches == 6) {
return;
}
// 过滤候选单词,保留与guess有相同匹配数的单词
var newCandidates = new List<string>();
foreach (string word in candidates) {
if (GetMatches(word, guess) == matches) {
newCandidates.Add(word);
}
}
candidates = newCandidates;
}
}
}
var findSecretWord = function(words, master) {
function getMatches(word1, word2) {
let matches = 0;
for (let i = 0; i < 6; i++) {
if (word1[i] === word2[i]) matches++;
}
return matches;
}
let candidates = [...words];
for (let i = 0; i < 10; i++) {
if (candidates.length === 0) break;
// Choose word that minimizes worst case
let bestWord = candidates[0];
let minWorstCase = candidates.length;
for (let word of candidates) {
let groups = new Map();
for (let candidate of candidates) {
let matches = getMatches(word, candidate);
groups.set(matches, (groups.get(matches) || 0) + 1);
}
let maxGroupSize = Math.max(...groups.values());
if (maxGroupSize < minWorstCase) {
minWorstCase = maxGroupSize;
bestWord = word;
}
}
let result = master.guess(bestWord);
if (result === 6) return;
candidates = candidates.filter(word => getMatches(bestWord, word) === result);
}
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(N × 6 × attempts) | N为单词数量,每次比较需要O(6),最多尝试10次 |
| 空间复杂度 | O(N) | 存储候选单词列表 |