题目描述:地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:输入:m = 2, n = 3, k = 1 输出:3
示例 2:输入:m = 3, n = 1, k = 0 输出:1
提示:1 <= n,m <= 100 0 <= k <= 20
1、深度优先DFS
深度优先搜索: 可以理解为暴力法模拟机器人在矩阵中的所有路径。DFS 通过递归,先朝一个方向搜到底,再回溯至上个节点,沿另一个方向搜索,以此类推。
剪枝: 在搜索中,遇到数位和超出目标值、此元素已访问,则应立即返回,称之为可行性剪枝
由题可知,机器人只能往右或者往下移动,当机器人移动时,需要查看要移动到的格子下标数位和是否大过 k,大了话就不给移动(提前进行剪枝返回),对于移动到过的格子,要做 true 处理。
代码讲解
1、先新建一个 m 行 n 列的数组,全部初始化为 false
2、进行深度优先遍历,如果索引越界、i 和 j 的数位和大于 k、该格子已遍历过,那么提前剪 枝,返回0
3、如果符合条件,将数组该格子变为 true,代表已遍历
4、继续进行递归,往右的和往下的,同时将递归的返回值 连同 + 1 进行返回
class Solution {
public:
int movingCount(int m, int n, int k)
{
int count;
//临时变量visited记录格子是否被访问过
vector<vector<bool>> visited(m,vector<bool>(n,false));
return dfs(m,n,k,0,0,visited);
}
int dfs(int& m, int& n, int& k, int row, int col, vector<vector<bool>>& visited)
{
int row_ge = row%10;
int row_shi = row/10;
int col_ge = col%10;
int col_shi = col/10;
int sum = row_ge+row_shi+col_ge+col_shi;
//越界 或 行列坐标的数位之和大于k 或 该格子已经访问过
if(row<0 || row>=m || col<0 || col>=n || sum>k || visited[row][col]) return 0;
//标注这个格子被访问过
visited[row][col] = true;
return 1+dfs(m,n,k,row+1,col,visited)+dfs(m,n,k,row-1,col,visited)
+dfs(m,n,k,row,col-1,visited)+dfs(m,n,k,row,col+1,visited);
}
};
每个可以到达的格子,始终可以通过左边或者上方到达。
回溯返回值: 返回 1 + 右方搜索的可达解总数 + 下方搜索的可达解总数
,代表从本单元格递归搜索的可达解总数。
return 1+dfs(m,n,k,row+1,col,visited)+dfs(m,n,k,row,col+1,visited);
2、广度优先遍历 BFS
BFS/DFS : 两者目标都是遍历整个矩阵,不同点在于搜索顺序不同。DFS 是朝一个方向走到底,再回退,以此类推;BFS 则是按照“平推”的方式向前搜索。
BFS 实现: 通常利用队列实现广度优先遍历。
算法解析:
1、初始化: 将机器人初始点 (0, 0) 加入队列 queue ;
2、迭代终止条件: queue 为空。代表已遍历完所有可达解。
3、迭代工作:
(1) 单元格出队: 将队首单元格的坐标索引弹出,作为当前搜索单元格。
(2)判断是否跳过: 若 ① 行列索引越界 或 ② 数位和超出目标值 k 或 ③ 当前元素已访 问过时,执行 continue 。
(3)标记当前单元格 :将单元格索引 (i, j) 存入 visited 中,代表此单元格 已被访问 过 。
(4)单元格入队: 将当前元素的下方、右方单元格的坐标索引加入 queue 。
4、返回值: Set visited 的长度 len(visited) ,即可达解的数量。
class Solution {
public:
int movingCount(int m, int n, int k)
{
//临时变量visited记录格子是否被访问过
vector<vector<bool>> visited(m,vector<bool>(n,false));
//创建一个队列,保存的是访问到的格子坐标,是一组键值对
queue<pair<int,int>> queue;
//从左上角坐标[0,0]点开始访问,把坐标点加入到队列的队尾
queue.push(make_pair(0,0));
visited[0][0] = true; //[0,0]坐标的格子已经访问
int res = 1; //能够到达的格子数
// 向下和向右的方向数组
int dx[2] = {0, 1};
int dy[2] = {1, 0};
while (queue.size() > 0) //while(!queue.empty())
{
//获取当前格子的坐标值
pair<int,int> index = queue.front(); //auto index = queue.front();
//这里的pop()函数表示的是移除队列头部元素,因为队列是先进先出,从尾部添加,从头部移除
queue.pop();
for(int i=0;i<2;i++)
{
//i=0时,坐标值+(0,1)表示向下移动一个格子
//i=1时,坐标值+(1,0)表示向右移动一个格子
int tx = index.first + dx[i];
int ty = index.second + dy[i];
//边界条件的判断,判断当前格子坐标是否满足条件,visited[tx][ty]判断这个格子是否被访问过
if (tx < 0 || tx >= m || ty < 0 || ty >= n || k < Sum(tx, ty) || visited[tx][ty])
{
continue; //退出本次循环,进入下一次循环
}
//标注这个格子被访问过,并且插入到队列中
visited[tx][ty] = true;
queue.push(make_pair(tx,ty));
res++;
}
}
return res;
}
int Sum(int x,int y) {
int sum = 0;
while(x > 0)
{
sum += x % 10;
x /= 10;
}
while(y > 0)
{
sum += y % 10;
y /=10;
}
return sum;
}
};