mirror of
https://github.com/youngyangyang04/leetcode-master.git
synced 2025-07-06 23:28:29 +08:00
Update
This commit is contained in:
BIN
pics/1047.删除字符串中的所有相邻重复项.png
Normal file
BIN
pics/1047.删除字符串中的所有相邻重复项.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
BIN
pics/131.分割回文串.png
Normal file
BIN
pics/131.分割回文串.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
BIN
pics/37.解数独.png
Normal file
BIN
pics/37.解数独.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 154 KiB |
@ -24,11 +24,13 @@ https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
|
||||
|
||||
这是一个回溯法的经典题目,**不要以为回溯是一个性能很高的算法,回溯其实就是暴力枚举,纯暴力,搜出所有的可能性。**
|
||||
|
||||
回溯一般都伴随着递归,而这种组合问题,都可以画成一个树形结构。如图所示:
|
||||
回溯一般都伴随着递归,而这种组合问题,都可以画成一个树形结构。
|
||||
|
||||
<img src='../pics/17. 电话号码的字母组合.jpeg' width=600> </img></div>
|
||||
例如:输入:"23",如图所示:
|
||||
|
||||
可以想成遍历这棵树,然后把叶子节点都保存下来。
|
||||
<img src='../pics/17. 电话号码的字母组合.png' width=600> </img></div>
|
||||
|
||||
可以想成遍历这棵树,然后把叶子节点都保存下来,输出["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]。
|
||||
|
||||
|
||||
3. 输入1 * #按键等等异常情况
|
||||
|
@ -94,7 +94,11 @@ cd a/b/c/../../
|
||||
|
||||
那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。
|
||||
|
||||
分析完之后,代码其实就比较好些了,如下:
|
||||
分析完之后,代码其实就比较好写了,
|
||||
|
||||
但还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
|
||||
|
||||
实现代码如下:
|
||||
|
||||
## C++代码
|
||||
|
||||
@ -118,5 +122,7 @@ public:
|
||||
}
|
||||
};
|
||||
```
|
||||
技巧性的东西没有固定的学习方法,还是要多看多练,自己总灵活运用了。
|
||||
|
||||
> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
|
||||
|
@ -4,7 +4,23 @@ https://leetcode-cn.com/problems/sudoku-solver/
|
||||
|
||||
# 思路
|
||||
|
||||
这道题目和之前递归的方式都不一样,这里相当于两层递归,之前的都是一层递归
|
||||
棋盘搜索问题依然是要使用回溯法,暴力搜索,只不过这次我们要做的是双层递归。
|
||||
|
||||
怎么做双层递归呢?如果大家之前做过回溯类型的题目,例如:,77.组合(组合问题),131.分割回文串(分割问题),78.子集(子集问题),46.全排列(排列问题),以及51.N皇后(N皇后问题)等等,都会发现,这些都是单层递归。
|
||||
|
||||
**如果大家以上这几道题目没有做过的话,不建议上来就做这道题哈!**
|
||||
|
||||
n皇后因为每一行每一列只放一个皇后,只需要一层for循环遍历行数,递归来处理每一行的一个皇后放在哪里就可以。
|
||||
|
||||
本题就不一样了,本题中棋盘每一个位置都要放一个数字,这个问题的树形结构要比N皇后更宽更深。
|
||||
|
||||
如图所示:
|
||||
|
||||
<img src='../pics/37.解数独.png' width=600> </img></div>
|
||||
|
||||
可以看出我们需要的是一个二维的递归(也就是两个for循环,递归遍历每一个棋盘的位置)
|
||||
|
||||
这道题目和之前递归的方式都不一样,这里相当于两层递归,之前的都是一层递归。
|
||||
|
||||
# C++代码
|
||||
|
||||
@ -12,38 +28,37 @@ https://leetcode-cn.com/problems/sudoku-solver/
|
||||
class Solution {
|
||||
private:
|
||||
bool backtracking(vector<vector<char>>& board) {
|
||||
for (int i = 0; i < board.size(); i++) {
|
||||
for (int j = 0; j < board[0].size(); j++) {
|
||||
for (int i = 0; i < board.size(); i++) { // 遍历行
|
||||
for (int j = 0; j < board[0].size(); j++) { // 遍历列
|
||||
if (board[i][j] != '.') continue;
|
||||
for (char k = '1'; k <= '9'; k++) {
|
||||
if (isValid(i, j, k, board)) {
|
||||
for (char k = '1'; k <= '9'; k++) { // (i, j) 这个位置放k是否合适
|
||||
if (isValid(i, j, k, board)) {
|
||||
board[i][j] = k;
|
||||
if (backtracking(board)) return true;
|
||||
if (backtracking(board)) return true; // 如果找到合适一组立刻返回
|
||||
board[i][j] = '.';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return true; // 遍历完没有返回false,说明找到了合适棋盘位置了
|
||||
|
||||
}
|
||||
bool isValid(int row, int col, char val, vector<vector<char>>& board) {
|
||||
|
||||
for (int i = 0; i < 9; i++) {
|
||||
for (int i = 0; i < 9; i++) { // 判断行里是否重复
|
||||
if (board[row][i] == val) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int j = 0; j < 9; j++) {
|
||||
for (int j = 0; j < 9; j++) { // 判断列里是否重复
|
||||
if (board[j][col] == val) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
int startRow = (row / 3) * 3;
|
||||
int startCol = (col / 3) * 3;
|
||||
for (int i = startRow; i < startRow + 3; i++) {
|
||||
for (int i = startRow; i < startRow + 3; i++) { // 判断9方格里是否重复
|
||||
for (int j = startCol; j < startCol + 3; j++) {
|
||||
if (board[i][j] == val ) {
|
||||
return false;
|
||||
|
@ -4,30 +4,97 @@ https://leetcode-cn.com/problems/palindrome-partitioning/
|
||||
|
||||
## 思路
|
||||
|
||||
(分割 有这么多系列啊!!!!!)
|
||||
|
||||
本题这涉及到两个关键问题:
|
||||
|
||||
1. 切割问题,有不同的切割方式
|
||||
2. 判断回文
|
||||
|
||||
相信这里不同的切割方式可以搞懵很多同学了。
|
||||
|
||||
|
||||
这种题目,想用for循环暴力解法,可能都不那么容易写出来,所以要换一种暴力的方式,就是回溯
|
||||
|
||||
一些同学可能想不清楚 回溯究竟是如果切割子串
|
||||
|
||||
我们来分析一下切割,其实切割,类似于组合问题。
|
||||
|
||||
例如对于字符串abcdef,
|
||||
|
||||
组合问题:选取一个a之后,在bcdef中再去选取第二个,选取b之后在cdef中在选组第三个.....。
|
||||
切割问题:切割一个a之后,在bcdef中再去切割第二段,切割b之后在cdef中在切割第三段.....。
|
||||
|
||||
感受出来了不?
|
||||
|
||||
所以切割问题,也可以抽象为一颗树形结构,如图:
|
||||
|
||||
<img src='../pics/131.分割回文串.png' width=600> </img></div>
|
||||
|
||||
递归用来纵向遍历,for循环用来横向遍历,切割线(就是图中的红线)切割到字符串的结尾位置,说明找到了一个切割方法。
|
||||
|
||||
此时可以发现,切割问题的回溯搜索的过程和组合问题的回溯搜索的过程是差不多的。
|
||||
|
||||
回溯法模板:
|
||||
|
||||
```
|
||||
backtracking() {
|
||||
if (终止条件) {
|
||||
存放结果;
|
||||
}
|
||||
|
||||
for (选择:选择列表(可以想成树中节点孩子的数量)) {
|
||||
递归,处理节点;
|
||||
backtracking();
|
||||
回溯,撤销处理结果
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在来分析一下终止条件,要从上图中可以看出,切割线切到了字符串最后面,说明找到了一种切割方法,那么此时,就是本层递归的终止终止条件。
|
||||
|
||||
那么在代码里什么是切割线呢,在处理集合问题的时候,参数我们都要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。
|
||||
|
||||
代码如下:
|
||||
|
||||
```
|
||||
void backtracking (const string& s, int startIndex) {
|
||||
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
|
||||
if (startIndex >= s.size()) {
|
||||
result.push_back(path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在来看看在递归循环,中如何截取子串呢
|
||||
|
||||
|
||||
|
||||
## C++代码
|
||||
```
|
||||
class Solution {
|
||||
private:
|
||||
vector<vector<string>> result;
|
||||
|
||||
// startIndex: 搜索的起始位置,vec: 放已经回文的子串
|
||||
void search(const string& s, int startIndex, vector<string>& vec) {
|
||||
vector<string> path; // 放已经回文的子串
|
||||
// startIndex: 搜索的起始位置
|
||||
void backtracking (const string& s, int startIndex) {
|
||||
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
|
||||
if (startIndex >= s.size()) {
|
||||
result.push_back(vec);
|
||||
result.push_back(path);
|
||||
return;
|
||||
}
|
||||
for (int i = startIndex; i < s.size(); i++) {
|
||||
if (isPalindrome(s, startIndex, i)) { // 是回文子串
|
||||
// 获取[startIndex,i]在s中的子串
|
||||
string str = s.substr(startIndex, i - startIndex + 1);
|
||||
vec.push_back(str);
|
||||
path.push_back(str);
|
||||
} else { // 如果不是则直接跳过
|
||||
continue;
|
||||
}
|
||||
search(s, i + 1, vec); // 寻找i+1为起始位置的子串
|
||||
vec.pop_back(); // 回溯过程,弹出本次已经填在的子串
|
||||
backtracking(s, i + 1, path); // 寻找i+1为起始位置的子串
|
||||
path.pop_back(); // 回溯过程,弹出本次已经填在的子串
|
||||
}
|
||||
}
|
||||
bool isPalindrome(const string& s, int start, int end) {
|
||||
@ -41,8 +108,8 @@ private:
|
||||
public:
|
||||
vector<vector<string>> partition(string s) {
|
||||
result.clear();
|
||||
vector<string> vec;
|
||||
search(s, 0 , vec);
|
||||
path.clear();
|
||||
backtracking(s, 0);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
@ -1,13 +1,60 @@
|
||||
## 题目地址
|
||||
https://leetcode-cn.com/problems/remove-all-adjacent-duplicates-in-string/
|
||||
|
||||
## 思路
|
||||
> 匹配问题都是栈的强项
|
||||
|
||||
# 1047. 删除字符串中的所有相邻重复项
|
||||
|
||||
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。
|
||||
|
||||
在 S 上反复执行重复项删除操作,直到无法继续删除。
|
||||
|
||||
在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
|
||||
|
||||
|
||||
示例:
|
||||
输入:"abbaca"
|
||||
输出:"ca"
|
||||
解释:
|
||||
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。
|
||||
|
||||
|
||||
提示:
|
||||
1 <= S.length <= 20000
|
||||
S 仅由小写英文字母组成。
|
||||
|
||||
# 思路
|
||||
|
||||
## 题外话
|
||||
|
||||
这道题目就像是我们玩过的游戏对对碰,如果相同的元素放在挨在一起就要消除。
|
||||
|
||||
可能我们在玩游戏的时候感觉理所当然应该消除,但程序又怎么知道该如果消除呢,特别是消除之后又有新的元素可能挨在一起。
|
||||
|
||||
此时游戏的后端逻辑就可以用一个栈来实现(我没有实际考察对对碰或者爱消除游戏的代码实现,仅从原理上进行推断)。
|
||||
|
||||
游戏开发可能使用栈结构,编程语言的一些功能实现也会使用栈结构,实现函数递归调用就需要栈,但不是每种编程语言都支持递归,例如:
|
||||
|
||||
<img src='../pics/1047.删除字符串中的所有相邻重复项.png' width=600> </img></div>
|
||||
|
||||
**递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中**,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
|
||||
|
||||
相信大家应该遇到过一种错误就是栈溢出,系统输出的异常是`Segmentation fault`(当然不是所有的`Segmentation fault` 都是栈溢出导致的) ,如果你使用了递归,就要想一想是不是无限递归了,那么系统调用栈就会溢出。
|
||||
|
||||
而且**在企业项目开发中,尽量不要使用递归!**在项目比较大的时候,由于参数多,全局变量等等,使用递归很容易判断不充分return的条件,非常容易无限递归(或者递归层级过深),**造成栈溢出错误(这种问题还不好排查!)**
|
||||
|
||||
好了,题外话over,我们进入正题。
|
||||
|
||||
## 正题
|
||||
|
||||
本题要删除相邻相同元素,其实也是匹配问题,相同左元素相当于左括号,相同右元素就是相当于右括号,匹配上了就删除。
|
||||
|
||||
那么再来看一下本题:可以把字符串顺序放到一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。
|
||||
|
||||
这道题目就像是我们玩过的游戏对对碰, 可以把字符串放到与一个栈中,然后如果相同的话 栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。
|
||||
|
||||
如动画所示:
|
||||
|
||||
<video src='../video/1047.删除字符串中的所有相邻重复项.mp4' controls='controls' width='640' height='320' autoplay='autoplay'> Your browser does not support the video tag.</video></div>
|
||||
<img src='../video/1047.删除字符串中的所有相邻重复项.gif' width=600> </img></div>
|
||||
|
||||
从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒叙的,所以在对字符串进行反转一下,就得到了最终的结果。
|
||||
|
||||
@ -22,20 +69,20 @@ public:
|
||||
if (st.empty() || s != st.top()) {
|
||||
st.push(s);
|
||||
} else {
|
||||
st.pop(); // s == st.top()
|
||||
st.pop(); // s 与 st.top()相等的情况
|
||||
}
|
||||
}
|
||||
string result = "";
|
||||
while(!st.empty()) {
|
||||
while (!st.empty()) { // 将栈中元素放到result字符串汇总
|
||||
result += st.top();
|
||||
st.pop();
|
||||
}
|
||||
reverse(result.begin(), result.end()); // 此时字符串需要反转一下
|
||||
reverse (result.begin(), result.end()); // 此时字符串需要反转一下
|
||||
return result;
|
||||
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
> 更过算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。
|
||||
|
||||
|
6
problems/回溯总结.md
Normal file
6
problems/回溯总结.md
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
* 集合问题
|
||||
* 切割问题
|
||||
* 子集问题
|
||||
* 排列问题
|
||||
* 皇后问题
|
BIN
video/1047.删除字符串中的所有相邻重复项.gif
Normal file
BIN
video/1047.删除字符串中的所有相邻重复项.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 662 KiB |
Reference in New Issue
Block a user