题目描述
地上有一个rows行和cols列的方格。坐标从 [0,0] 到 [rows-1,cols-1]。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于threshold的格子。 例如,当threshold为18时,机器人能够进入方格[35,37],因为3+5+3+7 = 18。但是,它不能进入方格[35,38],因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
示例1
输入:1,2,3
返回值:3
题目分析
对于计算该机器人能够到达多少格子,首先可以采取暴力的遍历方法,遍历所有的格子,计算能够满足条件的格子数。
这里判断的条件是坐标的数位和要小于要求的数,同时因为是暴力遍历,需要判断这个位置是否能由之前的位置到达。
计算数位和的代码如下,主要是统计数字的每位数之和。
int get(int x) { int res = 0; while (x != 0) { res += x % 10; x /= 10; } return res; }
解题思路
方法1:暴力法,直接遍历数组
为了判断是否能够达到某个位置的格子,定义一个与格子大小一样的访问数组visited[n][m],只有在满足它的上下左右是否已经有格子到达的,才能在满足条件。
访问的双循环,在循环内,判断的条件有:
①坐标和是否满足条件
②坐标的左边或上边是否可达(因为坐标和的大小计算,可以排除右边或下边的位置(一定比当前位置大))
如图:参数(3,3,2)
方法2:回溯法(DFS)
关于数组的遍历问题,可以使用深度遍历,即从初始位置开始一直朝某个方向一直判断,直到不满足条件则回退,去其他方向判断,直到所有方向都判断完成后,可以计算总共可以到达的格子数。
回溯法相比暴力法,不一定要遍历整个数组,碰到不满足的条件的可以排除。
在回溯法中,也需要使用标记数组 visited[][],来判断当前位置是否被访问过。
如图,先按照从右方向递归,再转到向下(之后又是右),重复之前的步骤,直到所有可达的格子遍历完。
代码实现
方法1:暴力法,直接遍历数组
public int movingCount(int threshold, int rows, int cols) { if (threshold == 0) { return 1; } // 标记数组 boolean[][] vis = new boolean[rows][cols]; // 起始坐标默认已经包括 int ans = 1; vis[0][0] = true; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { // 直接遍历整个数组 if ((i == 0 && j == 0) || get(i) + get(j) > threshold) { continue; } // 边界判断,判断是否能从左边过来 if (i - 1 >= 0) { vis[i][j] |= vis[i - 1][j]; } // 边界判断,判断是否能从上边过来 if (j - 1 >= 0) { vis[i][j] |= vis[i][j - 1]; } // 位置可以到达,则数量加1 ans += vis[i][j] ? 1 : 0; } } return ans; } // 计算数位和 private int get(int x) { int res = 0; while (x != 0) { res += x % 10; x /= 10; } return res; }
时间复杂度:O(nm),n是rows排数,m是cols列数,需要遍历整个数组;
空间复杂度:O(nm),需要消耗n*m空间的标记数组;
方法2:回溯法(DFS)
// 使用静态变量作为结果返回 public static int num = 0; public int movingCount(int threshold, int rows, int cols) { // 标志变量数组,标记位置是否访问 boolean[][] visited = new boolean[rows][cols]; movingCount(threshold, rows, cols, 0, 0, visited); return num; } public void movingCount(int threshold, int rows, int cols, int i, int j, boolean[][] visited){ //当坐标非法时排除;当坐标已访问过排除;坐标和不满足时排除 if(i<0 || i>=rows || j<0 || j >= cols || visited[i][j] || check(i,j,threshold)){ return ; } // 剩下的即为满足的情况,数量加1,标记访问过 num++; visited[i][j] = true; // 继续访问该坐标的右、下位置 movingCount(threshold, rows, cols, i+1, j, visited); movingCount(threshold, rows, cols, i, j+1, visited); } // 判断坐标和是否满足条件 public boolean check(int i, int j, int threshold){ int sum = count(i) + count(j); return sum > threshold; } // 计算数位和 public int count(int n){ int res = 0; while(n != 0){ res += n % 10; n /= 10; } return res; }
时间复杂度:O(nm),n是rows排数,m是cols列数,递归需要将数组中所有满足的情况遍历一遍,最差情况遍历整个数组;
空间复杂度:O(nm),需要消耗n*m空间的标记数组;