Skip to content

Commit b7fe42c

Browse files
committed
feat: leetcode 676
1 parent e4be115 commit b7fe42c

File tree

8 files changed

+249
-1
lines changed

8 files changed

+249
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<p align="center">
44
<!-- TOPICS COUNT START -->
5-
<img src="https://img.shields.io/badge/-进度:427-green" alt="进度:427">
5+
<img src="https://img.shields.io/badge/-进度:428-green" alt="进度:428">
66
<!-- TOPICS COUNT END -->
77
<a href="./assets/docs/TOPICS.md"><img src="https://img.shields.io/badge/-题库目录-blue" alt="题库目录"></a>
88
<a href="./assets/docs/CATEGORIES.md"><img src="https://img.shields.io/badge/-题库分类-red" alt="题库分类"></a>

assets/data/categories.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3428,6 +3428,12 @@
34283428
"path": "./problemset/longest-uncommon-subsequence-2/README.md",
34293429
"difficulty": "中等"
34303430
},
3431+
{
3432+
"id": "676",
3433+
"title": "实现一个魔法字典",
3434+
"path": "./problemset/implement-magic-dictionary/README.md",
3435+
"difficulty": "中等"
3436+
},
34313437
{
34323438
"id": "699",
34333439
"title": "掉落的方块",
@@ -3616,6 +3622,12 @@
36163622
"title": "字典序的第K小数字",
36173623
"path": "./problemset/k-th-smallest-in-lexicographical-order/README.md",
36183624
"difficulty": "困难"
3625+
},
3626+
{
3627+
"id": "676",
3628+
"title": "实现一个魔法字典",
3629+
"path": "./problemset/implement-magic-dictionary/README.md",
3630+
"difficulty": "中等"
36193631
}
36203632
]
36213633
},

assets/data/topics.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3239,6 +3239,16 @@
32393239
"url": "https://leetcode.cn/problems/cut-off-trees-for-golf-event/",
32403240
"path": "./problemset/cut-off-trees-for-golf-event/README.md"
32413241
},
3242+
{
3243+
"id": "676",
3244+
"title": {
3245+
"cn": "实现一个魔法字典",
3246+
"en": "implement-magic-dictionary"
3247+
},
3248+
"difficulty": "中等",
3249+
"url": "https://leetcode.cn/problems/implement-magic-dictionary/",
3250+
"path": "./problemset/implement-magic-dictionary/README.md"
3251+
},
32423252
{
32433253
"id": "682",
32443254
"title": {

assets/docs/CATEGORIES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@
650650
| 204. [计数质数](../../problemset/count-primes/README.md) | 中等 |
651651
| 479. [最大回文数乘积](../../problemset/largest-palindrome-product/README.md) | 困难 |
652652
| 522. [最长特殊序列 II](../../problemset/longest-uncommon-subsequence-2/README.md) | 中等 |
653+
| 676. [实现一个魔法字典](../../problemset/implement-magic-dictionary/README.md) | 中等 |
653654
| 699. [掉落的方块](../../problemset/falling-squares/README.md) | 困难 |
654655
| 812. [最大三角形面积](../../problemset/largest-triangle-area/README.md) | 简单 |
655656
| 1601. [最多可达成的换楼请求数目](../../problemset/maximum-number-of-achievable-transfer-requests/README.md) | 困难 |
@@ -694,6 +695,7 @@
694695
| 211. [添加与搜索单词 - 数据结构设计](../../problemset/design-add-and-search-words-data-structure/README.md) | 中等 |
695696
| 212. [单词搜索 II](../../problemset/word-search-2/README.md) | 困难 |
696697
| 440. [字典序的第K小数字](../../problemset/k-th-smallest-in-lexicographical-order/README.md) | 困难 |
698+
| 676. [实现一个魔法字典](../../problemset/implement-magic-dictionary/README.md) | 中等 |
697699

698700
## 优先队列
699701

assets/docs/TOPICS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,8 @@
648648

649649
[675. 为高尔夫比赛砍树](../../problemset/cut-off-trees-for-golf-event/README.md)
650650

651+
[676. 实现一个魔法字典](../../problemset/implement-magic-dictionary/README.md)
652+
651653
[682. 棒球比赛](../../problemset/baseball-game/README.md)
652654

653655
[688. 骑士在棋盘上的概率](../../problemset/knight-probability-in-chessboard/README.md)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# 实现一个魔法字典
2+
3+
> 难度:中等
4+
>
5+
> https://leetcode.cn/problems/implement-magic-dictionary/
6+
7+
## 题目
8+
9+
设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 **互不相同** 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。
10+
11+
实现 `MagicDictionary` 类:
12+
13+
- `MagicDictionary()` 初始化对象
14+
- `void buildDict(String[] dictionary)` `使用字符串数组 dictionary` `设定该数据结构,dictionary` 中的字符串互不相同
15+
- `bool search(String searchWord)` 给定一个字符串 `searchWord` ,判定能否只将字符串中 **一个** 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 `true` ;否则,返回 `false`
16+
 
17+
18+
### 示例:
19+
20+
```
21+
输入
22+
["MagicDictionary", "buildDict", "search", "search", "search", "search"]
23+
[[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]]
24+
输出
25+
[null, null, false, true, false, false]
26+
27+
解释
28+
MagicDictionary magicDictionary = new MagicDictionary();
29+
magicDictionary.buildDict(["hello", "leetcode"]);
30+
magicDictionary.search("hello"); // 返回 False
31+
magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
32+
magicDictionary.search("hell"); // 返回 False
33+
magicDictionary.search("leetcoded"); // 返回 False
34+
```
35+
36+
## 解题
37+
38+
### 枚举每个字典中的字符串并判断
39+
40+
```ts
41+
/**
42+
* 枚举每个字典中的字符串并判断
43+
* @desc 时间复杂度 O(qnl) 空间复杂度 O(nl)
44+
* @desc 其中n是dictionary 的长度,l是dictionary中字符串的平均长度,q是函数search(searchWord)的调用次数
45+
*/
46+
export class MagicDictionary {
47+
words: string[] = []
48+
49+
buildDict(dictionary: string[]): void {
50+
this.words = dictionary
51+
}
52+
53+
search(searchWord: string): boolean {
54+
for (const word of this.words) {
55+
if (word.length !== searchWord.length) continue
56+
let diff = 0
57+
58+
for (let i = 0; i < word.length; i++) {
59+
if (word[i] !== searchWord[i]) {
60+
diff++
61+
if (diff > 1) break
62+
}
63+
}
64+
if (diff === 1) return true
65+
}
66+
return false
67+
}
68+
}
69+
```
70+
71+
### 使用字典树优化枚举
72+
73+
```ts
74+
/**
75+
* 使用字典树优化枚举
76+
* @desc 时间复杂度 O(nl+ql∣Σ∣) 空间复杂度 O(nl)
77+
* @desc 其中n是dictionary 的长度,l是dictionary中字符串的平均长度,q是函数search(searchWord)的调用次数,Σ 是字符集
78+
*/
79+
export class MagicDictionary2 {
80+
root = new Trie()
81+
82+
buildDict(dictionary: string[]): void {
83+
for (const word of dictionary) {
84+
let cur: Trie = this.root
85+
for (let i = 0; i < word.length; i++) {
86+
const ch = word[i]
87+
const idx = ch.charCodeAt(0) - 'a'.charCodeAt(0)
88+
if (!cur.child[idx]) cur!.child[idx] = new Trie()
89+
cur = cur.child[idx] as Trie
90+
}
91+
cur.isFinished = true
92+
}
93+
}
94+
95+
search(searchWord: string): boolean {
96+
return this._dfs(searchWord)
97+
}
98+
99+
private _dfs(searchWord: string, node: Trie = this.root, pos = 0, modified = false): boolean {
100+
if (pos === searchWord.length) return modified && node.isFinished
101+
102+
const idx = searchWord[pos].charCodeAt(0) - 'a'.charCodeAt(0)
103+
if (node.child[idx] && this._dfs(searchWord, node.child[idx]!, pos + 1, modified)) return true
104+
105+
if (!modified) {
106+
for (let i = 0; i < 26; i++) {
107+
if (i !== idx && node.child[i]) {
108+
if (this._dfs(searchWord, node.child[i]!, pos + 1, true))
109+
return true
110+
}
111+
}
112+
}
113+
114+
return false
115+
}
116+
}
117+
118+
class Trie {
119+
isFinished = false
120+
child: (Trie | null)[] = new Array(26).fill(null)
121+
}
122+
```
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { MagicDictionary, MagicDictionary2 } from '.'
3+
4+
describe('实现一个魔法字典', () => {
5+
describe('枚举每个字典中的字符串并判断', () => testCase(MagicDictionary))
6+
describe('使用字典树优化枚举', () => testCase(MagicDictionary2))
7+
})
8+
9+
type CtorType = new () => ({
10+
buildDict: (dictionary: string[]) => void
11+
search: (searchWord: string) => boolean
12+
})
13+
14+
function testCase(Ctor: CtorType) {
15+
it('示例', () => {
16+
const magicDictionary = new Ctor()
17+
magicDictionary.buildDict(['hello', 'leetcode'])
18+
expect(magicDictionary.search('hello')).toBe(false) // 返回 False
19+
expect(magicDictionary.search('hhllo')).toBe(true) // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True
20+
expect(magicDictionary.search('hell')).toBe(false) // 返回 False
21+
expect(magicDictionary.search('leetcoded')).toBe(false) // 返回 False
22+
})
23+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* 枚举每个字典中的字符串并判断
3+
* @desc 时间复杂度 O(qnl) 空间复杂度 O(nl)
4+
* @desc 其中n是dictionary 的长度,l是dictionary中字符串的平均长度,q是函数search(searchWord)的调用次数
5+
*/
6+
export class MagicDictionary {
7+
words: string[] = []
8+
9+
buildDict(dictionary: string[]): void {
10+
this.words = dictionary
11+
}
12+
13+
search(searchWord: string): boolean {
14+
for (const word of this.words) {
15+
if (word.length !== searchWord.length) continue
16+
let diff = 0
17+
18+
for (let i = 0; i < word.length; i++) {
19+
if (word[i] !== searchWord[i]) {
20+
diff++
21+
if (diff > 1) break
22+
}
23+
}
24+
if (diff === 1) return true
25+
}
26+
return false
27+
}
28+
}
29+
30+
/**
31+
* 使用字典树优化枚举
32+
* @desc 时间复杂度 O(nl+ql∣Σ∣) 空间复杂度 O(nl)
33+
* @desc 其中n是dictionary 的长度,l是dictionary中字符串的平均长度,q是函数search(searchWord)的调用次数,Σ 是字符集
34+
*/
35+
export class MagicDictionary2 {
36+
root = new Trie()
37+
38+
buildDict(dictionary: string[]): void {
39+
for (const word of dictionary) {
40+
let cur: Trie = this.root
41+
for (let i = 0; i < word.length; i++) {
42+
const ch = word[i]
43+
const idx = ch.charCodeAt(0) - 'a'.charCodeAt(0)
44+
if (!cur.child[idx]) cur!.child[idx] = new Trie()
45+
cur = cur.child[idx] as Trie
46+
}
47+
cur.isFinished = true
48+
}
49+
}
50+
51+
search(searchWord: string): boolean {
52+
return this._dfs(searchWord)
53+
}
54+
55+
private _dfs(searchWord: string, node: Trie = this.root, pos = 0, modified = false): boolean {
56+
if (pos === searchWord.length) return modified && node.isFinished
57+
58+
const idx = searchWord[pos].charCodeAt(0) - 'a'.charCodeAt(0)
59+
if (node.child[idx] && this._dfs(searchWord, node.child[idx]!, pos + 1, modified)) return true
60+
61+
if (!modified) {
62+
for (let i = 0; i < 26; i++) {
63+
if (i !== idx && node.child[i]) {
64+
if (this._dfs(searchWord, node.child[i]!, pos + 1, true))
65+
return true
66+
}
67+
}
68+
}
69+
70+
return false
71+
}
72+
}
73+
74+
class Trie {
75+
isFinished = false
76+
child: (Trie | null)[] = new Array(26).fill(null)
77+
}

0 commit comments

Comments
 (0)