This commit is contained in:
youngyangyang04
2020-10-28 09:58:42 +08:00
parent c82f6f8897
commit ad46b77cef
7 changed files with 202 additions and 32 deletions

View File

@ -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) |字符串 |简单|**双指针**|

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
pics/77.组合4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -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<vector<int>> 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 = 4k = 4的话那么第一层for循环的时候从元素2开始的遍历都没有意义了。 在第二层for循环从元素3开始的遍历都没有意义了。
这么说有点抽象,如图所示:
<img src='../pics/77.组合4.png' width=600> </img></div>
图中每一个节点图中为矩形就代表本层的一个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 = 4k = 3 目前已经选取的元素为0path.size为0n - (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<vector<int>> result;
vector<int> 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转发一下吧让更多的同学知道这里**

View File

@ -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;
}
};
```

View File

@ -1,9 +1,8 @@
## 题目地址
https://leetcode-cn.com/problems/reconstruct-itinerary/
# 第242题.有效的字母异位词
# 332. 重新安排行程
# 思路

View File

@ -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就可以了**。
如图所示:
<img src='../pics/1207.独一无二的出现次数.png' width=600> </img></div>
C++代码如下:
```
class Solution {
public:
bool uniqueOccurrences(vector<int>& 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),你值得关注!**
**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**