diff --git a/README.md b/README.md index f75138b9..9677a99f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ * 求职 * [程序员应该如何写简历(附简历模板)](https://mp.weixin.qq.com/s/PkBpde0PV65dJjj9zZJYtg) - * [一线互联网公司技术面试的流程以及注意事项](https://mp.weixin.qq.com/s/1VMvQ_6HbVpEn85CNilTiw) + * [BAT级别技术面试流程和注意事项都在这里了](https://mp.weixin.qq.com/s/815qCyFGVIxwut9I_7PNFw) * 算法性能分析 * [究竟什么是时间复杂度,怎么求时间复杂度,看这一篇就够了](https://mp.weixin.qq.com/s/lYL9TSxLqCeFXIdjt4dcIw) @@ -128,6 +128,8 @@ * 回溯算法 * [关于回溯算法,你该了解这些!](https://mp.weixin.qq.com/s/gjSgJbNbd1eAA5WkA-HeWw) + * [回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ) + * [回溯算法:组合问题再剪剪枝](https://mp.weixin.qq.com/s/Ri7spcJMUmph4c6XjPWXQA) (持续更新中....) @@ -351,6 +353,7 @@ |[0239.滑动窗口最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0239.滑动窗口最大值.md) |滑动窗口/队列 |困难| **单调队列**| |[0242.有效的字母异位词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0242.有效的字母异位词.md) |哈希表 |简单| **哈希**| |[0257.二叉树的所有路径](https://github.com/youngyangyang04/leetcode/blob/master/problems/0257.二叉树的所有路径.md) |树 |简单| **递归/回溯**| +|[0316.去除重复字母](https://github.com/youngyangyang04/leetcode/blob/master/problems/0316.去除重复字母.md) |贪心/字符串 |中等| **单调栈** 这道题目处理的情况比较多,属于单调栈中的难题| |[0332.重新安排行程](https://github.com/youngyangyang04/leetcode/blob/master/problems/0332.重新安排行程.md) |深度优先搜索/回溯 |中等| **深度优先搜索/回溯算法**| |[0344.反转字符串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0344.反转字符串.md) |字符串 |简单| **双指针**| |[0347.前K个高频元素](https://github.com/youngyangyang04/leetcode/blob/master/problems/0347.前K个高频元素.md) |哈希/堆/优先级队列 |中等| **哈希/优先级队列**| @@ -396,6 +399,7 @@ |[0977.有序数组的平方](https://github.com/youngyangyang04/leetcode/blob/master/problems/0977.有序数组的平方.md) |数组 |中等|**双指针** 还是比较巧妙的| |[1002.查找常用字符](https://github.com/youngyangyang04/leetcode/blob/master/problems/1002.查找常用字符.md) |栈 |简单|**栈**| |[1047.删除字符串中的所有相邻重复项](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |哈希表 |简单|**哈希表/数组**| +|[1207.独一无二的出现次数](https://github.com/youngyangyang04/leetcode/blob/master/problems/1207.独一无二的出现次数.md) |哈希表 |简单|**哈希** 两层哈希| |[1365.有多少小于当前数字的数字](https://github.com/youngyangyang04/leetcode/blob/master/problems/1365.有多少小于当前数字的数字.md) |数组、哈希表 |简单|**哈希** 从后遍历的技巧很不错| |[1382.将二叉搜索树变平衡](https://github.com/youngyangyang04/leetcode/blob/master/problems/1047.删除字符串中的所有相邻重复项.md) |二叉搜索树 |中等|**递归** **迭代** 98和108的组合题目| |[剑指Offer05.替换空格](https://github.com/youngyangyang04/leetcode/blob/master/problems/剑指Offer05.替换空格.md) |字符串 |简单|**双指针**| diff --git a/pics/1207.独一无二的出现次数.png b/pics/1207.独一无二的出现次数.png new file mode 100644 index 00000000..bbb036e6 Binary files /dev/null and b/pics/1207.独一无二的出现次数.png differ diff --git a/pics/77.组合4.png b/pics/77.组合4.png new file mode 100644 index 00000000..b2519ccd Binary files /dev/null and b/pics/77.组合4.png differ diff --git a/problems/0077.组合优化.md b/problems/0077.组合优化.md index 8d9a1ed6..6525a9c7 100644 --- a/problems/0077.组合优化.md +++ b/problems/0077.组合优化.md @@ -1,37 +1,16 @@ -## 剪枝优化 +> 如果想在电脑上看文章的话,可以看这里:https://github.com/youngyangyang04/leetcode-master,已经按照顺序整理了「代码随想录」的所有文章,可以fork到自己仓库里,随时复习。**那么重点来了,来都来了,顺便给一个star吧,哈哈** -我们说过,回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。 -在遍历的过程中有如下代码 : +在[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)中,我们通过回溯搜索法,解决了n个数中求k个数的组合问题。 -``` -for (int i = startIndex; i <= n; i++) -``` +文中的回溯法是可以剪枝优化的,本篇我们继续来看一下题目77. 组合。 -这个遍历的范围是可以剪枝优化的,怎么优化呢? +链接:https://leetcode-cn.com/problems/combinations/ -来举一个例子,n = 4, k = 4的话,那么从2开始的遍历都没有意义了。 +**看本篇之前,需要先看[回溯算法:求组合问题!](https://mp.weixin.qq.com/s/OnBjbLzuipWz_u4QfmgcqQ)**。 -所以,可以优化递归中每一层中for循环搜索的起始位置。 - -优化过程如下: - -1. 已经选择的元素个数:path.size(); - -2. 要选择的元素个数 : k - path.size(); - -3. 在集合n中开始选择的起始位置 : n - (k - path.size()); - -因为起始位置是从1开始的,而且代码里是n <= 起始位置,所以 集合n中开始选择的起始位置 : n - (k - path.size()) + 1; - -所以优化之后是: - -``` -for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) -``` - -优化后整体代码如下: +大家先回忆一下[77. 组合]给出的回溯法的代码: ``` class Solution { @@ -43,7 +22,90 @@ private: result.push_back(path); return; } - for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { + for (int i = startIndex; i <= n; i++) { + path.push_back(i); // 处理节点 + backtracking(n, k, i + 1); // 递归 + path.pop_back(); // 回溯,撤销处理的节点 + } + } +public: + vector> combine(int n, int k) { + result.clear(); // 可以不写 + path.clear(); // 可以不写 + backtracking(n, k, 1); + return result; + } +}; +``` + +## 剪枝优化 + +我们说过,回溯法虽然是暴力搜索,但也有时候可以有点剪枝优化一下的。 + +在遍历的过程中有如下代码: + +``` +for (int i = startIndex; i <= n; i++) { + path.push_back(i); + backtracking(n, k, i + 1); + path.pop_back(); +} +``` + +这个遍历的范围是可以剪枝优化的,怎么优化呢? + +来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了。 在第二层for循环,从元素3开始的遍历都没有意义了。 + +这么说有点抽象,如图所示: + + + +图中每一个节点(图中为矩形),就代表本层的一个for循环,那么每一层的for循环从第二个数开始遍历的话,都没有意义,都是无效遍历。 + +**所以,可以剪枝的地方就在递归中每一层的for循环所选择的起始位置**。 + +**如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了**。 + +注意代码中i,就是for循环里选择的起始位置。 +``` +for (int i = startIndex; i <= n; i++) { +``` + +接下来看一下优化过程如下: + +1. 已经选择的元素个数:path.size(); + +2. 还需要的元素个数为: k - path.size(); + +3. 在集合n中至少要从该起始位置 : n - (k - path.size()) + 1,开始遍历 + +为什么有个+1呢,因为包括起始位置,我们要是一个左闭的集合。 + +举个例子,n = 4,k = 3, 目前已经选取的元素为0(path.size为0),n - (k - 0) + 1 即 4 - ( 3 - 0) + 1 = 2。 + +从2开始搜索都是合理的,可以是组合[2, 3, 4]。 + +这里大家想不懂的话,建议也举一个例子,就知道是不是要+1了。 + +所以优化之后的for循环是: + +``` +for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) // i为本次搜索的起始位置 +``` + +优化后整体代码如下: + +``` +class Solution { +private: + vector> result; + vector path; + void backtracking(int n, int k, int startIndex) { + if (path.size() == k) { + result.push_back(path); + return; + } + for (int i = startIndex; i <= n - (k - path.size()) + 1; i++) { // 优化的地方 path.push_back(i); // 处理节点 backtracking(n, k, i + 1); path.pop_back(); // 回溯,撤销处理的节点 @@ -57,3 +119,11 @@ public: } }; ``` + +# 总结 + +本篇我们准对求组合问题的回溯法代码做了剪枝优化,这个优化如果不画图的话,其实不好理解,也不好讲清楚。 + +所以我依然是把整个回溯过程抽象为一颗树形结构,然后可以直观的看出,剪枝究竟是剪的哪里。 + +**就酱,学到了就帮Carl转发一下吧,让更多的同学知道这里!** diff --git a/problems/0316.去除重复字母.md b/problems/0316.去除重复字母.md new file mode 100644 index 00000000..787cc371 --- /dev/null +++ b/problems/0316.去除重复字母.md @@ -0,0 +1,52 @@ +# 思路 + +// 浓浓的单调栈气息 + +这道题目一点都不简单,输入单调栈里情况比较多的题目。 + +需要解决如下问题: +// 这不是单纯遇到小的 栈里就弹出,判断是否在栈里,情况2 +// 如何记录它之前有没有出现过呢,也就是情况1 + + +情况1: +输入:"bbcaac" +输出:"ac" +预期结果:"bac" + +情况2 +输入:"abacb" +输出:"acb" +预期结果:"abc" + +情况3: +aba 输出 a 预期是ab + +``` +class Solution { +public: + string removeDuplicateLetters(string s) { + int letterCount[26] = {0}; + for (int i = 0; i < s.size(); i++) { + letterCount[s[i] - 'a']++; + } + bool isIn[26] = {false}; // 1 已经在栈里,0 不在栈里 + string st; + for (int i = 0; i < s.size(); i++) { + while(!st.empty() + && s[i] < st.back() + && letterCount[st.back() - 'a'] > 0 // 保证字符串i之后还有这个栈顶元素,栈才能做弹出操作,情况3 + && isIn[s[i] - 'a'] == false) { // 如果栈里已经有s[i]了,跳过:情况2 + isIn[st.back() - 'a'] = false; + st.pop_back(); + } + if (isIn[s[i] - 'a'] == false) { + st.push_back(s[i]); + isIn[s[i] - 'a'] = true; + } + letterCount[s[i] - 'a']--; // 只要用过了就减一:情况1 + } + return st; + } +}; +``` diff --git a/problems/0332.重新安排行程.md b/problems/0332.重新安排行程.md index 4c908cfe..9e0a84a7 100644 --- a/problems/0332.重新安排行程.md +++ b/problems/0332.重新安排行程.md @@ -1,9 +1,8 @@ ## 题目地址 +https://leetcode-cn.com/problems/reconstruct-itinerary/ - - -# 第242题.有效的字母异位词 +# 332. 重新安排行程 # 思路 diff --git a/problems/1207.独一无二的出现次数.md b/problems/1207.独一无二的出现次数.md new file mode 100644 index 00000000..832a9084 --- /dev/null +++ b/problems/1207.独一无二的出现次数.md @@ -0,0 +1,45 @@ + +## 思路 + +这道题目数组在是哈希法中的经典应用,如果对数组在哈希法中的使用还不熟悉的同学可以看这两篇:[数组在哈希法中的应用](https://mp.weixin.qq.com/s/vM6OszkM6L1Mx2Ralm9Dig)和[哈希法:383. 赎金信](https://mp.weixin.qq.com/s/sYZIR4dFBrw_lr3eJJnteQ) + +进而可以学习一下[set在哈希法中的应用](https://mp.weixin.qq.com/s/N9iqAchXreSVW7zXUS4BVA),以及[map在哈希法中的应用](https://mp.weixin.qq.com/s/uVAtjOHSeqymV8FeQbliJQ) + +回归本题,**本题强调了-1000 <= arr[i] <= 1000**,那么就可以用数组来做哈希,arr[i]作为哈希表(数组)的下标,那么arr[i]可以是负数,怎么办?负数不能做数组下标。 + +**PS:本文[leetcode刷题攻略](https://github.com/youngyangyang04/leetcode-master)已经收录,相信可以帮你稳稳的提升算法能力,给star支持一下吧!** + + +**此时可以定义一个2000大小的数组,例如int count[2002];**,统计的时候,将arr[i]统一加1000,这样就可以统计arr[i]的出现频率了。 + +题目中要求的是是否有相同的频率出现,那么需要再定义一个哈希表(数组)用来记录频率是否重复出现过,bool fre[1002]; 定义布尔类型的就可以了,**因为题目中强调1 <= arr.length <= 1000,所以哈希表大小为1000就可以了**。 + +如图所示: + + + + +C++代码如下: + +``` +class Solution { +public: + bool uniqueOccurrences(vector& arr) { + int count[2002] = {0}; // 统计数字出现的频率 + for (int i = 0; i < arr.size(); i++) { + count[arr[i] + 1000]++; + } + bool fre[1002] = {false}; // 看相同频率是否重复出现 + for (int i = 0; i <= 2000; i++) { + if (count[i]) { + if (fre[count[i]] == false) fre[count[i]] = true; + else return false; + } + } + return true; + } +}; +``` +> **我是[程序员Carl](https://github.com/youngyangyang04),更多[精彩算法文章](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzUxNjY5NTYxNA==&action=getalbum&album_id=1485825793120387074&scene=173#wechat_redirect)尽在:[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png),你值得关注!** + +**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**