Merge pull request #1781 from juguagua/leetcode-modify-the-code-of-the-string-part

完善字符串部分代码和文本
This commit is contained in:
程序员Carl
2022-11-27 09:54:42 +08:00
committed by GitHub
7 changed files with 120 additions and 116 deletions

View File

@ -94,7 +94,7 @@ next数组就是一个前缀表prefix table
**前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。** **前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。**
为了清楚了解前缀表的来历,我们来举一个例子: 为了清楚了解前缀表的来历,我们来举一个例子:
要在文本串aabaabaafa 中查找是否出现过一个模式串aabaaf。 要在文本串aabaabaafa 中查找是否出现过一个模式串aabaaf。
@ -110,9 +110,9 @@ next数组就是一个前缀表prefix table
![KMP详解1](https://code-thinking.cdn.bcebos.com/gifs/KMP%E7%B2%BE%E8%AE%B21.gif) ![KMP详解1](https://code-thinking.cdn.bcebos.com/gifs/KMP%E7%B2%BE%E8%AE%B21.gif)
动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说 动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说
可以看出文本串中第六个字符b 和 模式串的第六个字符f不匹配了。如果暴力匹配发现不匹配,此时就要从头匹配了。 可以看出文本串中第六个字符b 和 模式串的第六个字符f不匹配了。如果暴力匹配发现不匹配此时就要从头匹配了。
但如果使用前缀表就不会从头匹配而是从上次已经匹配的内容开始匹配找到了模式串中第三个字符b继续开始匹配。 但如果使用前缀表就不会从头匹配而是从上次已经匹配的内容开始匹配找到了模式串中第三个字符b继续开始匹配。
@ -157,7 +157,7 @@ next数组就是一个前缀表prefix table
以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要! 以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!
**下标5之前这部分的字符串也就是字符串aabaa的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面新匹配就可以了。** **下标5之前这部分的字符串也就是字符串aabaa的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面新匹配就可以了。**
所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。 所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。
@ -199,7 +199,7 @@ next数组就是一个前缀表prefix table
所以要看前一位的 前缀表的数值。 所以要看前一位的 前缀表的数值。
前一个字符的前缀表的数值是2把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。 前一个字符的前缀表的数值是2把下标移动到下标2的位置继续比配。 可以再反复看一下上面的动画。
最后就在文本串中找到了和模式串匹配的子串了。 最后就在文本串中找到了和模式串匹配的子串了。
@ -211,7 +211,7 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
为什么这么做呢,其实也是很多文章视频没有解释清楚的地方。 为什么这么做呢,其实也是很多文章视频没有解释清楚的地方。
其实**这并不涉及到KMP的原理而是具体实现next数组可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1。** 其实**这并不涉及到KMP的原理而是具体实现next数组可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1。**
后面我会提供两种不同的实现代码,大家就明白了。 后面我会提供两种不同的实现代码,大家就明白了。
@ -231,7 +231,7 @@ next数组就可以是前缀表但是很多实现都是把前缀表统一减
其中n为文本串长度m为模式串长度因为在匹配的过程中根据前缀表不断调整匹配的位置可以看出匹配的过程是O(n)之前还要单独生成next数组时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。 其中n为文本串长度m为模式串长度因为在匹配的过程中根据前缀表不断调整匹配的位置可以看出匹配的过程是O(n)之前还要单独生成next数组时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
暴力的解法显而易见是O(n × m),所以**KMP在字符串匹配中极大提高搜索的效率。** 暴力的解法显而易见是O(n × m),所以**KMP在字符串匹配中极大提高搜索的效率。**
为了和力扣题目28.实现strStr保持一致方便大家理解以下文章统称haystack为文本串, needle为模式串。 为了和力扣题目28.实现strStr保持一致方便大家理解以下文章统称haystack为文本串, needle为模式串。
@ -251,7 +251,7 @@ void getNext(int* next, const string& s)
2. 处理前后缀不相同的情况 2. 处理前后缀不相同的情况
3. 处理前后缀相同的情况 3. 处理前后缀相同的情况
接下来我们详解详解一下。 接下来我们详解一下。
1. 初始化: 1. 初始化:

View File

@ -119,7 +119,7 @@ void removeExtraSpaces(string& s) {
1. leetcode上的测试集里字符串的长度不够长如果足够长性能差距会非常明显。 1. leetcode上的测试集里字符串的长度不够长如果足够长性能差距会非常明显。
2. leetcode的测程序耗时不是很准确的。 2. leetcode的测程序耗时不是很准确的。
版本一的代码是比较如何一般思考过程,就是 先移除字符串的空格,移除中间的,移除后面部分。 版本一的代码是一般思考过程,就是 先移除字符串的空格,移除中间的,移除后面部分。
不过其实还可以优化,这部分和[27.移除元素](https://programmercarl.com/0027.移除元素.html)的逻辑是一样一样的,本题是移除空格,而 27.移除元素 就是移除元素。 不过其实还可以优化,这部分和[27.移除元素](https://programmercarl.com/0027.移除元素.html)的逻辑是一样一样的,本题是移除空格,而 27.移除元素 就是移除元素。
@ -145,7 +145,7 @@ void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间
此时我们已经实现了removeExtraSpaces函数来移除冗余空格。 此时我们已经实现了removeExtraSpaces函数来移除冗余空格。
实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)和[541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)里已经讲过了。 实现反转字符串的功能,支持反转字符串子区间,这个实现我们分别在[344.反转字符串](https://programmercarl.com/0344.反转字符串.html)和[541.反转字符串II](https://programmercarl.com/0541.反转字符串II.html)里已经讲过了。
代码如下: 代码如下:
@ -451,6 +451,7 @@ class Solution:
tmp.append(s[left]) tmp.append(s[left])
left += 1 left += 1
return tmp return tmp
#2.翻转字符数组 #2.翻转字符数组
def reverse_string(self, nums, left, right): def reverse_string(self, nums, left, right):
while left < right: while left < right:
@ -458,6 +459,7 @@ class Solution:
left += 1 left += 1
right -= 1 right -= 1
return None return None
#3.翻转每个单词 #3.翻转每个单词
def reverse_each_word(self, nums): def reverse_each_word(self, nums):
start = 0 start = 0

View File

@ -46,17 +46,17 @@
## 移动匹配 ## 移动匹配
当一个字符串sabcabc内部重复的子串组成,那么这个字符串的结构一定是这样的: 当一个字符串sabcabc内部重复的子串组成,那么这个字符串的结构一定是这样的:
![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728104518.png) ![图一](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728104518.png)
也就是前后相同的子串组成。 也就是前后相同的子串组成。
那么既然前面有相同的子串,后面有相同的子串,用 s + s这样组成的字符串中后面的子串做前串前后的子串做后串就一定还能组成一个s如图 那么既然前面有相同的子串,后面有相同的子串,用 s + s这样组成的字符串中后面的子串做前串前后的子串做后串就一定还能组成一个s如图
![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728104931.png) ![图二](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728104931.png)
所以判断字符串s是否重复子串组成只要两个s拼接在一起里面还出现一个s的话就说明是重复子串组成。 所以判断字符串s是否重复子串组成只要两个s拼接在一起里面还出现一个s的话就说明是重复子串组成。
当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候**要刨除 s + s 的首字符和尾字符**这样避免在s+s中搜索出原来的s我们要搜索的是中间拼接出来的s。 当然,我们在判断 s + s 拼接的字符串里是否出现一个s的的时候**要刨除 s + s 的首字符和尾字符**这样避免在s+s中搜索出原来的s我们要搜索的是中间拼接出来的s。
@ -81,7 +81,7 @@ public:
## KMP ## KMP
### 为什么会使用KMP ### 为什么会使用KMP
以下使用KMP方式讲解强烈建议大家先把下两个视频看了理解KMP算法来看下面讲解,否则会很懵。 以下使用KMP方式讲解强烈建议大家先把下两个视频看了理解KMP算法来看下面讲解,否则会很懵。
* [视频讲解版帮你把KMP算法学个通透理论篇](https://www.bilibili.com/video/BV1PD4y1o7nd/) * [视频讲解版帮你把KMP算法学个通透理论篇](https://www.bilibili.com/video/BV1PD4y1o7nd/)
* [视频讲解版帮你把KMP算法学个通透求next数组代码篇](https://www.bilibili.com/video/BV1M5411j7Xx) * [视频讲解版帮你把KMP算法学个通透求next数组代码篇](https://www.bilibili.com/video/BV1M5411j7Xx)
@ -93,12 +93,12 @@ KMP算法中next数组为什么遇到字符不匹配的时候可以找到上一
那么 最长相同前后缀和重复子串的关系又有什么关系呢。 那么 最长相同前后缀和重复子串的关系又有什么关系呢。
可能很多录友又忘了 前缀和后缀的定义,回顾一下: 可能很多录友又忘了 前缀和后缀的定义,回顾一下:
* 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串; * 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
* 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串 * 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里字符串sabababab 来举例ab就是最小重复单位如图所示 在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里字符串sabababab 来举例ab就是最小重复单位如图所示
![图三](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728205249.png) ![图三](https://code-thinking-1253855093.file.myqcloud.com/pics/20220728205249.png)
@ -123,11 +123,11 @@ KMP算法中next数组为什么遇到字符不匹配的时候可以找到上一
### 简单推理 ### 简单推理
这里给出一个数推导,就容易理解很多。 这里给出一个数推导,就容易理解很多。
假设字符串s使用多个重复子串构成这个子串是最小重复单位重复出现的子字符串长度是x所以s是由n * x组成。 假设字符串s使用多个重复子串构成这个子串是最小重复单位重复出现的子字符串长度是x所以s是由n * x组成。
因为字符串s的最长相同前后缀的长度一定是不包含s本身所以 最长相同前后缀长度必然是m * x而且 n - m = 1这里如果不懂看上面的推理 因为字符串s的最长相同前后缀的长度一定是不包含s本身所以 最长相同前后缀长度必然是m * x而且 n - m = 1这里如果不懂看上面的推理
所以如果 nx % (n - m)x = 0就可以判定有重复出现的子字符串。 所以如果 nx % (n - m)x = 0就可以判定有重复出现的子字符串。

View File

@ -294,6 +294,8 @@ func reverseStr(s string, k int) string {
ss := []byte(s) ss := []byte(s)
length := len(s) length := len(s)
for i := 0; i < length; i += 2 * k { for i := 0; i < length; i += 2 * k {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if i + k <= length { if i + k <= length {
reverse(ss[i:i+k]) reverse(ss[i:i+k])
} else { } else {
@ -326,7 +328,7 @@ javaScript:
var reverseStr = function(s, k) { var reverseStr = function(s, k) {
const len = s.length; const len = s.length;
let resArr = s.split(""); let resArr = s.split("");
for(let i = 0; i < len; i += 2 * k) { for(let i = 0; i < len; i += 2 * k) { // 每隔 2k 个字符的前 k 个字符进行反转
let l = i - 1, r = i + k > len ? len : i + k; let l = i - 1, r = i + k > len ? len : i + k;
while(++l < --r) [resArr[l], resArr[r]] = [resArr[r], resArr[l]]; while(++l < --r) [resArr[l], resArr[r]] = [resArr[r], resArr[l]];
} }

View File

@ -36,7 +36,7 @@ i指向新长度的末尾j指向旧长度的末尾。
这么做有两个好处: 这么做有两个好处:
1. 不用申请新数组。 1. 不用申请新数组。
2. 从后向前填充元素,避免了从前后填充元素要来的 每次添加元素都要将添加元素之后的所有元素向后移动。 2. 从后向前填充元素,避免了从前后填充元素时,每次添加元素都要将添加元素之后的所有元素向后移动的问题
时间复杂度空间复杂度均超过100%的用户。 时间复杂度空间复杂度均超过100%的用户。

View File

@ -31,7 +31,7 @@
不能使用额外空间的话模拟在本串操作要实现左旋转字符串的功能还是有点困难的 不能使用额外空间的话模拟在本串操作要实现左旋转字符串的功能还是有点困难的
那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过使用整体反转+局部反转就可以实现反转单词顺序的目的 那么我们可以想一下上一题目[字符串:花式反转还不够!](https://programmercarl.com/0151.翻转字符串里的单词.html)中讲过使用整体反转+局部反转就可以实现反转单词顺序的目的
这道题目也非常类似依然可以通过局部反转+整体反转 达到左旋转的目的 这道题目也非常类似依然可以通过局部反转+整体反转 达到左旋转的目的
@ -41,7 +41,7 @@
2. 反转区间为n到末尾的子串 2. 反转区间为n到末尾的子串
3. 反转整个字符串 3. 反转整个字符串
最后就可以到左旋n的目的而不用定义新的字符串完全在本串上操作 最后就可以到左旋n的目的而不用定义新的字符串完全在本串上操作
例如 示例1中 输入字符串abcdefgn=2 例如 示例1中 输入字符串abcdefgn=2
@ -75,7 +75,7 @@ public:
在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。 在这篇文章[344.反转字符串](https://programmercarl.com/0344.反转字符串.html),第一次讲到反转一个字符串应该怎么做,使用了双指针法。
然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html),这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在for循环的表达式上做做文章。 然后发现[541. 反转字符串II](https://programmercarl.com/0541.反转字符串II.html)这里开始给反转加上了一些条件当需要固定规律一段一段去处理字符串的时候要想想在for循环的表达式上做做文章。
后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。 后来在[151.翻转字符串里的单词](https://programmercarl.com/0151.翻转字符串里的单词.html)中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。