diff --git a/Contents/05.Hash-Table/01.Hash-Table.md b/Contents/05.Hash-Table/01.Hash-Table.md index a0841661..3e43101e 100644 --- a/Contents/05.Hash-Table/01.Hash-Table.md +++ b/Contents/05.Hash-Table/01.Hash-Table.md @@ -1,11 +1,13 @@ ## 1. 哈希表简介 -> **哈希表(Hash Table)**:也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。也就是说,它通过键 `key` 和一个映射函数 `Hash(key)` 计算出对应的值 `value`,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数)」,存放记录的数组叫做「哈希表(散列表)」。 +> **哈希表(Hash Table)**:也叫做散列表。是根据关键码值(Key Value)直接进行访问的数据结构。 +> +> 哈希表通过「键 `key` 」和「映射函数 `Hash(key)` 」计算出对应的「值 `value`」,把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做「哈希函数(散列函数)」,存放记录的数组叫做「哈希表(散列表)」。 哈希表的关键思想是使用哈希函数,将键 `key` 映射到对应表的某个区块中。我们可以将算法思想分为两个部分: -- 向哈希表中插入一个关键字:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。 -- 在哈希表中搜索一个关键字:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。 +- **向哈希表中插入一个关键码值**:哈希函数决定该关键字的对应值应该存放到表中的哪个区块,并将对应值存放到该区块中。 +- **在哈希表中搜索一个关键码值**:使用相同的哈希函数从哈希表中查找对应的区块,并在特定的区块搜索该关键字对应的值。 哈希表的原理示例图如下所示: @@ -13,9 +15,9 @@ 在上图例子中,我们使用 `value = Hash(key) = key // 1000` 作为哈希函数。`//` 符号代表整除。我们以这个例子来说明一下哈希表的插入和查找策略。 -- 插入:通过哈希函数解析关键字,并将对应值存放到该区块中。 +- **向哈希表中插入一个关键码值**:通过哈希函数解析关键字,并将对应值存放到该区块中。 - 比如:`0138` 通过哈希函数 `Hash(key) = 0138 // 100 = 0`,得出应将 `0138` 分配到`0` 所在的区块中。 -- 查找:通过哈希函数解析关键字,并在特定的区块搜索该关键字对应的值。 +- **在哈希表中搜索一个关键码值**:通过哈希函数解析关键字,并在特定的区块搜索该关键字对应的值。 - 比如:查找 `2321`,通过哈希函数,得出 `2321` 应该在 `2` 所对应的区块中。然后我们从 `2` 对应的区块中继续搜索,并在 `2` 对应的区块中成功找到了 `2321`。 - 比如:查找 `3214`,通过哈希函数,得出 `3214` 应该在 `3` 所对应的区块中。然后我们从 `3` 对应的区块中继续搜索,但并没有找到对应值,则说明 `3214` 不在哈希表中。 @@ -29,8 +31,8 @@ - 存放所有拼音和对应地址的表可以看做是 **「哈希表」**。 - `赞` 字的拼音索引 `zan` 可以看做是哈希表中的 **「关键字 `key`」**。 -- 根据拼音索引 `zan` 可以确定字对应页码的过程可以看做是哈希表中的 **「哈希函数 `Hash(key)`」**。 -- 所查找到的对应页码 `599` 可以看做是哈希表中的 **「哈希地址 `value`」**。 +- 根据拼音索引 `zan` 来确定字对应页码的过程可以看做是哈希表中的 **「哈希函数 `Hash(key)`」**。 +- 查找到的对应页码 `599` 可以看做是哈希表中的 **「哈希地址 `value`」**。 ## 2. 哈希函数 @@ -49,22 +51,21 @@ ### 2.1 直接定址法 -- 直接定址法:取关键字或者关键字的某个线性函数值为哈希地址。即:`Hash(key) = key` 或者 `Hash(key) = a * key + b`,其中 `a` 和 `b` 为常数。 +- **直接定址法**:取关键字本身 / 关键字的某个线性函数值 作为哈希地址。即:`Hash(key) = key` 或者 `Hash(key) = a * key + b`,其中 `a` 和 `b` 为常数。 这种方法计算最简单,且不会产生冲突。适合于关键字分布基本连续的情况,如果关键字分布不连续,空位较多,则会造成存储空间的浪费。 举一个例子,假设我们有一个记录了从 `1` 岁到 `100` 岁的人口数字统计表。其中年龄为关键字,哈希函数取关键字自身,如下表所示。 -| 地址 | 01 | 02 | 03 | ... | 25 | 26 | 27 | ... | 100 | -| :--: | :--: | :--: | :--: | :-: | :--: | :-: | :-: | :-: | :-: | -| 人数 | 3000 | 2000 | 5000 | ... | 1050 | ... | ... | ... | ... | -| 年龄 | 1 | 2 | 3 | ... | 25 | 26 | 27 | ... | 100 | +| 年龄 | 1 | 2 | 3 | ... | 25 | 26 | 27 | ... | 100 | +| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | +| 人数 | 3000 | 2000 | 5000 | ... | 1050 | ... | ... | ... | ... | 比如我们想要查询 `25` 岁的人有多少,则只要查询表中第 `25` 项即可。 ### 2.2 除留余数法 -- 除留余数法:假设哈希表的表长为 `m`,取一个不大于 `m` 但接近或等于 `m` 的质数 `p`,利用取模运算,将关键字转换为哈希地址。即:`Hash(key) = key % p`,其中 `p` 为不大于 `m` 的质数。 +- **除留余数法**:假设哈希表的表长为 `m`,取一个不大于 `m` 但接近或等于 `m` 的质数 `p`,利用取模运算,将关键字转换为哈希地址。即:`Hash(key) = key % p`,其中 `p` 为不大于 `m` 的质数。 这也是一种简单且常用的哈希函数方法。其关键点在于 `p` 的选择。根据经验而言,一般 `p` 取素数或者 `m`,这样可以尽可能的减少冲突。 @@ -76,14 +77,14 @@ ### 2.3 平方取中法 -- 平方取中法:先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长度取关键字平方值的中间几位数为哈希地址。 +- **平方取中法**:先通过求关键字平方值的方式扩大相近数之间的差别,然后根据表长度取关键字平方值的中间几位数为哈希地址。 - 比如:`Hash(key) = (key * key) // 100 % 1000`,先计算平方,去除末尾的 2 位数,再取中间 3 位数作为哈希地址。 这种方法因为关键字平方值的中间几位数和原关键字的每一位数都相关,所以产生的哈希地址也比较均匀,有利于减少冲突的发生。 ### 2.4 基数转换法 -- 基数转换法:将关键字看成另一种进制的数再转换成原来进制的数,然后选其中几位作为哈希地址。 +- **基数转换法**:将关键字看成另一种进制的数再转换成原来进制的数,然后选其中几位作为哈希地址。 - 比如,将关键字看做是 `13` 进制的数,再将其转变为 `10` 进制的数,将其作为哈希地址。 以 `343246` 为例,哈希地址计算方式如下: diff --git a/Contents/06.String/01.String-Basic/01.String-Basic.md b/Contents/06.String/01.String-Basic/01.String-Basic.md index ddd4c855..f08a5f1e 100644 --- a/Contents/06.String/01.String-Basic/01.String-Basic.md +++ b/Contents/06.String/01.String-Basic/01.String-Basic.md @@ -6,9 +6,9 @@ - **字符串名称**:字符串定义中的 `s` 就是字符串的名称。 - **字符串的值**:$a_1a_2...a_n$ 组成的字符序列就是字符串的值,一般用双引号括起来。 -- **字符变量**:字符串每一个位置上的元素都是一个字符变量。字符 $a_i$ 可以是字母、数字或者其他字符。`i` 是该字符在字符串中的位置。 -- **字符串的长度**:字符串中字符的数目 `n` 称为字符串的长度。 -- **空串**:零个字符构成的串也成为 **「空字符串(Null String)」**,它的长度为 `0`,可以表示为 `""`。 +- **字符变量**:字符串每一个位置上的元素都是一个字符变量。字符 $a_i$ 可以是字母、数字或者其他字符。$i$ 是该字符在字符串中的位置。 +- **字符串的长度**:字符串中字符的数目 $n$ 称为字符串的长度。 +- **空串**:零个字符构成的串也成为 **「空字符串(Null String)」**,它的长度为 0ドル,ドル可以表示为 `""`。 - **子串**:字符串中任意个连续的字符组成的子序列称为该字符串的 **「子串(Substring)」**。并且有两种特殊子串,起始于位置为 `0`、长度为 `k` 的子串称为 **「前缀(Prefix)」**。而终止于位置 `n - 1`、长度为 `k` 的子串称为 **「后缀(Suffix)」**。 - **主串**:包含子串的字符串相应的称为 **「主串」**。 @@ -22,7 +22,9 @@ str = "Hello World"  -可以看出来,字符串和数组有很多相似之处。比如使用 `名称[下标]` 的方式来访问一个字符。之所以单独讨论字符串是因为: +可以看出来,字符串和数组有很多相似之处。比如同样使用 `名称[下标]` 的方式来访问一个字符。 + +之所以单独讨论字符串是因为: - 字符串中的数据元素都是字符,结构相对简单,但规模可能比较庞大。 - 经常需要把字符串作为一个整体来使用和处理。操作对象一般不是某个数据元素,而是一组数据元素(整个字符串或子串)。 @@ -30,17 +32,19 @@ str = "Hello World" 根据字符串的特点,我们可以将字符串问题分为以下几种: -- 字符串匹配问题; -- 子串相关问题; +- 字符串匹配问题。 +- 子串相关问题。 - 前缀 / 后缀相关问题; -- 回文串相关问题; +- 回文串相关问题。 - 子序列相关问题。 ## 2. 字符串的比较 ### 2.1 字符串的比较操作 -两个数字之间很容易比较大小,例如 `1 < 2`。而字符串之间的比较相对来说复杂一点。字符串之间的大小取决于它们按顺序排列字符的前后顺序。比如字符串 `str1 = "abc"` 和 `str2 = "acc"`,它们的第一个字母都是 `a`,而第二个字母,由于字母 `b` 比字母 `c` 要靠前,所以 `b < c`,于是我们可以说 `"abc" < "acd" `,也可以说 `str1 < str2`。 +两个数字之间很容易比较大小,例如 `1 < 2`。而字符串之间的比较相对来说复杂一点。字符串之间的大小取决于它们按顺序排列字符的前后顺序。 + +比如字符串 `str1 = "abc"` 和 `str2 = "acc"`,它们的第一个字母都是 `a`,而第二个字母,由于字母 `b` 比字母 `c` 要靠前,所以 `b < c`,于是我们可以说 `"abc" < "acd" `,也可以说 `str1 < str2`。 字符串之间的比较是通过组成字符串的字符之间的「字符编码」来决定的。而字符编码指的是字符在对应字符集中的序号。 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md b/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md index bfb693cd..c266f10a 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/01.String-Brute-Force.md @@ -1,18 +1,20 @@ -## 1. BF 算法介绍 +## 1. Brute Force 算法介绍 -BF 算法的全称是 **「Brute Force 算法」**,中文意思是暴力匹配算法,也可以叫做朴素匹配算法。 +> **Brute Force 算法**:简称为 BF 算法。中文意思是暴力匹配算法,也可以叫做朴素匹配算法。 +> +> - **BF 算法思想**:对于给定文本串 `T` 与模式串 `p`,从文本串的第一个字符开始与模式串 `p` 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 `T` 的第二个字符起重新和模式串 `p` 进行比较。依次类推,直到模式串 `p` 中每个字符依次与文本串 `T` 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 -> **BF 算法思想**:对于给定文本串 `T` 与模式串 `p`,从文本串的第一个字符开始与模式串 `p` 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 `T` 的第二个字符起重新和模式串 `p` 进行比较。依次类推,直到模式串 `p` 中每个字符依次与文本串 `T` 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 + -## 2. BF 算法步骤 +## 2. Brute Force 算法步骤 -- 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -- 同时遍历文本串 `T` 和模式串 `p`,先将 `T[0]` 与 `p[0]` 进行比较。 -- 如果相等,则继续比较 `T[1]` 和 `p[1]`。以此类推,一直到模式串 `p` 的末尾 `p[m - 1]` 为止。 -- 如果不相等,则将文本串 `T` 移动到上次匹配开始位置的下一个字符位置,模式串 `p` 则回退到开始位置,再依次进行比较。 -- 当遍历完文本串 `T` 或者模式串 `p` 的时候停止搜索。 +1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 同时遍历文本串 `T` 和模式串 `p`,先将 `T[0]` 与 `p[0]` 进行比较。 + 1. 如果相等,则继续比较 `T[1]` 和 `p[1]`。以此类推,一直到模式串 `p` 的末尾 `p[m - 1]` 为止。 + 2. 如果不相等,则将文本串 `T` 移动到上次匹配开始位置的下一个字符位置,模式串 `p` 则回退到开始位置,再依次进行比较。 +3. 当遍历完文本串 `T` 或者模式串 `p` 的时候停止搜索。 -## 3. BF 算法代码实现 +## 3. Brute Force 算法代码实现 ```Python def bruteForce(T: str, p: str) -> int: @@ -33,15 +35,15 @@ def bruteForce(T: str, p: str) -> int: return -1 # 匹配失败,返回 -1 ``` -## 4. BF 算法分析 +## 4. Brute Force 算法分析 BF 算法非常简单,容易理解,但其效率很低。主要是因为在匹配过程中可能会出现回溯:当遇到一对字符不同时,模式串 `p` 直接回到开始位置,文本串也回到匹配开始位置的下一个位置,再重新开始比较。 -在回溯之后,文本串和模式串中一些部分的比较是没有必要的。由于这种操作策略,导致 BF 算法的效率很低。最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行 `m` 次字符对比,总共需要进行 `n - m + 1` 轮比较,总的比较次数为 `m * (n - m + 1) `。所以 BF 算法的最坏时间复杂度为 $O(m * n)$。 +在回溯之后,文本串和模式串中一些部分的比较是没有必要的。由于这种操作策略,导致 BF 算法的效率很低。最坏情况是每一趟比较都在模式串的最后遇到了字符不匹配的情况,每轮比较需要进行 `m` 次字符对比,总共需要进行 `n - m + 1` 轮比较,总的比较次数为 `m * (n - m + 1) `。所以 BF 算法的最坏时间复杂度为 $O(m \times n)$。 在最理想的情况下(第一次匹配直接匹配成功),BF 算法的最佳时间复杂度是 $O(m)$。 -在一般情况下,根据等概率原则,平均搜索次数为 $\frac{(n + m)}{2},ドル所以 BF 算法的平均时间复杂度为 $O(n + m)$。 +在一般情况下,根据等概率原则,平均搜索次数为 $\frac{(n + m)}{2},ドル所以 Brute Force 算法的平均时间复杂度为 $O(n + m)$。 ## 参考资料 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md b/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md index 89111a75..539dfb99 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/02.String-Rabin-Karp.md @@ -1,22 +1,22 @@ -## 1. RK 算法介绍 +## 1. Rabin Karp 算法介绍 -RK 算法的全称叫 **「Rabin Karp 算法」**,是由它的两位发明者 Michael Oser Rabin 和 Richard Manning Karp 的名字来命名的。RK 算法是他们在 1987 年提出的、使用哈希函数以在文本中搜寻单个模式串的字符串搜索算法。 +> **Rabin Karp 算法**:简称为 RK 算法。是由它的两位发明者 Michael Oser Rabin 和 Richard Manning Karp 的名字来命名的。RK 算法是他们在 1987 年提出的、使用哈希函数以在文本中搜寻单个模式串的字符串搜索算法。 +> +> - **Rabin Karp 算法思想**:对于给定文本串 `T` 与模式串 `p`,通过滚动哈希算快速筛选出与模式串 `p` 不匹配的文本位置,然后在其余位置继续检查匹配项。 -> **RK 算法思想**:对于给定文本串 `T` 与模式串 `p`,通过滚动哈希算快速筛选出与模式串 `p` 不匹配的文本位置,然后在其余位置继续检查匹配项。 +## 2. Rabin Karp 算法步骤 -## 2. RK 算法步骤 +### 2.1 Rabin Karp 算法整体步骤 -### 2.1 RK 算法整体步骤 - -- 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -- 通过滚动哈希算法求出模式串 `p` 的哈希值 `hash_p`。 -- 再通过滚动哈希算法对文本串 `T` 中 `n - m + 1` 个子串分别求哈希值 `hash_t`。 -- 然后逐个与模式串的哈希值比较大小。 - - 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 不同,则说明两者不匹配,则继续向后匹配。 - - 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 - - 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 - - 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 -- 比较到末尾,如果仍未成功匹配,则说明文本串 `T` 中不包含模式串 `p`,方法返回 `-1`。 +1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 通过滚动哈希算法求出模式串 `p` 的哈希值 `hash_p`。 +3. 再通过滚动哈希算法对文本串 `T` 中 `n - m + 1` 个子串分别求哈希值 `hash_t`。 +4. 然后逐个与模式串的哈希值比较大小。 + 1. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 不同,则说明两者不匹配,则继续向后匹配。 + 2. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 + 1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 + 2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 +5. 比较到末尾,如果仍未成功匹配,则说明文本串 `T` 中不包含模式串 `p`,方法返回 `-1`。 ### 2.2 滚动哈希算法 @@ -56,7 +56,7 @@ $\begin{align} Hash(ate) &= (Hash(cat) - c \times 26 \times 26) * 26 + e \times 因为哈希值过大会造成溢出,所以我们在计算过程中还要对结果取模。取模的值应该尽可能大,并且应该是质数,这样才能减少哈希碰撞的概率。 -## 3. RK 算法代码实现 +## 3. Rabin Karp 算法代码实现 ```Python # T 为文本串,p 为模式串,d 为字符集的字符种类数,q 为质数 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md b/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md index 789357ad..a052f372 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/03.String-KMP.md @@ -1,8 +1,8 @@ ## 1. KMP 算法介绍 -KMP 算法的全叫做 **「Knuth Morris Pratt 算法」**,是由它的三位发明者 Donald Knuth、James H. Morris、 Vaughan Pratt 的名字来命名的。KMP 算法是他们三人在 1977 年联合发表的。 - -> **KMP 算法思想**:对于给定文本串 `T` 与模式串 `p`,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 +> **KMP 算法**:全称叫做 **「Knuth Morris Pratt 算法」**,是由它的三位发明者 Donald Knuth、James H. Morris、 Vaughan Pratt 的名字来命名的。KMP 算法是他们三人在 1977 年联合发表的。 +> +> - **KMP 算法思想**:对于给定文本串 `T` 与模式串 `p`,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 ### 1.1 朴素匹配算法的缺陷 @@ -88,13 +88,13 @@ KMP 算法就是使用了这样的思路,对模式串 `p` 进行了预处理 ### 3.2 KMP 算法整体步骤 -- 根据 `next` 数组的构造步骤生成「前缀表」`next`。 -- 使用两个指针 `i`、`j`,其中 `i` 指向文本串中当前匹配的位置,`j` 指向模式串中当前匹配的位置。初始时,`i = 0`,`j = 0`。 -- 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 `j = next[j - 1]`,直到 `j == 0` 时或前缀匹配成功时停止回退。 -- 如果当前模式串前缀匹配成功,则令模式串向右移动 `1` 位,即 `j += 1`。 -- 如果当前模式串 **完全** 匹配成功,则返回模式串 `p` 在文本串 `T` 中的开始位置,即 `i - j + 1`。 -- 如果还未完全匹配成功,则令文本串向右移动 `1` 位,即 `i += 1`,然后继续匹配。 -- 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 `-1`。 +1. 根据 `next` 数组的构造步骤生成「前缀表」`next`。 +2. 使用两个指针 `i`、`j`,其中 `i` 指向文本串中当前匹配的位置,`j` 指向模式串中当前匹配的位置。初始时,`i = 0`,`j = 0`。 +3. 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 `j = next[j - 1]`,直到 `j == 0` 时或前缀匹配成功时停止回退。 +4. 如果当前模式串前缀匹配成功,则令模式串向右移动 `1` 位,即 `j += 1`。 +5. 如果当前模式串 **完全** 匹配成功,则返回模式串 `p` 在文本串 `T` 中的开始位置,即 `i - j + 1`。 +6. 如果还未完全匹配成功,则令文本串向右移动 `1` 位,即 `i += 1`,然后继续匹配。 +7. 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 `-1`。 ## 3. KMP 算法代码实现 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md b/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md index 0953a84f..80224248 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/04.String-Boyer-Moore.md @@ -1,8 +1,8 @@ -## 1. BM 算法介绍 +## 1. Boyer Moore 算法介绍 -BM 算法的全称叫做 **「Boyer Moore 算法」**,是由它的两位发明者 Robert S. Boyer 和 J Strother Moore 的名字来命名的。BM 算法是他们在 1977 年提出的高效字符串搜索算法。在实际应用中,比 KMP 算法要快 3~5 倍。 - -> **BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 +> **Boyer Moore 算法**:简称为 BM 算法,是由它的两位发明者 Robert S. Boyer 和 J Strother Moore 的名字来命名的。BM 算法是他们在 1977 年提出的高效字符串搜索算法。在实际应用中,比 KMP 算法要快 3~5 倍。 +> +> - **BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 BM 算法的精髓在于使用了两种不同的启发策略来计算后移位数:**「坏字符规则(The Bad Character Rule)」** 和 **「好后缀规则(The Good Suffix Shift Rule)」**。 @@ -12,7 +12,7 @@ BM 算法的精髓在于使用了两种不同的启发策略来计算后移位 下面我们来讲解一下 BF 算法中的两种不同启发策略:「坏字符规则」和「好后缀规则」。 -## 2. BM 算法启发策略 +## 2. Boyer Moore 算法启发策略 ### 2.1 坏字符规则 @@ -56,7 +56,7 @@ BM 算法的精髓在于使用了两种不同的启发策略来计算后移位  -## 3. BM 算法匹配过程示例 +## 3. Boyer Moore 算法匹配过程示例 下面我们根据 J Strother Moore 教授给出的例子,先来介绍一下 BF 算法的匹配过程,顺便加深对 **「坏字符规则」** 和 **「好后缀规则」** 的理解。 @@ -114,23 +114,22 @@ BM 算法的精髓在于使用了两种不同的启发策略来计算后移位 11. 继续从模式串的尾部开始逐位比较,发现模式串全部匹配,于是搜索结束,返回模式串在文本串中的位置。 -## 4. BM 算法步骤 +## 4. Boyer Moore 算法步骤 整个 BM 算法步骤描述如下: -- 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 - -- 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 -- 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 - - 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 - - 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - - 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: - - 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 - - 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 - - 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 -- 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 + 1. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: + 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 + 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 + 3. 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 -## 5. BM 算法代码实现 +## 5. Boyer Moore 算法代码实现 BM 算法的匹配过程实现起来并不是很难,而整个算法实现的难点在于预处理阶段的「生成坏字符位置表」和「生成好后缀规则后移位数表」这两步上。尤其是「生成好后缀规则后移位数表」,实现起来十分复杂。下面我们一一进行讲解。 @@ -219,7 +218,7 @@ def generageGoodSuffixList(p: str): return gs_list ``` -### 5.3 BM 算法整体代码实现 +### 5.3 Boyer Moore 算法整体代码实现 ```Python # BM 匹配算法 @@ -291,7 +290,7 @@ print(boyerMoore("abbcfdddbddcaddebc", "aaaaa")) print(boyerMoore("", "")) ``` -## 6. BM 算法分析 +## 6. Boyer Moore 算法分析 - BM 算法在预处理阶段的时间复杂度为 $O(n + \sigma),ドル其中 $\sigma$ 是字符集的大小。 - BM 算法在搜索阶段最好情况是每次匹配时,模式串 `p` 中不存在与文本串 `T` 中第一个匹配的字符。这时的时间复杂度为 $O(n / m)$。 diff --git a/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md b/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md index 32742481..e4745452 100644 --- a/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md +++ b/Contents/06.String/02.String-Single-Pattern-Matching/05.String-Horspool.md @@ -1,8 +1,8 @@ ## 1.1 Horspool 算法介绍 -**「Horspool 算法」** 是一种在字符串中查找子串的算法,它是由 Nigel Horspool 教授于 1980 年出版的,是首个对 Boyer Moore 算法进行简化的算法。 - -> **Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 +> **Horspool 算法**:是一种在字符串中查找子串的算法,它是由 Nigel Horspool 教授于 1980 年出版的,是首个对 Boyer Moore 算法进行简化的算法。 +> +> - **Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 可以看出,Horspool 算法思想和 Boyer Moore 算法思想是一致的。Horspool 算法是在 Boyer Moore 算法思想基础上改进了「坏字符规则」。当文本串 `T` 中某个字符跟模式串 `p` 的某个字符不匹配时,可以模式串 `p` 快速向右移动。 @@ -25,14 +25,14 @@ 整个 Horspool 算法步骤描述如下: -- 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 -- 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 -- 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 - - 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 - - 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 - - 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: - - 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 -- 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: + 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 ## 3. Horspool 算法代码实现 diff --git "a/Solutions/0028. 346円211円276円345円207円272円345円255円227円347円254円246円344円270円262円344円270円255円347円254円254円344円270円200円344円270円252円345円214円271円351円205円215円351円241円271円347円232円204円344円270円213円346円240円207円.md" "b/Solutions/0028. 346円211円276円345円207円272円345円255円227円347円254円246円344円270円262円344円270円255円347円254円254円344円270円200円344円270円252円345円214円271円351円205円215円351円241円271円347円232円204円344円270円213円346円240円207円.md" index daa3dca8..8d2edb38 100644 --- "a/Solutions/0028. 346円211円276円345円207円272円345円255円227円347円254円246円344円270円262円344円270円255円347円254円254円344円270円200円344円270円252円345円214円271円351円205円215円351円241円271円347円232円204円344円270円213円346円240円207円.md" +++ "b/Solutions/0028. 346円211円276円345円207円272円345円255円227円347円254円246円344円270円262円344円270円255円347円254円254円344円270円200円344円270円252円345円214円271円351円205円215円351円241円271円347円232円204円344円270円213円346円240円207円.md" @@ -20,13 +20,30 @@ ```Python 输入:haystack = "hello", needle = "ll" 输出:2 +解释:"sad" 在下标 0 和 6 处匹配。第一个匹配项的下标是 0 ,所以返回 0 。 + +输入:haystack = "leetcode", needle = "leeto" +输出:-1 +解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。 ``` ## 解题思路 字符串匹配的经典题目。常见的字符串匹配算法有:BF(Brute Force)算法、RK(Robin-Karp)算法、KMP(Knuth Morris Pratt)算法、BM(Boyer Moore)算法、Horspool 算法、Sunday 算法等。 -### 思路 1:BF(Brute Force)算法代码 +### 思路 1:BF(Brute Force)算法 + +**BF 算法思想**:对于给定文本串 `T` 与模式串 `p`,从文本串的第一个字符开始与模式串 `p` 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 `T` 的第二个字符起重新和模式串 `p` 进行比较。依次类推,直到模式串 `p` 中每个字符依次与文本串 `T` 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。 + +BF 算法具体步骤如下: + +1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 同时遍历文本串 `T` 和模式串 `p`,先将 `T[0]` 与 `p[0]` 进行比较。 + 1. 如果相等,则继续比较 `T[1]` 和 `p[1]`。以此类推,一直到模式串 `p` 的末尾 `p[m - 1]` 为止。 + 2. 如果不相等,则将文本串 `T` 移动到上次匹配开始位置的下一个字符位置,模式串 `p` 则回退到开始位置,再依次进行比较。 +3. 当遍历完文本串 `T` 或者模式串 `p` 的时候停止搜索。 + +### 思路 1:代码 ```Python class Solution: @@ -50,8 +67,28 @@ class Solution: return -1 ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:平均时间复杂度为 $O(n + m),ドル最坏时间复杂度为 $O(m \times n)$。其中文本串 $T$ 的长度为 $n,ドル模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(1)$。 + +### 思路 2:RK(Robin Karp)算法 + +**RK 算法思想**:对于给定文本串 `T` 与模式串 `p`,通过滚动哈希算快速筛选出与模式串 `p` 不匹配的文本位置,然后在其余位置继续检查匹配项。 + +RK 算法具体步骤如下: + +1. 对于给定的文本串 `T` 与模式串 `p`,求出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 通过滚动哈希算法求出模式串 `p` 的哈希值 `hash_p`。 +3. 再通过滚动哈希算法对文本串 `T` 中 `n - m + 1` 个子串分别求哈希值 `hash_t`。 +4. 然后逐个与模式串的哈希值比较大小。 + 1. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 不同,则说明两者不匹配,则继续向后匹配。 + 2. 如果当前子串的哈希值 `hash_t` 与模式串的哈希值 `hash_p` 相等,则验证当前子串和模式串的每个字符是否真的相等(避免哈希冲突)。 + 1. 如果当前子串和模式串的每个字符相等,则说明当前子串和模式串匹配。 + 2. 如果当前子串和模式串的每个字符不相等,则说明两者不匹配,继续向后匹配。 +5. 比较到末尾,如果仍未成功匹配,则说明文本串 `T` 中不包含模式串 `p`,方法返回 `-1`。 -### 思路 2:RK(Robin-Karp)算法代码 +### 思路 2:代码 ```Python class Solution: @@ -75,7 +112,26 @@ class Solution: return rabinKarp(haystack, needle) ``` -### 思路 3:KMP(Knuth Morris Pratt)算法代码 +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n,ドル模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 + +### 思路 3:KMP(Knuth Morris Pratt)算法 + +**KMP 算法思想**:对于给定文本串 `T` 与模式串 `p`,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。 + +KMP 算法具体步骤如下: + +1. 根据 `next` 数组的构造步骤生成「前缀表」`next`。 +2. 使用两个指针 `i`、`j`,其中 `i` 指向文本串中当前匹配的位置,`j` 指向模式串中当前匹配的位置。初始时,`i = 0`,`j = 0`。 +3. 循环判断模式串前缀是否匹配成功,如果模式串前缀匹配不成功,将模式串进行回退,即 `j = next[j - 1]`,直到 `j == 0` 时或前缀匹配成功时停止回退。 +4. 如果当前模式串前缀匹配成功,则令模式串向右移动 `1` 位,即 `j += 1`。 +5. 如果当前模式串 **完全** 匹配成功,则返回模式串 `p` 在文本串 `T` 中的开始位置,即 `i - j + 1`。 +6. 如果还未完全匹配成功,则令文本串向右移动 `1` 位,即 `i += 1`,然后继续匹配。 +7. 如果直到文本串遍历完也未完全匹配成功,则说明匹配失败,返回 `-1`。 + +### 思路 3:代码 ```Python class Solution: @@ -120,7 +176,29 @@ class Solution: return kmp(haystack, needle) ``` -### 思路 4:BM(Boyer Moore)算法代码 +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n + m),ドル其中文本串 $T$ 的长度为 $n,ドル模式串 $p$ 的长度为 $m$。 +- **空间复杂度**:$O(m)$。 + +### 思路 4:BM(Boyer Moore)算法 + +**BM 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够直接尽可能地跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +BM 算法具体步骤如下: + +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成坏字符位置表 `bc_table` 和好后缀规则后移位数表 `gs_talbe`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始进行逐位比较。 + 1. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置 `T[i + j]` 上的字符与 `p[j]` 不相同,则: + 1. 根据坏字符位置表计算出在「坏字符规则」下的移动距离 `bad_move`。 + 2. 根据好后缀规则后移位数表计算出在「好后缀规则」下的移动距离 `good_mode`。 + 3. 取两种移动距离的最大值,然后对模式串进行移动,即 `i += max(bad_move, good_move)`。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 + +### 思路 4:代码 ```Python class Solution: @@ -182,7 +260,27 @@ class Solution: return boyerMoore(haystack, needle) ``` -### 思路 5:Horspool 算法代码 +### 思路 4:复杂度分析 + +- **时间复杂度**:$O(n + \sigma),ドル其中文本串 $T$ 的长度为 $n,ドル字符集的大小是 $\sigma$。 +- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 + +### 思路 5:Horspool 算法 + +**Horspool 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +Horspool 算法具体步骤如下: + +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: + 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 + +### 思路 5:代码 ```Python class Solution: @@ -215,7 +313,27 @@ class Solution: return horspool(haystack, needle) ``` -### 思路 6:Sunday 算法代码 +### 思路 5:复杂度分析 + +- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$。 +- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 + +### 思路 6:Sunday 算法 + +**Sunday 算法思想**:对于给定文本串 `T` 与模式串 `p`,先对模式串 `p` 进行预处理。然后在匹配的过程中,当发现文本串 `T` 的某个字符与模式串 `p` 不匹配的时候,根据启发策略,能够尽可能的跳过一些无法匹配的情况,将模式串多向后滑动几位。 + +Sunday 算法具体步骤如下: + +1. 计算出文本串 `T` 的长度为 `n`,模式串 `p` 的长度为 `m`。 +2. 先对模式串 `p` 进行预处理,生成后移位数表 `bc_table`。 +3. 将模式串 `p` 的头部与文本串 `T` 对齐,将 `i` 指向文本串开始位置,即 `i = 0`。`j` 指向模式串末尾位置,即 `j = m - 1`,然后从模式串末尾位置开始比较。 + 1. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 相同,则继续比较前一位字符。 + 1. 如果模式串全部匹配完毕,则返回模式串 `p` 在文本串中的开始位置 `i`。 + 2. 如果文本串对应位置的字符 `T[i + j]` 与模式串对应字符 `p[j]` 不同,则: + 1. 根据后移位数表 `bc_table` 和模式串末尾位置对应的文本串上的字符 `T[i + m - 1]` ,计算出可移动距离 `bc_table[T[i + m - 1]]`,然后将模式串进行后移。 +4. 如果移动到末尾也没有找到匹配情况,则返回 `-1`。 + +### 思路 6:代码 ```Python class Solution: @@ -248,4 +366,9 @@ class Solution: return bc_table return sunday(haystack, needle) -``` \ No newline at end of file +``` + +### 思路 6:复杂度分析 + +- **时间复杂度**:$O(n)$。其中文本串 $T$ 的长度为 $n$。 +- **空间复杂度**:$O(m)$。其中模式串 $p$ 的长度为 $m$。 \ No newline at end of file diff --git "a/Solutions/0036. 346円234円211円346円225円210円347円232円204円346円225円260円347円213円254円.md" "b/Solutions/0036. 346円234円211円346円225円210円347円232円204円346円225円260円347円213円254円.md" index eb2c927f..4f80f084 100644 --- "a/Solutions/0036. 346円234円211円346円225円210円347円232円204円346円225円260円347円213円254円.md" +++ "b/Solutions/0036. 346円234円211円346円225円210円347円232円204円346円225円260円347円213円254円.md" @@ -45,11 +45,11 @@ 判断数独有效,需要分别看每一行、每一列、每一个 `3 * 3` 的小方格是否出现了重复数字,如果都没有出现重复数字就是一个有效的数独,如果出现了重复数字则不是有效的数独。 -- 用 `3` 个 `9 * 9` 的数组分别来表示该数字是否在所在的行,所在的列,所在的方格出现过。其中方格角标的计算用 `box[(i/3)*3+(j/3)][n]` 来表示。 +- 用 `3` 个 `9 * 9` 的数组分别来表示该数字是否在所在的行,所在的列,所在的方格出现过。其中方格角标的计算用 `box[(i / 3) * 3 + (j / 3)][n]` 来表示。 - 双重循环遍历数独矩阵。如果对应位置上的数字如果已经在在所在的行 / 列 / 方格出现过,则返回 `False`。 - 遍历完没有重复出现,则返回 `Ture`。 -### 思路 1:哈希表代码 +### 思路 1:代码 ```Python class Solution: @@ -76,3 +76,7 @@ class Solution: return True ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(1)$。数独总共 81 个单元格,对每个单元格遍历一次,可以看做是常数级的时间复杂度。 +- **空间复杂度**:$O(1)$。使用 81 个单位空间,可以看做是常数级的空间复杂度。 diff --git "a/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" "b/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" index 4ce65e97..cbaaba85 100644 --- "a/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" +++ "b/Solutions/0125. 351円252円214円350円257円201円345円233円236円346円226円207円344円270円262円.md" @@ -67,4 +67,3 @@ class Solution: - **时间复杂度**:$O(len(s))$。 - **空间复杂度**:$O(len(s))$。 - diff --git "a/Solutions/0217. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円.md" "b/Solutions/0217. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円.md" index f4869405..c8a49eb1 100644 --- "a/Solutions/0217. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円.md" +++ "b/Solutions/0217. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円.md" @@ -19,6 +19,9 @@ ```Python 输入:nums = [1,2,3,1] 输出:True + +输入:nums = [1,2,3,4] +输出:False ``` ## 解题思路 @@ -43,6 +46,11 @@ class Solution: return False ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + ### 思路 2:集合 - 使用一个 `set` 集合存储数组中所有元素。 @@ -57,6 +65,11 @@ class Solution: return len(set(nums)) != len(nums) ``` +### 思路 2:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 + ### 思路 3:排序 - 对数组进行排序。 @@ -75,3 +88,8 @@ class Solution: return True return False ``` + +### 思路 3:复杂度分析 + +- **时间复杂度**:$O(n \times \log n)$。 +- **空间复杂度**:$O(1)$。 \ No newline at end of file diff --git "a/Solutions/0219. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 II.md" "b/Solutions/0219. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 II.md" index b7a54100..4b72e8d4 100644 --- "a/Solutions/0219. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 II.md" +++ "b/Solutions/0219. 345円255円230円345円234円250円351円207円215円345円244円215円345円205円203円347円264円240円 II.md" @@ -34,18 +34,22 @@ - 判断哈希表长度是否超过了 `k`,如果超过了 `k`,则删除哈希表中最旧的元素 `nums[i - k]`。 - 如果遍历完仍旧找不到,则返回 `False`。 -### 思路 1:哈希表代码 +### 思路 1:代码 ```Python class Solution: def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool: - nums_set = set() + nums_dict = dict() for i in range(len(nums)): - if nums[i] in nums_set: + if nums[i] in nums_dict: return True - nums_set.add(nums[i]) - if len(nums_set)> k: - nums_set.remove(nums[i - k]) + nums_dict[nums[i]] = 1 + if len(nums_dict)> k: + del nums_dict[nums[i - k]] return False ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 diff --git "a/Solutions/0350. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円 II.md" "b/Solutions/0350. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円 II.md" index e1c970d6..44264fdd 100644 --- "a/Solutions/0350. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円 II.md" +++ "b/Solutions/0350. 344円270円244円344円270円252円346円225円260円347円273円204円347円232円204円344円272円244円351円233円206円 II.md" @@ -5,15 +5,36 @@ ## 题目大意 -给定两个数组,编写一个函数来计算它们的交集。输出结果中,需要考虑元素出现的次数。 +**描述**:给定两个数组 `nums1` 和 `nums2`。 + +**要求**:返回两个数组的交集。可以不考虑输出结果的顺序。 + +**说明**: + +- 输出结果中,每个元素出现的次数,应该与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。 +- 1ドル \le nums1.length, nums2.length \le 1000$。 +- 0ドル \le nums1[i], nums2[i] \le 1000$。 + +**示例**: + +```Python +输入:nums1 = [1,2,2,1], nums2 = [2,2] +输出:[2,2] + + +输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] +输出:[4,9] +``` ## 解题思路 -先遍历第一个数组,利用字典来存放第一个数组的元素出现次数。 +### 思路 1:哈希表 -然后遍历第二个数组,如果字典中存在该元素,则将该元素加入到答案数组中,并减少字典中该元素出现的次数。 +1. 先遍历第一个数组,利用字典来存放第一个数组的元素出现次数。 +2. 然后遍历第二个数组,如果字典中存在该元素,则将该元素加入到答案数组中,并减少字典中该元素出现的次数。 +3. 遍历完之后,返回答案数组。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -32,3 +53,7 @@ class Solution: return nums ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0557. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円344円270円255円347円232円204円345円215円225円350円257円215円 III.md" "b/Solutions/0557. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円344円270円255円347円232円204円345円215円225円350円257円215円 III.md" index 96f2585f..2ab8fd9f 100644 --- "a/Solutions/0557. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円344円270円255円347円232円204円345円215円225円350円257円215円 III.md" +++ "b/Solutions/0557. 345円217円215円350円275円254円345円255円227円347円254円246円344円270円262円344円270円255円347円232円204円345円215円225円350円257円215円 III.md" @@ -5,17 +5,40 @@ ## 题目大意 -给定一个字符串 s,将字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。 +**描述**:给定一个字符串 `s`。 + +**要求**:将字符串中每个单词的字符顺序进行反装,同时仍保留空格和单词的初始顺序。 + +**说明**: + +- 1ドル \le s.length \le 5 * 10^4$。 +- `s` 包含可打印的 ASCII 字符。 +- `s` 不包含任何开头或结尾空格。 +- `s` 里至少有一个词。 +- `s` 中的所有单词都用一个空格隔开。 + +**示例**: + +```Python +输入:s = "Let's take LeetCode contest" +输出:"s'teL ekat edoCteeL tsetnoc" + + +输入: s = "God Ding" +输出:"doG gniD" +``` ## 解题思路 +### 思路 1:使用额外空间 + 因为 Python 的字符串是不可变的,所以在原字符串空间上进行切换顺序操作肯定是不可行的了。但我们可以利用切片方法。 -- 将字符串按空格进行分割,分割成一个个的单词。 -- 再将每个单词进行反转。 -- 然后再将每个单词连接起来。 +1. 将字符串按空格进行分割,分割成一个个的单词。 +2. 再将每个单词进行反转。 +3. 最后将每个单词连接起来。 -## 代码 +### 思路 1:代码 ```Python class Solution: @@ -23,3 +46,7 @@ class Solution: return " ".join(word[::-1] for word in s.split(" ")) ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(n)$。 +- **空间复杂度**:$O(n)$。 \ No newline at end of file diff --git "a/Solutions/0706. 350円256円276円350円256円241円345円223円210円345円270円214円346円230円240円345円260円204円.md" "b/Solutions/0706. 350円256円276円350円256円241円345円223円210円345円270円214円346円230円240円345円260円204円.md" index 4b6a5b62..ba9a19df 100644 --- "a/Solutions/0706. 350円256円276円350円256円241円345円223円210円345円270円214円346円230円240円345円260円204円.md" +++ "b/Solutions/0706. 350円256円276円350円256円241円345円223円210円345円270円214円346円230円240円345円260円204円.md" @@ -5,22 +5,59 @@ ## 题目大意 -要求不使用任何内建的哈希表库设计一个哈希映射(HashMap)。 +**要求**:不使用任何内建的哈希表库设计一个哈希映射(`HashMap`)。 -满足以下操作: +需要满足以下操作: -- MyHashMap() 用空映射初始化对象 -- void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value 。 -- int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1 。 -- void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value 。 +- `MyHashMap()` 用空映射初始化对象。 +- `void put(int key, int value) 向 HashMap` 插入一个键值对 `(key, value)` 。如果 `key` 已经存在于映射中,则更新其对应的值 `value`。 +- `int get(int key)` 返回特定的 `key` 所映射的 `value`;如果映射中不包含 `key` 的映射,返回 `-1`。 +- `void remove(key)` 如果映射中存在 key 的映射,则移除 `key` 和它所对应的 `value` 。 + +**说明**: + +- 0ドル \le key, value \le 10^6$。 +- 最多调用 10ドル^4$ 次 `put`、`get` 和 `remove` 方法。 + +**示例**: + +```Python +输入: +["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"] +[[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]] +输出: +[null, null, null, 1, -1, null, 1, null, -1] + +解释: +MyHashMap myHashMap = new MyHashMap(); +myHashMap.put(1, 1); // myHashMap 现在为 [[1,1]] +myHashMap.put(2, 2); // myHashMap 现在为 [[1,1], [2,2]] +myHashMap.get(1); // 返回 1 ,myHashMap 现在为 [[1,1], [2,2]] +myHashMap.get(3); // 返回 -1(未找到),myHashMap 现在为 [[1,1], [2,2]] +myHashMap.put(2, 1); // myHashMap 现在为 [[1,1], [2,1]](更新已有的值) +myHashMap.get(2); // 返回 1 ,myHashMap 现在为 [[1,1], [2,1]] +myHashMap.remove(2); // 删除键为 2 的数据,myHashMap 现在为 [[1,1]] +myHashMap.get(2); // 返回 -1(未找到),myHashMap 现在为 [[1,1]] +``` ## 解题思路 -和 [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) 类似。同样利用「数组+链表」的方式实现哈希集合,只不过之前的存储元素变为了 key:value。 +### 思路 1:链地址法 + +和 [0705. 设计哈希集合](https://leetcode.cn/problems/design-hashset/) 类似。这里我们使用「链地址法」来解决哈希冲突。即利用「数组 + 链表」的方式实现哈希集合。 -定义一个一维长度为 buckets 的二维数组 table。第一维度用于计算哈希函数,为 key 分桶。第二个维度用于寻找 key 存放的具体位置。第二维度的数组会根据 key 值动态增长,模拟真正的链表。 +1. 定义哈希表长度 `buckets` 为 `1003`。 +2. 定义一个一维长度为 `buckets` 的二维数组 `table`。其中第一维度用于计算哈希函数,为关键字 `key` 分桶。第二个维度用于存放 `key` 和对应的 `value`。第二维度的数组会根据 `key` 值动态增长,用数组模拟真正的链表。 +3. 定义一个 `hash(key)` 的方法,将 `key` 转换为对应的地址 `hash_key`。 +4. 进行 `put` 操作时,根据 `hash(key)` 方法,获取对应的地址 `hash_key`。然后遍历 `hash_key` 对应的数组元素,查找与 `key` 值一样的元素。 + 1. 如果找到与 `key` 值相同的元素,则更改该元素对应的 `value` 值。 + 2. 如果没找到与 `key` 值相同的元素,则在第二维数组 `table[hask_key]` 中增加元素,元素为 `(key, value)` 组成的元组。 -## 代码 +5. 进行 `get` 操作跟 `put` 操作差不多。根据 `hash(key)` 方法,获取对应的地址 `hash_key`。然后遍历 `hash_key` 对应的数组元素,查找与 `key` 值一样的元素。 + 1. 如果找到与 `key` 值相同的元素,则返回该元素对应的 `value`。 + 2. 如果没找到与 `key` 值相同的元素,则返回 `-1`。 + +### 思路 1:代码 ```Python class MyHashMap: @@ -59,3 +96,7 @@ class MyHashMap: return ``` +### 思路 1:复杂度分析 + +- **时间复杂度**:$O(\frac{n}{b})$。其中 $n$ 为哈希表中元素数量,$b$ 为链表的数量。 +- **空间复杂度**:$O(n + b)$。 \ No newline at end of file