☰
Codewars: A Brave New Wordle(C)解题分析
2026/10/15 3:49:45 网站建设 项目流程
  • 题目:A Brave New Wordle

  • 难度:Codewars 5 kyu

  • 语言:C

  • 分析日期:2026-10-14

  • 题目页:Training on A Brave New Wordle | Codewars

1. 题目要求与问题核心

实现:

void wordle( char hints[5 + 1], const char guess[5 + 1], const char solution[5 + 1] );

guess和solution都是长度为 5 的单词。函数需要向hints写入 5 个提示字符,并以'\0'结束:

提示

含义

g

当前字母与答案中同位置字母一致(green)

y

当前字母存在于答案中,但位置不对(yellow)

b

答案中没有可再匹配的该字母(black)

真正的难点不是普通的逐字符比较,而是重复字母的配额管理:

  1. 所有同位置匹配必须优先标成g。

  2. 一个字母最多只能被标记g或y的次数,不能超过它在solution中的出现次数。

  3. 对非绿色字母判定黄色时,必须从左到右处理。

例如:

guess: xxxxx solution: yxxxx hints: bgggg

后四个x已经消耗了答案中的四个x,第一个x没有剩余配额,因此是b,不是y。

2. 正确的抽象:两阶段“消耗库存”

可以把solution视作每种字母的有限库存。每次给出g或y,都必须恰好消耗答案中的一个尚未使用的位置或一个剩余计数。

阶段 1:逐位置找 green guess: a a x x x solution: x a x x a . g g g . 阶段 2:只处理非 green 的 guess,按从左到右顺序匹配尚未消耗的 solution 字母 剩余答案位置:0 -> x,4 -> a guess[0] = a -> y,消耗 solution[4] guess[4] = x -> y,消耗 solution[0] 结果:y g g g y

该顺序保证了两个关键性质:

  • 绿色匹配永远优先,不会被黄色匹配抢占。

  • 对重复字母,较左侧的非绿色猜测会优先得到黄色资格。

3. 你的实现

void wordle(char hints[5 + 1], const char guess[5 + 1], const char solution[5 + 1]) { char solution1[6]; for (int i = 0; i < 5; i++) { solution1[i] = solution[i]; } for (int i = 0; i < 5; i++) { if (guess[i] == solution1[i]) { solution1[i] = '1'; hints[i] = 'g'; } } for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { if ((hints[i] != 'g') && (solution1[j] != '1')) { if (guess[i] == solution1[j]) { hints[i] = 'y'; solution1[j] = '1'; break; } } } } for (int j = 0; j < 5; j++) { if ((hints[j] != 'g') && (hints[j] != 'y')) { hints[j] = 'b'; } } hints[5] = '\0'; }

4. 对你的算法的评价

做对的部分

你的主体思路正是本题需要的两阶段消耗算法:

  1. 复制solution到工作数组solution1。

  2. 第一轮标记所有绿色位置,同时把对应答案位置写成'1',表示已消耗。

  3. 第二轮从左到右扫描非绿色guess[i],在solution1中寻找第一个同字符且未消耗的位置;找到后标为黄色并消耗该位置。

  4. 没有得到绿色或黄色的位置最后写成黑色。

在hints已正确初始化的前提下,这个策略能正确处理题目中的重复字母、绿色优先和黄色从左到右规则。

关键问题:读取了未初始化的hints

以下代码会在非绿色位置读取尚未赋值的hints[i]:

if ((hints[i] != 'g') && (solution1[j] != '1'))

第一轮只在绿色位置写了hints[i] = 'g'。因此,对一个不是绿色的位置,第二轮在判断hints[i] != 'g'前,hints[i]还没有被初始化。最后一轮的hints[j] != 'g'和hints[j] != 'y'也有同样问题。

这不是“默认不是g”的安全前提。在 C 中,自动局部数组中未赋值元素的值不应被读取;程序行为不能依赖它恰好不是g或y。实际运行时,它可能碰巧通过,也可能导致个别位置没有进入黄色匹配或没有被正确写成b。

最小修复是在第一轮先给每个提示位置写默认值b,然后第二轮仅处理b:

for (int i = 0; i < 5; ++i) { hints[i] = 'b'; if (guess[i] == solution1[i]) { solution1[i] = '1'; hints[i] = 'g'; } }

次要问题:用'1'作为已消耗标记

在当前题目的“5 个字母”约束下,'1'不会与普通字母冲突,因此通常可以工作。但它把“输入永远不含字符'1'”变成了一个隐含依赖。若以后函数被复用到任意字符数据,真实的'1'会被错误当成已消耗位置。

更稳健的方式是使用独立的used[5]数组保存位置状态。这样不修改答案字符,也不需要保留字符来充当哨兵。

次要问题:solution1[6]不是一个完整字符串

你只复制了下标0到4,没有写入solution1[5] = '\0'。在当前实现中,所有访问都限制在前 5 个位置,所以不会因这一点直接越界;只是solution1实际上被当作“长度为 5 的字符数组”,不是 C 字符串。

两种清晰的写法:

char solution1[5]; // 明确它只是 5 个字符的工作缓冲区

或:

char solution1[6]; /* 复制前 5 个字符后: */ solution1[5] = '\0'; // 明确它可作为字符串使用

5. 保留你原思路的推荐实现

下面的版本仍然是“绿色优先 + 从左到右找未使用答案位置”,但消除了未初始化读取和哨兵字符依赖:

#include <stdbool.h> void wordle( char hints[5 + 1], const char guess[5 + 1], const char solution[5 + 1] ) { bool used[5] = { false }; /* 先写好默认黑色,并优先处理精确匹配。 */ for (int i = 0; i < 5; ++i) { hints[i] = 'b'; if (guess[i] == solution[i]) { hints[i] = 'g'; used[i] = true; } } /* 仅为非绿色位置,从左到右消耗答案中剩余的匹配字母。 */ for (int i = 0; i < 5; ++i) { if (hints[i] == 'g') { continue; } for (int j = 0; j < 5; ++j) { if (!used[j] && guess[i] == solution[j]) { hints[i] = 'y'; used[j] = true; break; } } } hints[5] = '\0'; }

这个版本的优点:

  • hints[0..4]在任何读取前都已初始化。

  • used[j]只表达“答案的第j个位置是否已被消耗”,语义直接。

  • 不改写solution,满足const的设计意图。

  • 除了 C99 标准头<stdbool.h>外没有额外依赖。

  • 与你的原算法具有相同的匹配顺序,因此保留题目需要的重复字母行为。

6. 更可扩展的计数法

当单词长度从固定的 5 推广为L时,上述双层扫描的时间复杂度为O(L^2)。如果输入是单字节字符,可以把“尚未被绿色消耗的答案字符”改为频次数组:

#include <limits.h> #include <stddef.h> void wordle( char hints[5 + 1], const char guess[5 + 1], const char solution[5 + 1] ) { unsigned char remaining[UCHAR_MAX + 1] = { 0 }; for (size_t i = 0; i < 5; ++i) { hints[i] = 'b'; if (guess[i] == solution[i]) { hints[i] = 'g'; } else { ++remaining[(unsigned char)solution[i]]; } } for (size_t i = 0; i < 5; ++i) { unsigned char letter; if (hints[i] == 'g') { continue; } letter = (unsigned char)guess[i]; if (remaining[letter] != 0) { hints[i] = 'y'; --remaining[letter]; } } hints[5] = '\0'; }

两个版本如何取舍

方案

时间复杂度(长度为 L)

额外空间

优点

适用情况

位置used[]

O(L^2)

O(L)

最接近题意,容易验证,不依赖字符集大小

本题固定 5 个字符,首选

频次数组

O(L + 256)

O(256)

对较长单词更快,重复字母配额表达清晰

单字节字符、长度可变的版本

对这道固定长度为 5 的 kata,5 * 5 = 25次比较是常数时间。计数法在这里不会带来可感知的性能收益,反而比used[]多使用 256 个计数器。因此实际提交时,推荐前面的used[]版本。

7. 正确性说明

设答案每个下标只能被消耗一次。

第一阶段:

  • 若guess[i] == solution[i],将hints[i]设为g并消耗答案下标i。

  • 因此每个绿色提示都对应一个同位置答案字符,并且不会被后续黄色匹配使用。

第二阶段:

  • 仅处理非绿色位置,按i = 0到4的顺序进行。

  • 只有找到未消耗的solution[j] == guess[i]时,才给出y并立即消耗j。

  • 所以同一个答案位置不会对应多个y,任一字母得到的g加y数不会超过它在答案中的出现次数。

  • 由于i从左到右扫描,重复猜测字母的黄色名额会先分配给更靠左的位置。

没有匹配到绿色或黄色的位置保持初始化时写入的b,故所有输出位置都符合题目定义。

8. 建议测试集

guess

solution

期望hints

覆盖点

crane

crane

ggggg

全部绿色

xxxxx

yxxxx

bgggg

绿色优先,额外重复字母为黑色

xxxxz

aaaax

ybbbb

仅一个黄色,其余重复字母为黑色

aaaaa

aaaab

ggggb

重复字母超出答案库存

aaxxx

xaxxa

ygggy

绿色与黄色混合,重复字母

aabbb

abcde

gbybb

黄色配额仅能使用一次

abcde

fghij

bbbbb

无匹配,并验证所有输出位置均已初始化

9. 结论

你的算法方向是正确的,且对固定长度 5 的输入已经足够高效:一般形式为O(L^2),本题实际是常数时间。最重要的改进不是性能,而是安全性:在读取前初始化hints,并用独立的used[]表示“答案位置已消耗”。

推荐提交第 5 节的used[]版本。它和你的设计思想一致,但更符合 C 的初始化规则,语义更清晰,也不依赖'1'不是输入字符这一隐含前提。

来源

  • Codewars kata(题意、重复字母规则、示例):Training on A Brave New Wordle | Codewars

  • Codewars challenge API(题目元数据与完整描述):https://www.codewars.com/api/v1/code-challenges/62013b174c72240016600e60

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询