mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-06 23:28:29 +08:00
Update
This commit is contained in:
@ -240,6 +240,46 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
前缀表不减一版本
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
void getNext(int* next, const string& s) {
|
||||
int j = 0;
|
||||
next[0] = 0;
|
||||
for(int i = 1; i < s.size(); i++) {
|
||||
while (j > 0 && s[i] != s[j]) {
|
||||
j = next[j - 1];
|
||||
}
|
||||
if (s[i] == s[j]) {
|
||||
j++;
|
||||
}
|
||||
next[i] = j;
|
||||
}
|
||||
}
|
||||
int strStr(string haystack, string needle) {
|
||||
if (needle.size() == 0) {
|
||||
return 0;
|
||||
}
|
||||
int next[needle.size()];
|
||||
getNext(next, needle);
|
||||
int j = 0;
|
||||
for (int i = 0; i < haystack.size(); i++) {
|
||||
while(j > 0 && haystack[i] != needle[j]) {
|
||||
j = next[j - 1];
|
||||
}
|
||||
if (haystack[i] == needle[j]) {
|
||||
j++;
|
||||
}
|
||||
if (j == needle.size() ) {
|
||||
return (i - needle.size() + 1);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
```
|
||||
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
|
||||
|
@ -30,36 +30,74 @@ candidates 中的数字可以无限制重复被选取。
|
||||
|
||||
# 思路
|
||||
|
||||
题目中的**无限制重复被选取,吓得我赶紧想想 出现0 可咋办**,然后看到下面提示:1 <= candidates[i] <= 200,我就放心了。
|
||||
|
||||
|
||||
这道题上来可以这么想,看看一个数能不能构成target,一个for循环遍历一遍,再看看两个数能不能构成target,两个for循环遍历,在看看三个数能不能构成target,三个for循环遍历,直到candidates.size()个for循环遍历一遍。
|
||||
|
||||
遇到这种问题,就要想到递归的层级嵌套关系就可以解决这种多层for循环的问题,而回溯则帮我们选择每一个合适的集合!
|
||||
|
||||
那么使用回溯的时候,要知道求的是排列,还是组合,排列和组合是不一样的。
|
||||
|
||||
一些同学可能海分不清,我大概说一下:
|
||||
|
||||
**组合是不强调元素顺序的,排列是强调元素顺序的。**
|
||||
|
||||
例如 集合 1,2 和 集合 2,1 在组合上,就是一个集合,因为不强调顺序,而要是排列的话,1,2 和 2,1 就是两个集合了。
|
||||
|
||||
**求组合,和求排列的回溯写法是不一样的,代码上有小小细节上的改变。**
|
||||
|
||||
本题选组过程如下:
|
||||
|
||||
<img src='../pics/39.组合总和.png' width=600> </img></div>
|
||||
|
||||
|
||||
分析完过程,回溯算法的模板框架如下:
|
||||
```
|
||||
backtracking() {
|
||||
if (终止条件) {
|
||||
存放结果;
|
||||
}
|
||||
|
||||
for (选择:选择列表(可以想成树中节点孩子的数量)) {
|
||||
递归,处理节点;
|
||||
backtracking();
|
||||
回溯,撤销处理结果
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
按照模板不难写出如下代码,但很一些细节,我在注释中标记出来了。
|
||||
|
||||
# C++代码
|
||||
|
||||
```
|
||||
// 无限制重复被选取。 吓得我赶紧想想 0 可咋办
|
||||
class Solution {
|
||||
private:
|
||||
vector<vector<int>> result;
|
||||
void backtracking(vector<int>& candidates, int target, vector<int>& vec, int sum, int startIndex) {
|
||||
vector<int> path;
|
||||
void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
|
||||
if (sum > target) {
|
||||
return;
|
||||
}
|
||||
if (sum == target) {
|
||||
result.push_back(vec);
|
||||
result.push_back(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 因为可重复,所以我们从0开始, 这道题目感觉像是47.全排列II,其实不是
|
||||
|
||||
// 这里i 依然从 startIndex开始,因为求的是组合,如果求的是排列,那么i每次都从0开始
|
||||
for (int i = startIndex; i < candidates.size(); i++) {
|
||||
sum += candidates[i];
|
||||
vec.push_back(candidates[i]);
|
||||
backtracking(candidates, target, vec, sum, i); // 关键点在这里,不用i+1了
|
||||
path.push_back(candidates[i]);
|
||||
backtracking(candidates, target, sum, i); // 关键点在这里,不用i+1了,表示可以重复读取当前的数
|
||||
sum -= candidates[i];
|
||||
vec.pop_back();
|
||||
path.pop_back();
|
||||
|
||||
}
|
||||
}
|
||||
public:
|
||||
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
|
||||
vector<int> vec;
|
||||
backtracking(candidates, target, vec, 0, 0);
|
||||
backtracking(candidates, target, 0, 0);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
@ -15,6 +15,81 @@
|
||||
|
||||
# 思路
|
||||
|
||||
这是回溯法的经典题目。
|
||||
|
||||
直觉上当然是使用for循环,例如示例中k为2,很容易想到 用两个for循环,这样就可以输出 和示例中一样的结果。
|
||||
|
||||
代码如下:
|
||||
```
|
||||
int n = 4;
|
||||
for (int i = 1; i <= n; i++) {
|
||||
for (int j = i + 1; j <= n; j++) {
|
||||
cout << i << " " << j << endl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输入:n = 100, k = 3
|
||||
那么就三层for循环,代码如下:
|
||||
|
||||
```
|
||||
for (int i = 1; i <= n; i++) {
|
||||
for (int j = i + 1; j <= n; j++) {
|
||||
for (int u = j + 1; u <=n; n++) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
**如果n为 100,k为50呢,那就50层for循环,是不是开始窒息。**
|
||||
|
||||
那么回溯法就能解决这个问题了。
|
||||
|
||||
回溯是用来做选择的,递归用来节点层叠嵌套,**每一次的递归是层叠嵌套的关系,可以用于解决多层嵌套循环的问题。**
|
||||
|
||||
其实子集和组合问题都可以抽象为一个树形结构,如下:
|
||||
|
||||
|
||||
<img src='../pics/77.组合.png' width=600> </img></div>
|
||||
|
||||
可以看一下这个棵树,一开始集合是 1,2,3,4, 从左向右去数,取过的数,不在重复取。
|
||||
|
||||
第一取1,集合变为2,3,4 ,因为k为2,我们只需要去一个数就可以了,分别取,2,3,4, 得到集合[1,2] [1,3] [1,4],以此类推。
|
||||
|
||||
**其实这就转化成从集合中选取子集的问题,可选择的范围随着选择的进行而限缩,于是做剪枝,调整可选择的范围**
|
||||
|
||||
如何在这个树上遍历,然后收集到我们要的结果集呢,用的就是回溯搜索法,**可以发现,每次搜索到了叶子节点,我们就找到了一个结果。**
|
||||
|
||||
分析完过程,我们来看一下 回溯算法的模板框架如下:
|
||||
```
|
||||
backtracking() {
|
||||
if (终止条件) {
|
||||
存放结果;
|
||||
}
|
||||
|
||||
for (选择:选择列表(可以想成树中节点孩子的数量)) {
|
||||
递归,处理节点;
|
||||
backtracking();
|
||||
回溯,撤销处理结果
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
分析模板:
|
||||
|
||||
什么是达到了终止条件,树中就可以看出,搜到了叶子节点了,就找到了一个符合题目要求的答案,就把这个答案存放起来。
|
||||
|
||||
看一下这个for循环,这个for循环是做什么的,for 就是处理树中节点各个孩子的情况, 一个节点有多少个孩子,这个for循环就执行多少次。
|
||||
|
||||
最后就要看这个递归的过程了,注意这个backtracking就是自己调用自己,实现递归。
|
||||
|
||||
一些同学对递归操作本来就不熟练,递归上面又加上一个for循环,可能就更迷糊了, 我来给大家捋顺一下。
|
||||
|
||||
这个backtracking 其实就是向树的叶子节点方向遍历, for循环可以理解是横向遍历,backtracking 就是纵向遍历,这样就把这棵树全遍历完了。
|
||||
|
||||
那么backtracking就是一直往深处遍历,总会遇到叶子节点,遇到了叶子节点,就要返回,那么backtracking的下面部分就是回溯的操作了,撤销本次处理的结果。
|
||||
|
||||
分析完模板,本题代码如下:
|
||||
|
||||
# C++ 代码
|
||||
|
||||
@ -22,17 +97,17 @@
|
||||
class Solution {
|
||||
private:
|
||||
vector<vector<int>> result; // 存放符合条件结果的集合
|
||||
vector<int> vec; // 用来存放符合条件结果
|
||||
vector<int> path; // 用来存放符合条件结果
|
||||
void backtracking(int n, int k, int startIndex) {
|
||||
if (vec.size() == k) {
|
||||
result.push_back(vec);
|
||||
if (path.size() == k) {
|
||||
result.push_back(path);
|
||||
return;
|
||||
}
|
||||
// 这个for循环有讲究,组合的时候 要用startIndex,排列的时候就要从0开始
|
||||
for (int i = startIndex; i <= n; i++) {
|
||||
vec.push_back(i);
|
||||
path.push_back(i); // 处理节点
|
||||
backtracking(n, k, i + 1);
|
||||
vec.pop_back();
|
||||
path.pop_back(); // 回溯,撤销处理的节点
|
||||
}
|
||||
}
|
||||
public:
|
||||
@ -43,3 +118,62 @@ public:
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 剪枝优化
|
||||
|
||||
在遍历的过程中如下代码 :
|
||||
|
||||
```
|
||||
for (int i = startIndex; i <= n; i++)
|
||||
```
|
||||
|
||||
这个遍历的范围是可以剪枝优化的,怎么优化呢?
|
||||
|
||||
来举一个例子,n = 4, k = 4的话,那么从2开始的遍历都没有意义了。
|
||||
|
||||
已经选择的元素个数:path.size();
|
||||
|
||||
要选择的元素个数 : k - path.size();
|
||||
|
||||
在集合n中开始选择的起始位置 : n - (k - path.size());
|
||||
|
||||
因为起始位置是从1开始的,而且代码里是n <= 起始位置,所以 集合n中开始选择的起始位置 : n - (k - path.size()) + 1;
|
||||
|
||||
所以优化之后是:
|
||||
|
||||
```
|
||||
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++)
|
||||
```
|
||||
|
||||
整体代码如下:
|
||||
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
vector<vector<int>> result; // 存放符合条件结果的集合
|
||||
vector<int> path; // 用来存放符合条件结果
|
||||
void backtracking(int n, int k, int startIndex) {
|
||||
if (path.size() == k) {
|
||||
result.push_back(path);
|
||||
return;
|
||||
}
|
||||
// 这个for循环有讲究,组合的时候 要用startIndex,排列的时候就要从0开始
|
||||
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) {
|
||||
path.push_back(i); // 处理节点
|
||||
backtracking(n, k, i + 1);
|
||||
path.pop_back(); // 回溯,撤销处理的节点
|
||||
}
|
||||
}
|
||||
public:
|
||||
|
||||
vector<vector<int>> combine(int n, int k) {
|
||||
backtracking(n, k, 1);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
|
||||
# 观后感
|
||||
我来写一下观后感: 很厉害,转化成从集合中选取子集的问题,可选择的范围随着选择的进行而限缩,于是做剪枝,调整可选择的范围。 每一次的递归是层叠嵌套的关系,可以用于解决多层嵌套循环的问题。 每一层递归中,尽量节省循环次数,这样在后续的递归调用中,节省下来的循环会被以至少指数等级放大。
|
||||
|
@ -2,11 +2,41 @@
|
||||
## 题目地址
|
||||
https://leetcode-cn.com/problems/repeated-substring-pattern/
|
||||
|
||||
## 思路
|
||||
> KMP算法还能干这个
|
||||
|
||||
这是一道标准的KMP的题目。
|
||||
# 题目459.重复的子字符串
|
||||
|
||||
使用KMP算法,next 数组记录的就是最长公共前后缀, 最后如果 next[len - 1] != -1,说明此时有最长公共前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度),同时如果len % (len - (next[len - 1] + 1)) == 0 ,则说明 (数组长度-最长公共前后缀的长度) 正好可以被 数组的长度整除,说明有重复的子字符串。
|
||||
给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。
|
||||
|
||||
示例 1:
|
||||
输入: "abab"
|
||||
输出: True
|
||||
解释: 可由子字符串 "ab" 重复两次构成。
|
||||
|
||||
示例 2:
|
||||
输入: "aba"
|
||||
输出: False
|
||||
|
||||
示例 3:
|
||||
输入: "abcabcabcabc"
|
||||
输出: True
|
||||
解释: 可由子字符串 "abc" 重复四次构成。 (或者子字符串 "abcabc" 重复两次构成。)
|
||||
|
||||
# 思路
|
||||
|
||||
这又是一道标准的KMP的题目。
|
||||
|
||||
我们在[字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)里提到了,在一个串中查找是否出现过另一个串,这是KMP的看家本领。
|
||||
|
||||
那么寻找重复子串怎么也涉及到KMP算法了呢?
|
||||
|
||||
这里就要说一说next数组了,next 数组记录的就是最长相同前后缀( [字符串:听说你对KMP有这些疑问?](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ) 这里介绍了什么是前缀,什么是后缀,什么又是最长相同前后缀), 如果 next[len - 1] != -1,则说明字符串有最长相同的前后缀(就是字符串里的前缀子串和后缀子串相同的最长长度)。
|
||||
|
||||
最长相等前后缀的长度为:next[len - 1] + 1。
|
||||
|
||||
数组长度为:len。
|
||||
|
||||
如果len % (len - (next[len - 1] + 1)) == 0 ,则说明 (数组长度-最长相等前后缀的长度) 正好可以被 数组的长度整除,说明有该字符串有重复的子字符串。
|
||||
|
||||
**强烈建议大家把next数组打印出来,看看next数组里的规律,有助于理解KMP算法**
|
||||
|
||||
@ -14,44 +44,87 @@ https://leetcode-cn.com/problems/repeated-substring-pattern/
|
||||
|
||||
<img src='../pics/459.重复的子字符串_1.png' width=600> </img></div>
|
||||
|
||||
此时next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时 字符串asdfasdfasdf的最长公共前后缀的长度。
|
||||
此时next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时 字符串asdfasdfasdf的最长相同前后缀的长度。
|
||||
|
||||
|
||||
(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串。
|
||||
(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。
|
||||
|
||||
代码如下:
|
||||
|
||||
## C++代码
|
||||
|
||||
# C++代码
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
void preKmp(int* next, const string& s){
|
||||
// KMP里标准构建next数组的过程
|
||||
void getNext (int* next, const string& s){
|
||||
next[0] = -1;
|
||||
int j = -1;
|
||||
for(int i = 1;i < s.size(); i++){
|
||||
while(j >= 0 && s[i] !=s [j+1])
|
||||
while(j >= 0 && s[i] != s[j+1]) {
|
||||
j = next[j];
|
||||
if(s[i] == s[j+1])
|
||||
}
|
||||
if(s[i] == s[j+1]) {
|
||||
j++;
|
||||
}
|
||||
next[i] = j;
|
||||
}
|
||||
}
|
||||
bool repeatedSubstringPattern(string s) {
|
||||
bool repeatedSubstringPattern (string s) {
|
||||
if (s.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
int next[s.size()];
|
||||
preKmp(next, s);
|
||||
getNext(next, s);
|
||||
int len = s.size();
|
||||
if (next[len - 1] != -1 && len % (len - (next[len - 1] + 1)) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
# next减一C++代码
|
||||
|
||||
```
|
||||
class Solution {
|
||||
public:
|
||||
// KMP里标准构建next数组的过程
|
||||
void getNext (int* next, const string& s){
|
||||
next[0] = 0;
|
||||
int j = 0;
|
||||
for(int i = 1;i < s.size(); i++){
|
||||
while(j > 0 && s[i] != s[j]) {
|
||||
j = next[j - 1];
|
||||
}
|
||||
if(s[i] == s[j]) {
|
||||
j++;
|
||||
}
|
||||
next[i] = j;
|
||||
}
|
||||
}
|
||||
bool repeatedSubstringPattern (string s) {
|
||||
if (s.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
int next[s.size()];
|
||||
getNext(next, s);
|
||||
int len = s.size();
|
||||
if (next[len - 1] != 0 && len % (len - (next[len - 1] )) == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# 拓展
|
||||
|
||||
此时我们已经分享了三篇KMP的文章,首先是[字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)讲解KMP算法的基础理论,给出next数组究竟是如何来了,前缀表又是怎么回事,为什么要选择前缀表。
|
||||
|
||||
然后通过[字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)讲解一道KMP的经典题目,判断文本串里是否出现过模式串,这里涉及到构造next数组的代码实现,以及使用next数组完成模式串与文本串的匹配过程。
|
||||
|
||||
后来很多同学反馈说:搞不懂前后缀,什么又是最长相同前后缀(最长公共前后缀我认为这个用词不准确),以及为什么前缀表要统一减一(右移)呢,不减一行不行?针对这些问题,我在[字符串:听说你对KMP有这些疑问?](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ)中又给出了详细的讲解。
|
||||
|
||||
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
|
||||
|
95
problems/字符串总结.md
Normal file
95
problems/字符串总结.md
Normal file
@ -0,0 +1,95 @@
|
||||
# 字符串:帮你对字符串不再恐惧(总结篇)
|
||||
|
||||
|
||||
# 什么是字符串
|
||||
|
||||
字符串是若干字符组成的有限序列,也可以理解为是一个字符数组,但是很多语言对字符串做了特殊的规定,接下来我来说一说C/C++中的字符串。
|
||||
|
||||
在C语言中,把一个字符串存入一个数组时,也把结束符 '\0'存入数组,并以此作为该字符串是否结束的标志。
|
||||
|
||||
例如这段代码:
|
||||
|
||||
```
|
||||
char a[5] = "asd";
|
||||
for (int i = 0; a[i] != '\0'; i++) {
|
||||
}
|
||||
```
|
||||
|
||||
在C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用'\0'来判断是否结束。
|
||||
|
||||
例如这段代码:
|
||||
|
||||
```
|
||||
string a = "asd";
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
}
|
||||
```
|
||||
|
||||
那么vector< char > 和 string 又有什么区别呢?
|
||||
|
||||
其实在基本操作上没有区别,但是 string提供更多的字符串处理的相关接口,例如string 重载了+,而vector却没有。
|
||||
|
||||
所以想处理字符串,我们还是会定义一个string类型。
|
||||
|
||||
# 要不要使用库函数
|
||||
|
||||
在文章[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)中强调了**打基础的时候,不要太迷恋于库函数。**
|
||||
|
||||
甚至一些同学习惯于调用substr,split,reverse之类的库函数,却不知道其实现原理,也不知道其时间复杂度,这样实现出来的代码,如果在面试面试现场,面试官问:“分析其时间复杂度”的话,一定会一脸懵逼!
|
||||
|
||||
所以我们建议**如果题目关键的部分直接用库函数就可以解决,建议不要使用库函数。**
|
||||
|
||||
**如果库函数仅仅是 解题过程中的一小部分,并且你已经很清楚这个库函数的内部实现原理的话,可以考虑使用库函数。**
|
||||
|
||||
# 双指针法
|
||||
|
||||
|
||||
在[字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) ,我们使用双指针法实现了反转字符串的操作,双指针法在数组,链表和字符串中很常用。
|
||||
|
||||
双指针法在数组,链表,字符串操作中,经常会使用双指针法。
|
||||
|
||||
接着在[字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg),同样还是使用双指针法在时间复杂度O(n)的情况下完成替换空格。
|
||||
|
||||
**其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。**
|
||||
|
||||
|
||||
# 反转系列
|
||||
|
||||
在反转上还可以在加一些玩法,其实考察的是对代码的掌控能力。
|
||||
|
||||
[字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)中,一些同学可能为了处理逻辑:每隔2k个字符的前k的字符,写了一堆逻辑代码或者再搞一个计数器,来统计2k,再统计前k个字符。
|
||||
|
||||
其实**当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章**。
|
||||
|
||||
只要让 i += (2 * k),i 每次移动 2 * k 就可以了,然后判断是否需要有反转的区间。
|
||||
|
||||
因为要找的也就是每2 * k 区间的起点,这样写程序会高效很多。
|
||||
|
||||
在[字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)中要求翻转字符串里的单词,这道题目可以说是综合考察了字符串的多种操作。是考察字符串的好题。
|
||||
|
||||
这道题目通过 **先整体反转再局部反转**,实现了反转字符串里的单词。
|
||||
|
||||
后来发现反转字符串还有一个牛逼的用处,就是达到左旋的效果。
|
||||
|
||||
在[字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ)中,我们通过**先局部反转再整体反转**达到了左旋的效果。
|
||||
|
||||
# KMP
|
||||
|
||||
KMP的主要思想是「当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。」
|
||||
|
||||
首先要理解KMP的理论基础,[字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug),这里提到了,什么是KMP,什么是前缀表,以及为什么要用前缀表
|
||||
|
||||
|
||||
|
||||
打基础的时候,不要太迷恋于库函数
|
||||
|
||||
* [字符串:反转个字符串还有这个用处?](https://mp.weixin.qq.com/s/PmcdiWSmmccHAONzU0ScgQ)
|
||||
|
||||
* [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA)
|
||||
* [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)
|
||||
* [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
|
||||
* [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw)
|
||||
* [字符串:KMP是时候上场了(一文读懂系列)](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)
|
||||
* [字符串:都来看看KMP的看家本领!](https://mp.weixin.qq.com/s/Gk9FKZ9_FSWLEkdGrkecyg)
|
||||
* [字符串:听说你对KMP有这些疑问?](https://mp.weixin.qq.com/s/mqx6IM2AO4kLZwvXdPtEeQ)
|
||||
* [字符串:KMP算法还能干这个!](https://mp.weixin.qq.com/s/lR2JPtsQSR2I_9yHbBmBuQ)
|
Reference in New Issue
Block a user