1. 目标和
这个题目很类似我们之前做过的子集问题,对于子集我们是当前值选或者是不选,而对于这道题目,是对于+或者-我们选或者不选,我们依然是先画出决策树。
接下来我们就可以直接去写代码了。
class Solution {
int count = 0;
public:
int findTargetSumWays(vector<int>& nums, int target) {
dfs(nums, 0, target, 0);
return count;
}
void dfs(vector<int>& nums, int pos, int target, int path)
{
if(pos == nums.size())
{
if(path == target) count++;
return;
}
// 加法
dfs(nums, pos + 1, target, path + nums[pos]);
// 减法
dfs(nums, pos + 1, target, path - nums[pos]);
}
};
2. 组合总和
解法一:每个位置选择选一个数,那么此时就会有一个决策树
class Solution {
vector<vector<int>> ret;
vector<int> path;
int aim;
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
aim = target;
dfs(candidates, 0, 0);
return ret;
}
void dfs(vector<int>& candidates, int pos, int sum)
{
if(sum > aim || pos == candidates.size())
{
return;
}
if(sum == aim)
{
ret.push_back(path);
return;
}
for(int i = pos; i < candidates.size(); i++)
{
path.push_back(candidates[i]);
dfs(candidates, i, sum + candidates[i]);
// 恢复现场
path.pop_back();
}
}
};
解法二:枚举每个值出现的次数,那么此时就会有一个决策树
class Solution
{
int aim;
vector<int> path;
vector<vector<int>> ret;
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target)
{
aim = target;
dfs(candidates, 0, 0);
return ret;
}
void dfs(vector<int>& candidates, int pos, int sum)
{
if (sum == aim)
{
ret.push_back(path);
return;
}
if (sum > aim || pos == candidates.size()) return;
// 枚举个数
for (int k = 0; k * candidates[pos] + sum <= aim; k++)
{
if (k) path.push_back(candidates[pos]);
dfs(candidates, pos + 1, sum + k * candidates[pos]);
}
// 恢复现场
for (int k = 1; k * candidates[pos] + sum <= aim; k++)
{
path.pop_back();
}
}
};
3. 字母大小写全排列
这个题目我们可以从开始位置开始,如果是数字就直接忽略,如果是字母就有两种情况,一种是变,一种是不变,此时我们来画一下决策树
class Solution {
string path;
vector<string> ret;
public:
vector<string> letterCasePermutation(string s) {
dfs(s, 0);
return ret;
}
void dfs(string& s, int pos)
{
if(s.size() == pos)
{
ret.push_back(path);
return;
}
// 不变
path += s[pos];
dfs(s, pos + 1);
path.pop_back(); // 恢复现场
// 改变
if(s[pos] <= 'z' && s[pos] >= 'a')
{
s[pos] -= 32;
path += s[pos];
dfs(s, pos + 1);
path.pop_back(); // 恢复现场
}
else if(s[pos] <= 'Z' && s[pos] >= 'A')
{
s[pos] += 32;
path += s[pos];
dfs(s, pos + 1);
path.pop_back(); // 恢复现场
}
}
};
4. 优美的排列
此时我们可以按照每个位置选不同的值来画出我们的决策树
class Solution {
bool check[16] = {false};
int ret;
public:
int countArrangement(int n) {
dfs(1, n); // 下标从1开始
return ret;
}
void dfs(int pos, int n)
{
if(pos == n + 1)
{
ret++;
return;
}
for(int i = 1; i <= n; i++)
{
// 1.递归的过程中不能使用重复元素
// 2.递归的过程中下标和值能够整除
// pos是下标
if(!check[i]&& (pos % i == 0 || i % pos == 0))
{
check[i] = true;
dfs(pos + 1, n);
check[i] = false; // 恢复现场
}
}
}
};
5. N 皇后
此时我们不能一个一个位置的考虑,而应该一行一行考虑,先来画出我们的决策树
class Solution {
bool checkCol[10] = { false };
bool checkRow[10] = { false };
bool checkDig1[20] = { false };
bool checkDig2[20] = { false };
vector<vector<string>> ret;
vector<string> path;
public:
vector<vector<string>> solveNQueens(int n) {
// 先初始化将全都path为"."
path.resize(n);
for(int i = 0; i < n; i++)
{
path[i].append(n, '.');
}
dfs(0, n);
return ret;
}
void dfs(int row, int& n)
{
if(n == row)
{
ret.push_back(path);
return;
}
// 尝试在这一行放皇后
for(int col = 0; col < n; col++)
{
// 判断四条线是否有其他皇后放了
if(!checkRow[row] && !checkCol[col] && !checkDig1[row - col + n] && !checkDig2[row + col])
{
path[row][col] = 'Q';
checkRow[row] = checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = true;
dfs(row + 1, n);
// 恢复现场
path[row][col] = '.';
checkRow[row] = checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = false;
}
}
}
};
6. 有效的数独
我们这个题目其实和递归没有任何关系,但是这个题目能够很好的帮我们理解上一个题目N皇后的剪枝策略以及下一个题目做支撑,建三个数组标记⾏、列以及 3*3 ⼩⽅格中是否出现 1~9 之间的数字即可解决。
class Solution {
bool row[9][10] = { false }; // 表示这一行出否出现y坐标元素
bool col[9][10] = { false }; // 表示这一列是否出现y坐标的值
bool grid[3][3][10] = { false }; //表示这个小方块是否出现z坐标的值
public:
bool isValidSudoku(vector<vector<char>>& board) {
for(int i = 0; i < board.size(); i++)
{
for(int j = 0; j < board[i].size(); j++)
{
if(board[i][j] != '.')
{
int num = board[i][j] - '0';
// 是否出现该数字
if(row[i][num] || col[j][num] || grid[i/3][j/3][num])
{
return false;
}
row[i][num] = col[j][num] = grid[i/3][j/3][num] = true;
}
}
}
return true;
}
};
7. 解数独
有了上一个题目的判断,我们这个题目就比较简单了,先画出决策树,我们没遇到一个格子,先判断能不能填,能填就填,不能填就换下一个数。
class Solution {
bool row[9][10] = { false }; // 表示这一行出否出现y坐标元素
bool col[9][10] = { false }; // 表示这一列是否出现y坐标的值
bool grid[3][3][10] = { false }; //表示这个小方块是否出现z坐标的值
public:
void solveSudoku(vector<vector<char>>& board) {
for(int i = 0; i < board.size(); i++)
{
for(int j = 0; j < board[i].size(); j++)
{
if(board[i][j] != '.')
{
// 先把所有为数字的标记为已使用
int val = board[i][j] - '0';
row[i][val] = true;
col[j][val] = true;
grid[i/3][j/3][val] = true;
}
}
}
dfs(board);
}
bool dfs(vector<vector<char>>& board)
{
for(int i = 0; i < board.size(); i++)
{
for(int j = 0; j < board[i].size(); j++)
{
if(board[i][j] == '.')
{
// 填数,填写的数字分别为1-9
for(int k = 1; k <=9 ; k++)
{
if(!row[i][k] && !col[j][k] && !grid[i/3][j/3][k])
{
board[i][j] = k + '0';
row[i][k] = col[j][k] = grid[i/3][j/3][k] = true;
// 本次填数成功
if(dfs(board)) return true;
board[i][j] = '.';
row[i][k] = col[j][k] = grid[i/3][j/3][k] = false;
}
}
// 1-9全部都填写了,但是依然不满足条件
return false;
}
}
}
return true;
}
};
8. 单词搜索
我们依然是先画出这个题目的决策树。
我们可以看到上面的决策树之后,我们会发现当我们找到word里面的一个单词在矩阵里面的时候,我们会遍历上下左右四个方向的字母,此时我们就可以使用下面的策略来帮助我们访问这四个位置。
此时我们就可以直接来写代码啦。
class Solution {
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
bool vis[7][7] = {false};
int m, n;
public:
bool exist(vector<vector<char>>& board, string word) {
m = board.size(), n = board[0].size();
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
// 找深搜的起点
if(board[i][j] == word[0])
{
vis[i][j] = true;
if(dfs(board, i, j, word, 1)) return true;
vis[i][j] = false; // 恢复现场
}
}
}
return false;
}
bool dfs(vector<vector<char>>& board, int i, int j, string word, int pos)
{
if(pos == word.size())
{
return true;
}
for(int k = 0; k < 4; k++)
{
int x = dx[k] + i;
int y = dy[k] + j;
if((x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && board[x][y] == word[pos]))
{
vis[x][y] = true;
if(dfs(board, x, y, word, pos + 1)) return true;
vis[x][y] = false; // 恢复现场
}
}
return false;
}
};
9. 黄金矿工
class Solution {
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
bool vis[16][16] = {false};
int m, n;
int ret;
public:
int getMaximumGold(vector<vector<int>>& grid) {
m = grid.size();
n = grid[0].size();
for(int i = 0; i < m; i++)
{
for(int j = 0; j < n; j++)
{
// 只要不为0,就可以开始深度递归
if(grid[i][j] != 0)
{
vis[i][j] = true;
dfs(grid, i, j, grid[i][j]);
vis[i][j] = false; // 恢复现场
}
}
}
return ret;
}
void dfs(vector<vector<int>>& grid, int i, int j, int path)
{
ret = max(ret, path);
for(int k = 0; k < 4; k++)
{
// 遍历上下左右四个方向
int x = dx[k] + i;
int y = dy[k] + j;
if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] != 0)
{
vis[i][j] = true;
dfs(grid, x, y, path + grid[x][y]);
vis[i][j] = false;
}
}
}
};
10. 不同路径 Ⅲ
在 dfs
函数中,对于当前的位置 (i, j)
:如果到达终点 (2)
,并且走过的步数等于 step
,则找到了一条合法路径,增加 ret
的计数。否则,遍历四个方向(上、下、左、右):计算新的位置 (x, y)
。如果新位置在网格内,且未被访问过,并且不是障碍物,则:标记新位置为已访问。递归调用 dfs
函数,传入新位置和增加的步数。递归返回后,恢复新位置的访问状态(即未访问)。
class Solution {
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
bool vis[21][21] = { false };
int m, n, step;
int ret = 0;
public:
int uniquePathsIII(vector<vector<int>>& grid) {
m = grid.size();
n = grid[0].size();
// 定义起始位置坐标
int x, y;
for(int i = 0; i < m; i++)
for(int j = 0; j < n; j++)
if(grid[i][j] == 0)
step++;
else if(grid[i][j] == 1)
x = i, y = j;
// 开始位置和结束位置也当作一步
step += 2;
// 开始深搜递归
vis[x][y] = true;
dfs(grid, x, y, 1);
return ret;
}
void dfs(vector<vector<int>>& grid, int i, int j, int count)
{
// 递归出口
// 到达终点
if(grid[i][j] == 2)
{
// 判断步数是否正确
if(count == step)
ret++;
return;
}
for(int k = 0; k < 4; k++)
{
int x = dx[k] + i;
int y = dy[k] + j;
if(x >= 0 && x < m && y >=0 && y < n && !vis[x][y] && grid[x][y] != -1)
{
vis[x][y] = true;
dfs(grid, x, y, count + 1);
vis[x][y] = false; // 恢复现场
}
}
}
};