引题
在一个n*n的方阵中,找到一条左上角到右下角的路径,每个格子只能走一次,现在两个整数数组col,row,对应每行每列,要求路径所经过的格子按照行列分别计数,等于对应的值,其中0<=n<=20且答案唯一;
输入格式:
第一行输入格子的行列的长度n;
第二行输入行数组row[x];
第三行数入列数组col[y];
输出格式:
输出一行整数表示路径;
分析
路径的种类有很多,但答案路径只有一种,不能确定寻路次数,因此for循环是无法使用的,这里我们使用回溯算法,并且使用auto关键字来进行数据访问
分步实现:
第一步 确定状态
因为每个格子只能走一遍,所以我们开一个二维数组来记录格子的访问情况,又因为每个格子只能走一次,所以这里我们使用bool类型的数组
class solution
{public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));// 这里开一个数组来记录路径
vector<int>path;// 同时也要确定格子所在的下标
function<bool(int, int)>dfs = [&](int x, int y) //将基础类型作为参数,其他的作为全局上下文
{
};
}
};
第二步 确定非法状态
某些状态是不符合题目条件的,遇到这种状态直接返回。当前坐标不在网格中;当前坐标已被走过;当前行列的格子数达到上限。
class solution
{public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;// 当前坐标不在网格中
if (vis[x][y]) return false;// 当前坐标已被走过
if (row[x] == 0 || col[y] == 0) return false;// 当前行列的格子数达到上限
}
}
};
第三步 更新状态
当行列数目减一,就标记当前格子为已走过,添加路径数组为当前路径坐标
class solution
{public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;
if (vis[x][y]) return false;
if (row[x] == 0 || col[y] == 0) return false;
row[x]--;
col[y]--;
vis[x][y] = true;// 当行列数目减一,就标记当前格子为已走过
path.push_back(x * n + y);// 添加路径数组为当前路径坐标
}
}
};
第四步 结束状态
当坐标达到终点且行列数组的最大可用格子数为0
class solution
{public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;
if (vis[x][y]) return false;
if (row[x] == 0 || col[y] == 0) return false;
row[x]--;
col[y]--;
vis[x][y] = true;
path.push_back(x * n + y);
if (x == n - 1 && y == n - 1 &&
accumulate(row, row + n, 0) == 0 &&
accumulate(col, col + n, 0) == 0) return true;// 当坐标达到终点且行列数组的最大可用格子数为0
}
}
};
第五步 状态转移
枚举下个状态,当前格子往上下左右四个方向进行运动,当找到一个合法路径时,直接返回
class solution
{
int dx[4] = { 0,0,1,-1 };
int dy[4] = { 1,-1,0,0 };//当前格子往上下左右四个方向进行运动
public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;
if (vis[x][y]) return false;
if (row[x] == 0 || col[y] == 0) return false;
row[x]--;
col[y]--;
vis[x][y] = true;
path.push_back(x * n + y);
if (x == n - 1 && y == n - 1 &&
accumulate(row, row + n, 0) == 0 &&
accumulate(col, col + n, 0) == 0) return true;
}
}
};
第六步 状态还原
进行更新状态相反的操作,行列最大格子数目加一,将返回后的格子未标记,删除路径数组的路径
class solution
{
int dx[4] = { 0,0,1,-1 };
int dy[4] = { 1,-1,0,0 };
public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;
if (vis[x][y]) return false;
if (row[x] == 0 || col[y] == 0) return false;
row[x]--;
col[y]--;
vis[x][y] = true;
path.push_back(x * n + y);
if (x == n - 1 && y == n - 1 &&
accumulate(row, row + n, 0) == 0 &&
accumulate(col, col + n, 0) == 0) return true;
for (int d = 0; d < 4; d++) {
if (dfs(x + dx[d], y + dy[d])) return true;
}
row[x]++;
col[y]++;
vis[x][y] = false;
path.pop_back();
return false;
};
dfs(0, 0);
return path;
}
};
第七步 状态剪枝
回溯算法的精髓,能大大减少回溯的时间,避免做不必要的循环
我们发现:当某列格子最大数目为0,但其左侧的格子不为0时,路线将无法做到清零和达到右侧格子中,故我们将其加入判断中
class solution
{
int dx[4] = { 0,0,1,-1 };
int dy[4] = { 1,-1,0,0 };
public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;
if (vis[x][y]) return false;
if (row[x] == 0 || col[y] == 0) return false;
if (row[x] == 1 && accumulate(row, row + n, 0) != 0) return false;
if (col[y] == 1 && accumulate(col, col + n, 0) != 0) return false;
row[x]--;
col[y]--;
vis[x][y] = true;
path.push_back(x * n + y);
if (x == n - 1 && y == n - 1 &&
accumulate(row, row + n, 0) == 0 &&
accumulate(col, col + n, 0) == 0) return true;
for (int d = 0; d < 4; d++) {
if (dfs(x + dx[d], y + dy[d])) return true;
}
row[x]++;
col[y]++;
vis[x][y] = false;
path.pop_back();
return false;
};
dfs(0, 0);
return path;
}
};
对于不同的状态,回溯的第七步可能存在顺序和写法上的差异,根据实际情况进行修改。
最后上全代码:
using namespace std;
class solution
{
int dx[4] = { 0,0,1,-1 };
int dy[4] = { 1,-1,0,0 };
public:
vector<int>pathpuzzle(int col[], int row[], int n) {
vector<vector<bool>>vis(n, vector<bool>(n, false));
vector<int>path;
function<bool(int, int)>dfs = [&](int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= n) return false;
if (vis[x][y]) return false;
if (row[x] == 0 || col[y] == 0) return false;
if (row[x] == 1 && accumulate(row, row + n, 0) != 0) return false;
if (col[y] == 1 && accumulate(col, col + n, 0) != 0) return false;
row[x]--;
col[y]--;
vis[x][y] = true;
path.push_back(x * n + y);
if (x == n - 1 && y == n - 1 &&
accumulate(row, row + n, 0) == 0 &&
accumulate(col, col + n, 0) == 0) return true;
for (int d = 0; d < 4; d++) {
if (dfs(x + dx[d], y + dy[d])) return true;
}
row[x]++;
col[y]++;
vis[x][y] = false;
path.pop_back();
return false;
};
dfs(0, 0);
return path;
}
};
int main() {
int n;
cin >> n;
int row[10001], col[10001];
for (int i = 0; i < n; i++)cin >> row[i];
for (int i = 0; i < n; i++)cin >> col[i];
auto path = (new solution())->pathpuzzle(row, col, n);
for (int i = 0; i < path.size(); i++)cout << path[i] << " ";
}