题目重点:题目可读 Level 1–3 的全部接口;用 ID 主表和规范化名称索引保证更新/删除一致,Level 4 不可见不推断。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- recipeId 为 'recipe'+从 1 递增的整数,删除后 ID 不复用;若失败的 add 是否消耗序号题目未明,训练版只成功时递增。
- 名称唯一/食材匹配采用 Unicode casefold 的训练解释,题目只说忽略大小写;是否 trim 空格/做 Unicode 规范化需按真实测试确定。
- 同 ID 的更新/编辑允许保持自身原名,只有与其他 ID 的规范化名称冲突才拒绝;所有用户均可编辑,Level 3 不设所有权。
- recipe ID 排序训练版按数值序号;若评测要求字符串词典序应按实际接口切换,题目只写‘ID 升序’。
解题路线
- 维护 recipes: id→(name,ingredients,steps,seq)、nameIndex: casefold(name)→id、users 集合;新增先查 nameIndex 冲突,成功后创建 ID 与索引。
- get 按原传入顺序把 ingredients 和 steps 用逗号连接,不对列表排序或去重;不存在返回空列表。
- update/edit 用共同的 replaceRecipe:先检查 recipe/用户与新名冲突,若改名则从旧索引删键并写新键,再原样复制新的 ingredient/steps。失败时所有状态不变。
- delete 同时删主表和名称索引;search ingredient 对每个 recipe 的 ingredient 逐项 casefold 精确匹配,再按 (ingredientCount,seq) 排。
- list_recipes 按 sort_by 分别排序:ingredient_count 用数量/ID;name 用原名词典序/ID;无效 sort_by 按 name。若需要大量查询才考虑倒排索引,更新时必须同步维护。
手动走一遍
训练例:add('Soup',['Salt','Water'],['Mix']) 得 recipe1;add('soup',...) 因忽略大小写同名失败。update recipe1 为 'Salad' 后旧名 Soup 可再次新增。搜索 'salt' 命中 recipe1;get 返回食材原输入顺序的 'Salt,Water'。
规模与取舍
主表/名称索引操作均摊 O(1) 加复制列表成本;ingredient 搜索扫描全部 R 道菜及 I 个食材 O(I+R log R) 排序,list O(R log R);存储 O(R+总食材/步骤长度)。
易错点与追问
容易失分的边界
- 只在 add 检查重名,update/edit 可制造重复。
- 同 ID 保持原名时误判与自己冲突。
- 删除只删主表,旧名称永久占用。
- 把 ingredient 按子串匹配,而题目说包含指定食材。
- 用户编辑错误地按所有权限制。
- 凭空补出 Level 4 功能。
继续深挖
- 若要支持大量食材检索,如何做倒排索引及更新?
- Unicode 大小写与空白如何规范化?
- recipe_id_10 与 recipe_id_2 应如何排序?
- 并发更新下如何保持名称唯一索引?
