✨ 吾与春风皆过客,君携春水揽星河 🌏
🔥个人专栏:算法训练
🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
目录
1. 找出所有子集的异或总和再求和
思路:
假设存在[1,2,3]这个集合,那么开始的时候是空集合,画出其决策树
全局变量:sum和path
sum用于求异或和,path用来当进入某一层时,异或该数,
方法dfs : dfs(nums[],pos) 在pos那层
回溯:异或运算:消消乐(相同的数异或为0)
AC代码如下:
int path, sum;
void dfs(vector<int>& nums, int pos)
{
sum += path;
for (int i = pos; i < nums.size(); i++)
{
path ^= nums[i];
dfs(nums, i + 1);
path ^= nums[i]; //恢复现场
}
}
int subsetXORSum(vector<int>& nums) {
dfs(nums, 0);
return sum;
}
2. N皇后
思路:
深度优先遍历(DFS)
函数名:void dfs(int r): 深度优先遍历函数。参数r:从第r行开始放棋子,处理第r行。
递归结束判定:见代码,当 r == n的时候,说明应该处理第 n行了,也代表第 0~n-1行放好棋子,也就是整个棋盘放好了棋子,也就是得到了一种解,也就是递归结束。
第r行,第i列能不能放棋子:用数组dg udl 分别表示:点对应的两个斜线以及列上是否有皇后。
dg[x + y] 表示 y行x列处,所在的对角线上有没有棋子,udg[n - x + y]表示 r行i列处,所在的反对角线上有没有棋子,cor[i]表示第i列上有没有棋子。如果 y行x列的对角线,反对角线上都没有棋子,即!col[x] && !dg[i + r] && !udg[n - i + r]为真,则代表 x行y列处可以放棋子。
AC代码如下:
vector<vector<string>> ret;
vector<string> path;
bool col[20], udg[20], dg[20];
int n;
void dfs(int pos) {
if (pos == n) {
ret.push_back(path);
return;
}
int x = pos;
for (int y = 0; y < n; y++) { // 尝试在这一行放皇后
if (!col[y] && !dg[y - x + n] && !udg[y + x]) {
col[y] = dg[y - x + n] = udg[y + x] = true;
path[x][y] = 'Q';
dfs(x + 1);
path[x][y] = '.';
col[y] = dg[y - x + n] = udg[y + x] = false;
}
}
}
vector<vector<string>> solveNQueens(int _n) {
n = _n;
path.resize(n);
for (int i = 0; i < n; i++) {
path[i].append(n, '.');
}
dfs(0);
return ret;
}
3. 有效的数独
思路:
创建二维数组 rows 和 col 分别记录数独的每一行和每一列中的每个数字的出现次数
创建三维数组 grid 记录数独的每一个小九宫格中的每个数字的出现次数
其中rows[i][num]、columns[j][num] 和 gird[i / 3] [j / 3][num] 分别表示数独的第 i 行第 j 列的单元格所在的行、列和小九宫格中,数字 num + 1 出现的次数,其中 0≤ num <9,对应的数字 num+1 满足 1≤num+1≤9。
AC代码如下:
bool col[9][10], row[9][10];
bool grid[3][3][10];
bool isValidSudoku(vector<vector<char>>& board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; 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;
}
4. 解数独
思路:
和上题类似的是,我们同样用
创建二维数组 rows 和 col 分别记录数独的每一行和每一列中的每个数字的出现次数
创建三维数组 grid 记录数独的每一个小九宫格中的每个数字的出现次数
我们首先对整个数独数组进行遍历,当我们遍历到第 i 行第 j 列的位置:
如果该位置是一个空白格,那么我们将其加入一个用来存储空白格位置的列表中,方便后续的递归操作;
如果该位置是一个数字 num,那么我们需要将 row[ i ][ num ],col[ j ][num以及 block[ ⌊i/3⌋ ][ ⌊j/3⌋ ][num] 均置为 True。
当我们结束了遍历过程之后,就可以开始递归枚举。当递归到第 i 行第 j 列的位置时,我们枚举填入的数字 num。根据题目的要求,数字 num 不能和当前行、列、九宫格中已经填入的数字相同,因此 row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = false;
当我们填入了数字 num 之后,我们要将上述的三个值都置为 True,并且继续对下一个空白格位置进行递归。在回溯到当前递归层时,我们还要将上述的三个值重新置为 False。
AC代码如下:
bool row[9][10], col[9][10];//储存每一行每一列存在的数字
bool grid[3][3][10]; //储存每一个 3*3宫存在的数字
bool dfs(vector<vector<char>>& board) {
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] == '.') {
//填数
for (int num = 1; num <= 9; num++) {
if (!row[i][num] && !col[j][num] && !grid[i / 3][j / 3][num]) {
board[i][j] = '0' + num;
row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;
if (dfs(board) == true) return true;
//恢复现场
board[i][j] = '.';
row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = false;
}
}
// 当前格子 1- 9 都不能填,那么就只能返回到上一个格子进行修改
return false;
}
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
//初始化
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
if (board[i][j] != '.') {
int num = board[i][j] - '0';
row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;
}
}
}
dfs(board);
}
5. 单词搜索
思路:
设函数 dfs(board,words,x,y,pos) 表示判断以网格的 (x,y)位置出发,能否搜索到单词 words[pos..],其中 words[pos..] 表示字符串 word 从第 pos 个字符开始的后缀子串。如果能搜索到,则返回 true,反之返回 false。函数 dfs(board,words,x,y,pos)的执行步骤如下:
- 如果 board[ x ][ y ] = words[pos],当前字符不匹配,直接返回 false。
- 如果当前已经访问到字符串的末尾,且对应字符依然匹配,此时直接返回 true。
- 否则,遍历当前位置的所有相邻位置。如果从某个相邻位置出发,能够搜索到子串 word[pos+1..],则返回 true,否则返回 false。
这样,我们对每一个位置 (x,y)都调用函数 dfs(board,words,x,y,pos)进行检查:只要有一处返回 true,就说明网格中能够找到相应的单词,否则说明不能找到。
为了防止重复遍历相同的位置,需要额外维护一个与 board 等大的 st 数组,用于标识每个位置是否被访问过。每次遍历相邻位置时,需要跳过已经被访问的位置。
注意:
if (dfs(board, word, i, j, 0)) return true;,而不是return dfs(board, word, i, j, 0);
AC代码如下:
int dir[4][2] = {
{1,0},{0,1},{-1,0},{0,-1}
};
int n, m;
bool st[505][505];
bool dfs(vector<vector<char>>& board, string word, int x, int y, int pos) {
if (pos == word.size()) return true;
//向量的方式,定义上下左右四个位置
for (int i = 0; i < 4; i++) {
int dx = x + dir[i][0], dy = y + dir[i][1];
if (dx >= 0 && dx < n && dy >= 0 && dy < m && board[dx][dy] == word[pos] && !st[dx][dy]) {
st[dx][dy] = true;
if (dfs(board, word, dx, dy, pos + 1)) return true;
st[dx][dy] = false;
}
}
return false;
}
bool exist(vector<vector<char>>& board, string word) {
n = board.size(), m = board[0].size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == word[0]) {
st[i][j] = true;
if (dfs(board, word, i, j, 1)) return true;
st[i][j] = false;
}
}
}
return false;
}
6. 黄金矿工
思路:
该题与上题解题步骤基本类似,只不过该题需要多加一个参数sum,来记录每条的和,然后求出最大值即可。
AC代码如下:
int dir[4][2] = {
{1,0},{0,1},{-1,0},{0,-1}
};
int maxi = 0;
int n, m;
bool st[505][505];
void dfs(vector<vector<int>>& grid, int x, int y, int sum) {
maxi = max(maxi, sum);
for (int i = 0; i < 4; i++) {
int dx = x + dir[i][0], dy = y + dir[i][1];
if (dx >= 0 && dx < n && dy >= 0 && dy < m && grid[dx][dy] > 0 && !st[dx][dy]) {
st[dx][dy] = true;
dfs(grid, dx, dy, sum + grid[dx][dy]);
st[dx][dy] = false;
}
}
}
int getMaximumGold(vector<vector<int>>& grid) {
n = grid.size(), m = grid[0].size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] != 0) {
st[i][j] = true;
dfs(grid, i, j, grid[i][j]);
st[i][j] = false;
}
}
}
return maxi;
}
7. 不同路径III
思路:
该题我们选择dfs的方法,主要步骤和 5、6题过程类似,但是在进行dfs之前,我们先需要做一些初始化的步骤,比如找到起始位置,和记录应该需要走的总步数
AC代码如下:
bool st[25][25];
int dir[4][2] = {
{1,0},{0,1},{-1,0},{0,-1}
};
int ret, step; //统计走的方法,和需要走的总步数
int n, m;
void dfs(vector<vector<int>>& grid, int x, int y, int cnt) {
if (grid[x][y] == 2) { //走到终止位置
if (cnt == step) ret++; //看是否走完所有路程
return;
}
for (int i = 0; i < 4; i++) {
int dx = x + dir[i][0], dy = y + dir[i][1];
if (dx >= 0 && dx < n && dy >= 0 && dy < m && grid[dx][dy] != -1 && !st[dx][dy]) {
st[dx][dy] = true;
dfs(grid, dx, dy, cnt + 1);
st[dx][dy] = false;
}
}
}
int uniquePathsIII(vector<vector<int>>& grid) {
n = grid.size(), m = grid[0].size();
int bx = 0, by = 0; //记录起始位置
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] == 0) step++; // 统计需要走的总步数
else if (grid[i][j] == 1) bx = i, by = j;
}
}
step += 2; //还需要加上起始位置和终止位置走的步数
st[bx][by] = true;
dfs(grid, bx, by, 1);
return ret;
}
8. 体操队形
思路:
AC代码如下:
#include <iostream>
#include <algorithm>
using namespace std;
int a[14];
bool vis[14];
int ans = 0, n;
void dfs(int pos){
if (pos == n + 1) {
ans++;
return;
}
for (int i = 1; i <= n; i++){
if (vis[i]) continue; //剪枝已经放过了
if (vis[a[i]]) return; //当前要放的元素要在其对应元素之前
vis[i] = true; //放上第 i 号队员
dfs(pos + 1);
vis[i] = false; //恢复现场
}
}
int main(){
cin >> n;
for (int i = 1; i <= n; i++)cin >> a[i];
dfs(1);
cout << ans << endl;
return 0;
}