回溯法(backtracking)是优先搜索的一种特殊情况,又称为试探法,常用于需要记录节点状态的深度优先搜索。通常来说,排列、组合、选择类问题使用回溯法比较方便。 顾名思义,回溯法的核心是回溯。在搜索到某一节点的时候,如果我们发现目前的节点(及其子节点)并不是需求目标时,我们回退到原来的节点继续搜索,并且把在目前节点修改的状态还原。这样的好处是我们可以始终只对图的总状态进行修改,而非每次遍历时新建一个图来储存状态。在具体的写法上,它与普通的深度优先搜索一样,都有 [修改当前节点状态]→[递归子节点] 的步骤,只是多了回溯的步骤,变成了 [修改当前节点状态]→[递归子节点]→[回改当前节点状态]。
(1)46.Permutations (Medium)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
给定一个无重复数字的整数数组,求其所有的排列方式。
个人思路
太久没做回溯想不起来应该怎么套用了,本来打算用循环暴力破解,想了一下好像不对,需要进行n!次循环,需要递归。不过递归后就能达到回溯的效果,当然在解题的时候需要注意修改状态和回溯状态,以及注意结束条件。这个回溯挺经典的,还是要牢记。
代码展示
class Solution {
public:
void dfs(vector<vector<int>>& ans,vector<bool>& used,vector<int>& current,vector<int>& nums){
if(current.size()==nums.size()){
ans.push_back(current);
return;
}
for(int i=0;i<nums.size();i++){
if(used[i])
continue;
current.push_back(nums[i]);
used[i]=true;
dfs(ans,used,current,nums);
used[i]=false;
current.pop_back();
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
vector<bool> used(nums.size(),false);
vector<int> current;
dfs(ans,used,current,nums);
return ans;
}
};
(2)77. Combinations (Medium)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
给定一个整数 n 和一个整数 k,求在 1 到 n 中选取 k 个数字的所有组合方法。
个人思路
有了前一题的经验,这道题目就非常好做了。唯一的区别在于前一题是长度为n,此题是长度为给定的k,因此稍作修改就行。还要注意的一点是不要重复,所以设置一个start作为起始递归值避免重复。
代码展示
class Solution {
public:
void dfs(vector<vector<int>>& ans,vector<bool>& used,vector<int>& current,vector<int>& nums,int k,int start){
if(current.size()==k){
ans.push_back(current);
return;
}
for(int i=start;i<nums.size();i++){
if(used[i])
continue;
current.push_back(nums[i]);
used[i]=true;
dfs(ans,used,current,nums,k,i);
used[i]=false;
current.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
vector<int> nums(n);
for(int i=0;i<n;i++){
nums[i]=i+1;
}
vector<vector<int>> ans;
vector<bool> used(nums.size(),false);
vector<int> current;
dfs(ans,used,current,nums,k,0);
return ans;
}
};
(3)79. Word Search (Medium)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
给定一个字母矩阵,所有的字母都与上下左右四个方向上的字母相连。给定一个字符串,求字符串能不能在字母矩阵中寻找到。
个人思路
和前两题同样的思路,不过不同的是这里变成了二维数组,因此需要注意判断和循环的方式。以及字符串的搜寻不是遍历矩阵了,而是在上下左右四个方向搜寻,和做深搜的时候一样,加个四方数组就行。
不过如果只是简单地循环,可能会出现超时,因为特殊情况下可能每次都要遍历到底。因此这里采用剪枝的方式对代码进行优化。
代码展示
class Solution {
public:
vector<int> direc = {-1, 0, 1, 0, -1};
bool dfs(vector<vector<char>>& board, const string& word, vector<vector<bool>>& visited, int current, int a, int b) {
if (current == word.size()) {
return true;
}
int x, y;
for (int i = 0; i < 4; i++) {
x = a + direc[i];
y = b + direc[i+1];
if (x >= 0 && x < board.size() && y >= 0 && y < board[0].size()) {
if (board[x][y] == word[current] && !visited[x][y]) {
visited[x][y] = true;
if (dfs(board, word, visited, current+1, x, y)) {
return true; // 找到了匹配的路径,提前结束
}
visited[x][y] = false;
}
}
}
return false; // 当前路径未找到匹配的路径
}
bool exist(vector<vector<char>>& board, string word) {
vector<vector<bool>> visited(board.size(), vector<bool>(board[0].size(), false));
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[0].size(); j++) {
if (board[i][j] == word[0]) {
visited[i][j] = true;
if (dfs(board, word, visited, 1, i, j)) {
return true; // 找到了匹配的路径,提前结束
}
visited[i][j] = false;
}
}
}
return false;
}
};
(4)51.N皇后问题(Hard)
力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
题目描述
给定一个大小为 n 的正方形国际象棋棋盘,求有多少种方式可以放置 n 个皇后并使得她们互不攻击,即每一行、列、左斜、右斜最多只有一个皇后。
个人思路
看到题目的时候我内心一喜:这不是考试考过的题目么。根据回溯的思路快速编了一遍代码,但是很遗憾没过,仔细又看了一遍题目:哦要求也不能是一条斜边,大脑直接宕机了。看了一下题解的代码也不是很简洁,还是老师写的比较简单易懂。将老师写的伪代码重新写了一遍,啪的一下,很快啊,就过了。
代码展示
class Solution {
public:
vector<vector<string>> solveNQueens(int n) {
vector<vector<string>> solutions;
vector<int> board(n, -1); // board[i] 表示第 i 行的皇后放在了第几列
solveNQueensHelper(solutions, board, 0, n);
return solutions;
}
bool isSafe(const vector<int>& board, int row, int col) {
for (int i = 0; i < row; ++i) {
if (board[i] == col || abs(row - i) == abs(col - board[i])) {
return false;
}
}
return true;
}
void solveNQueensHelper(vector<vector<string>>& solutions, vector<int>& board, int row, int n) {
if (row == n) {
vector<string> solution(n, string(n, '.'));
for (int i = 0; i < n; ++i) {
solution[i][board[i]] = 'Q';
}
solutions.push_back(solution);
return;
}
for (int col = 0; col < n; ++col) {
if (isSafe(board, row, col)) {
board[row] = col;
solveNQueensHelper(solutions, board, row + 1, n);
board[row] = -1; // 回溯,尝试其他位置
}
}
}
};
总结
总体来说,今天的回溯还是比较简单的,有了一个样板后做后面的题目都比较顺畅(除了八皇后属实打击了一下自己)。在做题的时候注意几点:首先是记得修改状态和恢复状态,其次是注意结束时间。另外要适时剪枝。