diff --git a/README.md b/README.md index 6b826931..0eae3978 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,13 @@ * [算法分析中的空间复杂度,你真的会了么?](https://mp.weixin.qq.com/s/sXjjnOUEQ4Gf5F9QCRzy7g) * [刷leetcode的时候,究竟什么时候可以使用库函数,什么时候不要使用库函数,过来人来说一说](https://leetcode-cn.com/circle/article/E1Kjzn/) +* 数组 + * [必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg) + * [数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) + * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) + * [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg) + * [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg) + * [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg) * 链表 * [关于链表,你该了解这些!](https://mp.weixin.qq.com/s/ntlZbEdKgnFQKZkSUAOSpQ) * [链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA) @@ -46,13 +53,6 @@ * [哈希表:解决了两数之和,那么能解决三数之和么?](https://mp.weixin.qq.com/s/r5cgZFu0tv4grBAexdcd8A) * [双指针法:一样的道理,能解决四数之和](https://mp.weixin.qq.com/s/nQrcco8AZJV1pAOVjeIU_g) -* 数组 - * [必须掌握的数组理论知识](https://mp.weixin.qq.com/s/X7R55wSENyY62le0Fiawsg) - * [数组:每次遇到二分法,都是一看就会,一写就废](https://mp.weixin.qq.com/s/fCf5QbPDtE6SSlZ1yh_q8Q) - * [数组:就移除个元素很难么?](https://mp.weixin.qq.com/s/wj0T-Xs88_FHJFwayElQlA) - * [数组:滑动窗口拯救了你](https://mp.weixin.qq.com/s/UrZynlqi4QpyLlLhBPglyg) - * [数组:这个循环可以转懵很多人!](https://mp.weixin.qq.com/s/KTPhaeqxbMK9CxHUUgFDmg) - * [数组:总结篇](https://mp.weixin.qq.com/s/LIfQFRJBH5ENTZpvixHEmg) * 字符串 * [字符串:这道题目,使用库函数一行代码搞定](https://mp.weixin.qq.com/s/X02S61WCYiCEhaik6VUpFA) @@ -110,6 +110,9 @@ * [二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) * [二叉树:构造二叉树登场!](https://mp.weixin.qq.com/s/7r66ap2s-shvVvlZxo59xg) * [二叉树:构造一棵最大的二叉树](https://mp.weixin.qq.com/s/1iWJV6Aov23A7xCF4nV88w) + * [本周小结!(二叉树系列三)](https://mp.weixin.qq.com/s/JLLpx3a_8jurXcz6ovgxtg) + * [二叉树:合并两个二叉树](https://mp.weixin.qq.com/s/3f5fbjOFaOX_4MXzZ97LsQ) + * [二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg) @@ -233,6 +236,7 @@ |[0018.四数之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0018.四数之和.md) | 数组 |中等|**双指针**| |[0020.有效的括号](https://github.com/youngyangyang04/leetcode/blob/master/problems/0020.有效的括号.md) | 栈 |简单|**栈**| |[0021.合并两个有序链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0021.合并两个有序链表.md) |链表 |简单|**模拟** | +|[0024.两两交换链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0024.两两交换链表中的节点.md) |链表 |中等|**模拟** | |[0026.删除排序数组中的重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/0026.删除排序数组中的重复项.md) |数组 |简单|**暴力** **快慢指针/快慢指针** | |[0027.移除元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0027.移除元素.md) |数组 |简单| **暴力** **双指针/快慢指针/双指针**| |[0028.实现strStr()](https://github.com/youngyangyang04/leetcode/blob/master/problems/0028.实现strStr().md) |字符串 |简单| **KMP** | @@ -326,7 +330,8 @@ |[0705.设计哈希集合](https://github.com/youngyangyang04/leetcode/blob/master/problems/0705.设计哈希集合.md) |哈希表 |简单|**模拟**| |[0707.设计链表](https://github.com/youngyangyang04/leetcode/blob/master/problems/0707.设计链表.md) |链表 |中等|**模拟**| |[0841.钥匙和房间](https://github.com/youngyangyang04/leetcode/blob/master/problems/0841.钥匙和房间.md) |孤岛问题 |中等|**bfs** **dfs**| -|[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |栈 |简单|**栈**| +|[1002.查找常用字符](https://github.com/youngyangyang04/leetcode/blob/master/problems/1002.查找常用字符.md) |栈 |简单|**栈**| +|[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |哈希表 |简单|**哈希表/数组**| |[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**| |[ 剑指Offer58-I.翻转单词顺序](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**模拟/双指针**| |[剑指Offer58-II.左旋转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer58-II.左旋转字符串.md) |字符串 |简单|**反转操作**| diff --git a/pics/1002.查找常用字符.png b/pics/1002.查找常用字符.png new file mode 100644 index 00000000..23d75057 Binary files /dev/null and b/pics/1002.查找常用字符.png differ diff --git a/pics/24.两两交换链表中的节点.png b/pics/24.两两交换链表中的节点.png new file mode 100644 index 00000000..3051c5a1 Binary files /dev/null and b/pics/24.两两交换链表中的节点.png differ diff --git a/pics/24.两两交换链表中的节点1.png b/pics/24.两两交换链表中的节点1.png new file mode 100644 index 00000000..e79642c3 Binary files /dev/null and b/pics/24.两两交换链表中的节点1.png differ diff --git a/pics/24.两两交换链表中的节点2.png b/pics/24.两两交换链表中的节点2.png new file mode 100644 index 00000000..e6dba912 Binary files /dev/null and b/pics/24.两两交换链表中的节点2.png differ diff --git a/pics/24.两两交换链表中的节点3.png b/pics/24.两两交换链表中的节点3.png new file mode 100644 index 00000000..7d706cd2 Binary files /dev/null and b/pics/24.两两交换链表中的节点3.png differ diff --git a/pics/42.接雨水4.png b/pics/42.接雨水4.png new file mode 100644 index 00000000..ba12383e Binary files /dev/null and b/pics/42.接雨水4.png differ diff --git a/pics/42.接雨水5.png b/pics/42.接雨水5.png new file mode 100644 index 00000000..066792b0 Binary files /dev/null and b/pics/42.接雨水5.png differ diff --git a/pics/98.验证二叉搜索树.png b/pics/98.验证二叉搜索树.png new file mode 100644 index 00000000..8a164140 Binary files /dev/null and b/pics/98.验证二叉搜索树.png differ diff --git a/problems/0015.三数之和.md b/problems/0015.三数之和.md index 2fddb401..c437cadb 100644 --- a/problems/0015.三数之和.md +++ b/problems/0015.三数之和.md @@ -22,6 +22,8 @@ https://leetcode-cn.com/problems/3sum/ # 思路 +**注意[0, 0, 0, 0] 这组数据** + ## 哈希解法 两层for循环就可以确定 a 和b 的数值了,可以使用哈希法来确定 0-(a+b) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。 diff --git a/problems/0024.两两交换链表中的节点.md b/problems/0024.两两交换链表中的节点.md new file mode 100644 index 00000000..92f9b294 --- /dev/null +++ b/problems/0024.两两交换链表中的节点.md @@ -0,0 +1,70 @@ + + +## 题目地址 + +## 思路 + +这道题目正常模拟就可以了。 + +建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。 + +对虚拟头结点的操作,还不熟悉的话,可以看这篇[链表:听说用虚拟头节点会方便很多?](https://mp.weixin.qq.com/s/slM1CH5Ew9XzK93YOQYSjA)。 + +接下来就是交换相邻两个元素了,**此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序** + +初始时,cur指向虚拟头结点,然后进行如下三步: + + + +操作之后,链表如下: + + + + +看这个可能就更直观一些了: + + + + +对应的C++代码实现如下: + +``` +class Solution { +public: + ListNode* swapPairs(ListNode* head) { + ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点 + dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作 + ListNode* cur = dummyHead; + while(cur->next != nullptr && cur->next->next != nullptr) { + ListNode* tmp = cur->next; // 记录临时节点 + ListNode* tmp1 = cur->next->next->next; // 记录临时节点 + + cur->next = cur->next->next; // 步骤一 + cur->next->next = tmp; // 步骤二 + cur->next->next->next = tmp1; // 步骤三 + + cur = cur->next->next; // cur移动两位,准备下一轮交换 + } + return dummyHead->next; + } +}; +``` +时间复杂度:O(n) +空间复杂度:O(1) + +## 拓展 + +**这里还是说一下,大家不必太在意leetcode上执行用时,打败多少多少用户,这个就是一个玩具,非常不准确。** + +做题的时候自己能分析出来时间复杂度就可以了,至于leetcode上执行用时,大概看一下就行。 + +上面的代码我第一次提交执行用时8ms,打败6.5%的用户,差点吓到我了。 + +心想应该没有更好的方法了吧,也就O(n)的时间复杂度,重复提交几次,这样了: + + + +所以,不必过于在意leetcode上这个统计。 + + + diff --git a/problems/0028.实现strStr().md b/problems/0028.实现strStr().md index f2e4e23a..0accdbdd 100644 --- a/problems/0028.实现strStr().md +++ b/problems/0028.实现strStr().md @@ -108,7 +108,7 @@ next数组就是一个前缀表(prefix table)。 如动画所示: - + 动画里,我特意把 子串`aa` 标记上了,这是有原因的,大家先注意一下,后面还会说道。 @@ -169,7 +169,7 @@ next数组就是一个前缀表(prefix table)。 再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示: - + 找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。 @@ -311,7 +311,7 @@ void getNext(int* next, const string& s){ 代码构造next数组的逻辑流程动画如下: - + 得到了next数组之后,就要用这个来做匹配了。 diff --git a/problems/0042.接雨水.md b/problems/0042.接雨水.md index 4a166ae3..ce90dd3c 100644 --- a/problems/0042.接雨水.md +++ b/problems/0042.接雨水.md @@ -105,4 +105,69 @@ public: 单调栈究竟如何做呢,得画一个图,不太好理解 -按照列来算的,遇到相同的怎么办。 +## 使用单调栈内元素的顺序 + +从打到小还是从小打到呢 + +从栈底到栈头(元素从栈头弹出)是从大到小的顺序,因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。 + +如图: + + + + +## 遇到相同高度的柱子怎么办。 + +遇到相同的元素,更新栈内下表,就是将栈里元素(旧下标)弹出,讲新元素(新下标)加入栈中。 + +例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。 + +因为我们要求宽度的时候 如果遇到相容高度的柱子,需要使用最右边的柱子来计算宽度。 + +如图所示: + + + + + + + + +没有必要 stack> st; // 高度,下表 + + +放进去元素,相同怎么办,相同也没事,放里面就行,计算结果也是0 + +**真的难** + +``` +class Solution { +public: + int trap(vector& height) { + if (height.size() <= 2) return 0; + stack st; // 存着下标,计算的时候用下标对应的柱子高度 + st.push(0); + int sum = 0; + for (int i = 1; i < height.size(); i++) { + if (height[i] < height[st.top()]) { + st.push(i); + } if (height[i] == height[st.top()]) { // 如果相等则更新栈内下表,例如 5 5 1 7 这种情况 + st.pop(); + st.push(i); + } else { + while (!st.empty() && height[i] > height[st.top()]) { // 注意这里是while + int mid = st.top(); + st.pop(); + if (!st.empty()) { + int h = min(height[st.top()], height[i]) - height[mid]; + int w = i - st.top() - 1 ; // 注意求宽度这里不加1,而是减一 + sum += h * w; + } + } + st.push(i); + } + } + return sum; + } +}; +``` diff --git a/problems/0098.验证二叉搜索树.md b/problems/0098.验证二叉搜索树.md index a1f503e0..ac827324 100644 --- a/problems/0098.验证二叉搜索树.md +++ b/problems/0098.验证二叉搜索树.md @@ -1,65 +1,246 @@ ## 题目地址 -## 思路 +> 学习完二叉搜索树的特性了,那么就验证一波 -中序遍历下,输出的二叉搜索树节点的数值是有序序列,有了这个特性,**验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。** +# 98.验证二叉搜索树 + +给定一个二叉树,判断其是否是一个有效的二叉搜索树。 + +假设一个二叉搜索树具有如下特征: + +* 节点的左子树只包含小于当前节点的数。 +* 节点的右子树只包含大于当前节点的数。 +* 所有左子树和右子树自身必须也是二叉搜索树。 + + + +# 思路 + +要知道中序遍历下,输出的二叉搜索树节点的数值是有序序列。 + +有了这个特性,**验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了。** + +## 递归法 + +可以递归中序遍历将二叉搜索树转变成一个数组,代码如下: + +``` +vector vec; +void traversal(TreeNode* root) { + if (root == NULL) return; + traversal(root->left); + vec.push_back(root->val); // 将二叉搜索树转换为有序数组 + traversal(root->right); +} +``` + +然后只要比较一下,这个数组是否是有序的,**注意二叉搜索树中不能有重复元素**。 + +``` +traversal(root); +for (int i = 1; i < vec.size(); i++) { + // 注意要小于等于,搜索树里不能有相同元素 + if (vec[i] <= vec[i - 1]) return false; +} +return true; +``` + +整体代码如下: + +``` +class Solution { +private: + vector vec; + void traversal(TreeNode* root) { + if (root == NULL) return; + traversal(root->left); + vec.push_back(root->val); // 将二叉搜索树转换为有序数组 + traversal(root->right); + } +public: + bool isValidBST(TreeNode* root) { + vec.clear(); // 不加这句在leetcode上也可以过,但最好加上 + traversal(root); + for (int i = 1; i < vec.size(); i++) { + // 注意要小于等于,搜索树里不能有相同元素 + if (vec[i] <= vec[i - 1]) return false; + } + return true; + } +}; +``` + +以上代码中,我们把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。 -所以代码实现上,我们就使用递归法来中序遍历,遍历的过程中判断节点上的数值是不是递增的就可以了。 这道题目比较容易陷入两个陷阱: -* 陷阱1 :[10,5,15,null,null,6,20] 这个case 要考虑道 +* 陷阱1 + +**不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了**。 + +写出了类似这样的代码: + +``` +if (root->val > root->left->val && root->val < root->right->val) { + return true; +} else { + return false; +} +``` + +**我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点。**所以以上代码的判断逻辑是错误的。 + +例如: [10,5,15,null,null,6,20] 这个case: + ![二叉搜索树](https://img-blog.csdnimg.cn/20200812191501419.png) -10的右子树只能包含大于当前节点的数,而右面出现了一个6 这就不符合了! +节点10小于左节点5,大于右节点15,但右子树里出现了一个6 这就不符合了! -* 陷阱2:样例中根节点的val 可能是int的最小值 +* 陷阱2 -问题可以进一步演进:如果样例中根节点的val 可能是longlong的最小值 又要怎么办呢?看下文解答! +样例中最小节点 可能是int的最小值,如果这样使用最小的int来比较也是不行的。 -## C++代码 +此时可以初始化比较元素为longlong的最小值。 -定于全局变量初始化为long long最小值 +问题可以进一步演进:如果样例中根节点的val 可能是longlong的最小值 又要怎么办呢?文中会解答。 + +了解这些陷阱之后我们来看一下代码应该怎么写: + +递归三部曲: + +* 确定递归函数,返回值以及参数 + +要定义一个longlong的全局变量,用来比较遍历的节点是否有序,因为后台测试数据中有int最小值,所以定义为longlong的类型,初始化为longlong最小值。 + +注意递归函数要有bool类型的返回值, 我们在[二叉树:递归函数究竟什么时候需要返回值,什么时候不要返回值?](https://mp.weixin.qq.com/s/6TWAVjxQ34kVqROWgcRFOg) 中讲了,只有寻找某一条边(或者一个节点)的时候,递归函数会有bool类型的返回值。 + +其实本题是同样的道理,我们在寻找一个不符合条件的节点,如果没有找到这个节点就遍历了整个树,如果找到不符合的节点了,立刻返回。 + +代码如下: + +``` +long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值 +bool isValidBST(TreeNode* root) +``` + +* 确定终止条件 + +如果是空节点 是不是二叉搜索树呢? + +是的,二叉搜索树也可以为空! + +代码如下: + +``` +if (root == NULL) return true; +``` + +* 确定单层递归的逻辑 + +中序遍历,一直更新maxVal,一旦发现maxVal >= root->val,就返回false,注意元素相同时候也要返回false。 + +代码如下: + +``` +bool left = isValidBST(root->left); // 左 + +// 中序遍历,验证遍历的元素是不是从小到大 +if (maxVal < root->val) maxVal = root->val; // 中 +else return false; + +bool right = isValidBST(root->right); // 右 +return left && right; +``` + +整体代码如下: ``` class Solution { public: - long long maxVal = LONG_MIN; + long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值 bool isValidBST(TreeNode* root) { if (root == NULL) return true; + bool left = isValidBST(root->left); // 中序遍历,验证遍历的元素是不是从小到大 if (maxVal < root->val) maxVal = root->val; else return false; bool right = isValidBST(root->right); + return left && right; } }; ``` -其实因为后台数据有int最小值测试用例,所以都改成了longlong最小值。 +以上代码是因为后台数据有int最小值测试用例,所以都把maxVal改成了longlong最小值。 -如果测试数据中有 longlong的最小值,怎么办?不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面的数值: +如果测试数据中有 longlong的最小值,怎么办? + +不可能在初始化一个更小的值了吧。 建议避免 初始化最小值,如下方法取到最左面节点的数值来比较。 + +代码如下: ``` class Solution { public: - long long maxVal = 0; // 记录中序遍历的过程中出现过的最大值 - bool flag = false; // 标记是否取到了最左面节点的数值 + TreeNode* pre = NULL; // 用来记录前一个节点 bool isValidBST(TreeNode* root) { if (root == NULL) return true; bool left = isValidBST(root->left); - if (!flag) { - maxVal = root->val; - flag = true; - } else { - // 中序遍历,这里相当于从大到小进行比较 - if (maxVal < root->val) maxVal = root->val; - else return false; - } + + if (pre != NULL && pre->val >= root->val) return false; + pre = root; // 记录前一个节点 + bool right = isValidBST(root->right); return left && right; } }; ``` +最后这份代码看上去整洁一些,思路也清晰。 + +## 迭代法 + +可以用迭代法模拟二叉树中序遍历,对前中后序迭代法生疏的同学可以看这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg),[二叉树:前中后序迭代方式统一写法](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg) + +迭代法中序遍历稍加改动就可以了,代码如下: + +``` +class Solution { +public: + bool isValidBST(TreeNode* root) { + stack st; + TreeNode* cur = root; + TreeNode* pre = NULL; // 记录前一个节点 + while (cur != NULL || !st.empty()) { + if (cur != NULL) { + st.push(cur); + cur = cur->left; // 左 + } else { + cur = st.top(); // 中 + st.pop(); + if (pre != NULL && cur->val <= pre->val) + return false; + pre = cur; //保存前一个访问的结点 + + cur = cur->right; // 右 + } + } + return true; + } +}; +``` + +在[二叉树:二叉搜索树登场!](https://mp.weixin.qq.com/s/vsKrWRlETxCVsiRr8v_hHg)中我们分明写出了痛哭流涕的简洁迭代法,怎么在这里不行了呢,因为本题是要验证二叉搜索树啊。 + +# 总结 + +这道题目是一个简单题,但对于没接触过的同学还是有难度的。 + +所以初学者刚开始学习算法的时候,看到简单题目没有思路很正常,千万别怀疑自己智商,学习过程都是这样的,大家智商都差不多,哈哈。 + +只要把基本类型的题目都做过,总结过之后,思路自然就开阔了。 + +**就酱,学到了的话,就转发给身边需要的同学吧!** + > 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/problems/0700.二叉搜索树中的搜索.md b/problems/0700.二叉搜索树中的搜索.md index c7ecada7..cb716034 100644 --- a/problems/0700.二叉搜索树中的搜索.md +++ b/problems/0700.二叉搜索树中的搜索.md @@ -74,12 +74,15 @@ return NULL; 整体代码如下: ``` -TreeNode* searchBST(TreeNode* root, int val) { - if (root == NULL || root->val == val) return root; - if (root->val > val) return searchBST(root->left, val); - if (root->val < val) return searchBST(root->right, val); - return NULL; -} +class Solution { +public: + TreeNode* searchBST(TreeNode* root, int val) { + if (root == NULL || root->val == val) return root; + if (root->val > val) return searchBST(root->left, val); + if (root->val < val) return searchBST(root->right, val); + return NULL; + } +}; ``` ## 迭代法 diff --git a/problems/1002.查找常用字符.md b/problems/1002.查找常用字符.md new file mode 100644 index 00000000..106e8e12 --- /dev/null +++ b/problems/1002.查找常用字符.md @@ -0,0 +1,110 @@ +# 题目地址 +https://leetcode-cn.com/problems/find-common-characters/ + +## 思路 + +这道题意一起就有点绕,不是那么容易懂,其实就是26个小写字符中有字符 在所有字符串里都出现的话,就输出,重复的也算。 + +例如: + +输入:["ll","ll","ll"] +输出:["l","l"] + +这道题目一眼看上去,就是用哈希法,**“小写字符”,“出现频率”, 这些关键字都是为哈希法量身定做的啊** + +首先可以想到的是暴力解法,一个字符串一个字符串去搜,时间复杂度是O(n^m),n是字符串长度,m是有几个字符串。 + +可以看出这是指数级别的时间复杂度,非常高,而且代码实现也不容易,因为要统计 重复的字符,还要适当的替换或者去重。 + +那我们还是哈希法吧。如果对哈希法不了解,可以这这篇文章:[关于哈希表,你该了解这些!](https://mp.weixin.qq.com/s/g8N6WmoQmsCUw3_BaWxHZA)。 + +如果对用数组来做哈希法不了解的话,可以看这篇:[哈希表:可以拿数组当哈希表来用,但哈希值不要太大](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)。 + +了解了哈希法,理解了数组在哈希法中的应用之后,可以来看解题思路了。 + +整体思路就是统计出搜索字符串里26个字符的出现的频率,然后取每个字符频率最小值,最后转成输出格式就可以了。 + +如图: + + + +先统计第一个字符串所有字符出现的次数,代码如下: + +``` +int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率 +for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化 + hash[A[0][i] - 'a']++; +} +``` + +接下来,把其他字符串里字符的出现次数也统计出来一次放在hashOtherStr中。 + +然后hash 和 hashOtherStr 取最小值,这是本题关键所在,此时取最小值,就是 一个字符在所有字符串里出现的最小次数了。 + +代码如下: + +``` +int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率 +for (int i = 1; i < A.size(); i++) { + memset(hashOtherStr, 0, 26 * sizeof(int)); + for (int j = 0; j < A[i].size(); j++) { + hashOtherStr[A[i][j] - 'a']++; + } + // 这是关键所在 + for (int k = 0; k < 26; k++) { // 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数 + hash[k] = min(hash[k], hashOtherStr[k]); + } +} +``` +此时hash里统计着字符在所有字符串里出现的最小次数,那么把hash转正题目要求的输出格式就可以了。 + +代码如下: + +``` +// 将hash统计的字符次数,转成输出形式 +for (int i = 0; i < 26; i++) { + while (hash[i] != 0) { // 注意这里是while,多个重复的字符 + string s(1, i + 'a'); // char -> string + result.push_back(s); + hash[i]--; + } +} +``` + +整体C++代码如下: + +``` +class Solution { +public: + vector commonChars(vector& A) { + vector result; + if (A.size() == 0) return result; + int hash[26] = {0}; // 用来统计所有字符串里字符出现的最小频率 + for (int i = 0; i < A[0].size(); i++) { // 用第一个字符串给hash初始化 + hash[A[0][i] - 'a']++; + } + + int hashOtherStr[26] = {0}; // 统计除第一个字符串外字符的出现频率 + for (int i = 1; i < A.size(); i++) { + memset(hashOtherStr, 0, 26 * sizeof(int)); + for (int j = 0; j < A[i].size(); j++) { + hashOtherStr[A[i][j] - 'a']++; + } + // 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数 + for (int k = 0; k < 26; k++) { + hash[k] = min(hash[k], hashOtherStr[k]); + } + } + // 将hash统计的字符次数,转成输出形式 + for (int i = 0; i < 26; i++) { + while (hash[i] != 0) { // 注意这里是while,多个重复的字符 + string s(1, i + 'a'); // char -> string + result.push_back(s); + hash[i]--; + } + } + + return result; + } +}; +``` diff --git a/video/KMP详解1.gif b/video/KMP精讲1.gif similarity index 100% rename from video/KMP详解1.gif rename to video/KMP精讲1.gif diff --git a/video/KMP精讲2.gif b/video/KMP精讲2.gif new file mode 100644 index 00000000..5a88d49b Binary files /dev/null and b/video/KMP精讲2.gif differ diff --git a/video/KMP精讲4.gif b/video/KMP精讲4.gif new file mode 100644 index 00000000..0f0f68ca Binary files /dev/null and b/video/KMP精讲4.gif differ