mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-07 15:45:40 +08:00
Update
This commit is contained in:
@ -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 = 4,k = 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 = 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<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转发一下吧,让更多的同学知道这里!**
|
||||
|
52
problems/0316.去除重复字母.md
Normal file
52
problems/0316.去除重复字母.md
Normal 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;
|
||||
}
|
||||
};
|
||||
```
|
@ -1,9 +1,8 @@
|
||||
|
||||
## 题目地址
|
||||
https://leetcode-cn.com/problems/reconstruct-itinerary/
|
||||
|
||||
|
||||
|
||||
# 第242题.有效的字母异位词
|
||||
# 332. 重新安排行程
|
||||
|
||||
# 思路
|
||||
|
||||
|
45
problems/1207.独一无二的出现次数.md
Normal file
45
problems/1207.独一无二的出现次数.md
Normal 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),你值得关注!**
|
||||
|
||||
**如果感觉题解对你有帮助,不要吝啬给一个👍吧!**
|
Reference in New Issue
Block a user