This commit is contained in:
youngyangyang04
2020-09-07 08:38:30 +08:00
parent 32a6071dc2
commit d61b7d8811
5 changed files with 137 additions and 60 deletions

View File

@ -50,6 +50,8 @@
* [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw) * [字符串:简单的反转还不够!](https://mp.weixin.qq.com/s/XGSk1GyPWhfqj2g7Cb1Vgw)
* [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg) * [字符串:替换空格](https://mp.weixin.qq.com/s/t0A9C44zgM-RysAQV3GZpg)
* [字符串:花式反转还不够!](https://mp.weixin.qq.com/s/X3qpi2v5RSp08mO-W5Vicw) * [字符串:花式反转还不够!](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)
(持续更新中.... (持续更新中....

View File

@ -2,23 +2,55 @@
## 题目地址 ## 题目地址
https://leetcode-cn.com/problems/implement-strstr/ https://leetcode-cn.com/problems/implement-strstr/
## 思路 > 在一个串中查找是否出现过另一个串这是KMP的看家本领。
# 题目28. 实现 strStr()
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:
输入: haystack = "aaaaa", needle = "bba"
输出: -1
说明:
 needle 是空字符串时我们应当返回什么值呢这是一个在面试中很好的问题。
对于本题而言 needle 是空字符串时我们应当返回 0 。这与C语言的 strstr() 以及 Java的 indexOf() 定义相符。
# 思路
本题是KMP 经典题目。 本题是KMP 经典题目。
KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。** KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配。**
如果对KMP理论基础还不够了解的同学请看[]() 如果对KMP理论基础还不够了解的同学请看[字符串KMP是时候上场了一文读懂系列](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)。
定义一个函数getNext来构建next数组 函数参数为指向next数组的指针和一个字符串。 然后在函数里完成对next数组的构建。 **为了和[字符串KMP是时候上场了一文读懂系列](https://mp.weixin.qq.com/s/70OXnZ4Ez29CKRrUpVJmug)字符串命名统一方便大家理解以下文章统称haystack为文本串, needle为模式串。**
都知道使用KMP算法一定要构造next数组。
这其实就是计算模式串s前缀表的过程。 主要有如下三步: # 构造next数组
我们定义一个函数getNext来构建next数组函数参数为指向next数组的指针和一个字符串。 代码如下:
```
void getNext(int* next, const string& s)
```
**构造next数组其实就是计算模式串s前缀表的过程。** 主要有如下三步:
1. 初始化 1. 初始化
2. 处理前后缀不相同的情况 2. 处理前后缀不相同的情况
3. 处理前后缀相同的情况 3. 处理前后缀相同的情况
接下来我们详解详解一下。
1. 初始化: 1. 初始化:
定义两个指针i和jj指向前缀终止位置严格来说是终止位置减一的位置i指向后缀终止位置与j同理 定义两个指针i和jj指向前缀终止位置严格来说是终止位置减一的位置i指向后缀终止位置与j同理
@ -26,8 +58,8 @@ KMP的经典思想就是:**当出现字符串不匹配时,可以记录一部
然后还要对next数组进行初始化赋值如下 然后还要对next数组进行初始化赋值如下
``` ```
        int j = -1; int j = -1;
        next[0] = j; next[0] = j;
``` ```
j 为什么要初始化为 -1呢因为之前说过 前缀表要统一减一的操作所以j初始化为-1。 j 为什么要初始化为 -1呢因为之前说过 前缀表要统一减一的操作所以j初始化为-1。
@ -45,7 +77,7 @@ next[i] 表示 i包括i之前最长相等的前后缀长度其实就是
所以遍历模式串s的循环下表i 要从 1开始代码如下 所以遍历模式串s的循环下表i 要从 1开始代码如下
``` ```
        for(int i = 1; i < s.size(); i++) { for(int i = 1; i < s.size(); i++) {
``` ```
如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回溯。 如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回溯。
@ -80,29 +112,31 @@ next[i] = j;
最后整体构建next数组的函数代码如下 最后整体构建next数组的函数代码如下
``` ```
    void getNext(int* next, const string& s){ void getNext(int* next, const string& s){
        int j = -1;     int j = -1;
        next[0] = j;     next[0] = j;
        for(int i = 1; i < s.size(); i++) { // 注意i从1开始     for(int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了         while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回溯             j = next[j]; // 向前回溯
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j前缀的长度赋给next[i]
        }         }
        if (s[i] == s[j + 1]) { // 找到相同的前后缀
            j++;
        }
        next[i] = j; // 将j前缀的长度赋给next[i]
    }     }
}
``` ```
代码构造next数组的逻辑流程如下 代码构造next数组的逻辑流程动画如下:
<img src='../../media/video/KMP精讲3.gif' width=600> </img></div> <img src='../../media/video/KMP精讲3.gif' width=600> </img></div>
得到了next数组之后就要用这个来做匹配了。 得到了next数组之后就要用这个来做匹配了。
# 使用next数组来做匹配
在文本串s里 找是否出现过模式串t。 在文本串s里 找是否出现过模式串t。
定义两个下表j 指向模式串起始位置i指向文本串其实位置。 定义两个下表j 指向模式串起始位置i指向文本串其实位置。
@ -150,60 +184,55 @@ if (j == (t.size() - 1) ) {
那么使用next数组用模式串匹配文本串的整体代码如下 那么使用next数组用模式串匹配文本串的整体代码如下
``` ```
        int j = -1; // 因为next数组里记录的起始位置为-1 int j = -1; // 因为next数组里记录的起始位置为-1
        for (int i = 0; i < s.size(); i++) { // 注意i就从0开始 for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
            while(j >= 0 && s[i] != t[j + 1]) { // 不匹配     while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
                j = next[j]; // j 寻找之前匹配的位置         j = next[j]; // j 寻找之前匹配的位置
            }     }
            if (s[i] == t[j + 1]) { // 匹配j和i同时向后移动     if (s[i] == t[j + 1]) { // 匹配j和i同时向后移动
                j++; // i的增加在for循环里         j++; // i的增加在for循环里
            }     }
            if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t     if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
                return (i - t.size() + 1);         return (i - t.size() + 1);
            }     }
        } }
``` ```
此时所有逻辑的代码都已经写出来了,本题整体代码如下: 此时所有逻辑的代码都已经写出来了,本题整体代码如下:
## C++代码 ## C++代码
``` ```
class Solution { class Solution {
public: public:
void getNext(int* next, const string& s){     void getNext(int* next, const string& s) {
next[0] = -1;         int j = -1;
int j = -1;         next[0] = j;
for(int i = 1; i < s.size(); i++){         for(int i = 1; i < s.size(); i++) { // 注意i从1开始
while (j >= 0 && s[i] != s[j + 1]) {             while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
j = next[j];                 j = next[j]; // 向前回溯
}             }
if (s[i] == s[j + 1]) {             if (s[i] == s[j + 1]) { // 找到相同的前后缀
j++;                 j++;
}             }
next[i] = j;             next[i] = j; // 将j前缀的长度赋给next[i]
}         }
}     }
int strStr(string haystack, string needle) { int strStr(string haystack, string needle) {
if (needle.size() == 0) { if (needle.size() == 0) {
return 0; return 0;
} }
int next[needle.size()]; int next[needle.size()];
getNext(next, needle); getNext(next, needle);
int j = -1; // // 因为next数组里记录的起始位置为-1
int j = -1; for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始
for (int i = 0; i < haystack.size(); i++) { while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配
while(j >= 0 && haystack[i] != needle[j + 1]) { j = next[j]; // j 寻找之前匹配的位置
j = next[j];
} }
if (haystack[i] == needle[j + 1]) { if (haystack[i] == needle[j + 1]) { // 匹配j和i同时向后移动
j++; j++; // i的增加在for循环里
} }
if (j == (needle.size() - 1) ) { if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串t
return (i - needle.size() + 1); return (i - needle.size() + 1);
} }
} }
@ -212,5 +241,5 @@ public:
}; };
``` ```
> 算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。 > 算法干货文章持续更新可以微信搜索「代码随想录」第一时间围观关注后回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。

View File

@ -0,0 +1,46 @@
## 题目地址
https://leetcode-cn.com/problems/binary-tree-level-order-traversal-ii/
## 思路
这道题目相对于[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md),就把结果倒叙过来,就可以了。
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
需要借用一个辅助数据结构队列来实现,**队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。**
使用队列实现广度优先遍历,动画如下:
<video src='../video/102二叉树的层序遍历.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
这样就实现了层序从左到右遍历二叉树。
代码如下:这份代码也可以作为二叉树层序遍历的模板。
## C++代码
```
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root);
vector<vector<int>> result;
while (!que.empty()) {
int size = que.size();
vector<int> vec;
for (int i = 0; i < size; i++) {// 这里一定要使用固定大小size不要使用que.size()
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
result.push_back(vec);
}
reverse(result.begin(), result.end());
return result;
}
};
```

View File

@ -28,9 +28,9 @@ D = [ 0, 2]
# 思路 # 思路
本题咋眼一看好像和[第18题. 四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md)[第15题.三数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0015.三数之和.md)差不多,其实差很多。 本题咋眼一看好像和[0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)[0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)差不多,其实差很多。
**本题是使用哈希法的经典题目,而[第18题. 四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md)[第15题.三数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0015.三数之和.md) 并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。 **本题是使用哈希法的经典题目,而[0015.三数之和](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A)[0018.四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g)并不合适使用哈希法**,因为三数之和和四数之和这两道题目使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。
**而这道题目是四个独立的数组只要找到A[i] + B[j] + C[k] + D[l] = 0就可以不用考虑有重复的四个元素相加等于0的情况所以相对于题目18. 四数之和题目15.三数之和,还是简单了不少!** **而这道题目是四个独立的数组只要找到A[i] + B[j] + C[k] + D[l] = 0就可以不用考虑有重复的四个元素相加等于0的情况所以相对于题目18. 四数之和题目15.三数之和,还是简单了不少!**

BIN
video/KMP精讲3.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB