Day66:机器人的运动范围

时间:2022-07-24
本文章向大家介绍Day66:机器人的运动范围,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

剑指Offer_编程题——机器人的运动范围

题目描述:

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

具体要求:

时间限制: C/C++ 1秒,其他语言2秒 空间限制: C/C++32M,其他语言64M

具体实现:

背景知识介绍:   在做题之前,首先给大家介绍数据结构中典型的两种遍历方式:深度优先遍历以及广度优先遍历。图的遍历是指从图中的任一顶点出发,对图中的所有顶点访问一次且只访问一次。图的遍历操作和树的遍历操作功能相似。图的遍历是图的一种基本操作,图的其它算法如求解图的连通性问题,拓扑排序,求关键路径等都是建立在遍历算法的基础之上。由于图结构本身的复杂性,所以图的遍历操作也较复杂,主要表现在以下四个方面:在图结构中,没有一个“自然”的首结点,图中任意一个顶点都可作为第一个被访问的结点、在非连通图中,从一个顶点出发,只能够访问它所在的连通分量上的所有顶点,因此,还需考虑如何选取下一个出发点以访问图中其余的连通分量。在图结构中,如果有回路存在,那么一个顶点被访问之后,有可能沿回路又回到该顶点。在图结构中,一个顶点可以和其它多个顶点相连,当这样的顶点访问过后,存在如何选取下一个要访问的顶点的问题。图的遍历通常有深度优先搜索和广度优先搜索两种方式,他们对无向图和有向图都适用。   深度优先搜索(Depth_Fisrst Search)遍历类似于树的先根遍历,是树的先根遍历的推广。假设初始状态是图中所有顶点未曾被访问,则深度优先搜索可从图中某个顶点发v 出发,访问此顶点,然后依次从v 的未被访问的邻接点出发深度优先遍历图,直至图中所有和v 有路径相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。以如下图的无向图G5为例,进行图的深度优先搜索:

搜索过程:

  假设从顶点v1 出发进行搜索,在访问了顶点v1 之后,选择邻接点v2。因为v2 未曾访问,则从v2 出发进行搜索。依次类推,接着从v4 、v8 、v5 出发进行搜索。在访问了v5 之后,由于v5 的邻接点都已被访问,则搜索回到v8。由于同样的理由,搜索继续回到v4,v2 直至v1,此时由于v1 的另一个邻接点未被访问,则搜索又从v1 到v3,再继续进行下去由此,得到的顶点访问序列为:

  广度优先搜索:度优先搜索(Breadth_First Search) 遍历类似于树的按层次遍历的过程。假设从图中某顶点v 出发,在访问了v 之后依次访问v 的各个未曾访问过和邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问,直至图中所有已被访问的顶点的邻接点都被访问到。若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。换句话说,广度优先搜索遍历图的过程中以v 为起始点,由近至远,依次访问和v 有路径相通且路径长度为1,2,…的顶点。还是上面的图进行广度优先搜索遍历:

  首先访问v1 和v1 的邻接点v2 和v3,然后依次访问v2 的邻接点v4 和v5 及v3 的邻接点v6 和v7,最后访问v4 的邻接点v8。由于这些顶点的邻接点均已被访问,并且图中所有顶点都被访问,由些完成了图的遍历。得到的顶点访问序列为:

思路一:   根据我们上面的介绍的两种遍历方式,我们可以发现本题就是考察这两种的遍历方式。我们首先用深度优先遍历的方式解决该题。其实深度遍历就是昨天题目中用到的回溯法,我们今天还是用该方法解决该问题。具体的过程如下:使用一个访问数组记录是否已经经过该格子。机器人从(0,0)开始移动,当它准备进入(i,j)的格子时,通过检查坐标的数位来判断机器人是否能够进入。如果机器人能进入(i,j)的格子,接着在判断它是否能进入四个相邻的格子(i,j-1),(i,j+1),(i-1,j),(i+1,j)。另外需要会计算给定整数上的各个位上数之和。具体我们用java和Python两种语言将其实现: 1、首先我们用java将其实现:

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        if(rows<=0||cols<=0||threshold<0)   return 0;
        int[] visited=new int[rows*cols];
        return MovingCount(threshold,rows,cols,0,0,visited);
    }
    private int MovingCount(int threshold,int rows,int cols,int row,int col,int[] visited){
        int count=0;
        if(canWalkInto(threshold, rows, cols, row, col, visited)){
            visited[row*cols+col]=1;
            count=1+MovingCount(threshold,rows,cols,row-1,col,visited)  
                    +MovingCount(threshold,rows,cols,row+1,col,visited)  
                    +MovingCount(threshold, rows, cols, row, col-1, visited)   
                    +MovingCount(threshold, rows, cols, row, col+1, visited);
        }
        return count;
    }
    private boolean canWalkInto(int threshold,int rows,int cols,int row,int col,int[] visited){
        if(row>=0 && row<rows && col>=0 && col<cols
                && getSumOfDigits(row)+getSumOfDigits(col)<=threshold
                && visited[row*cols+col]==0)
            return true;
        else
            return false;
    }
    private int getSumOfDigits(int number){
        int sum=0;
        while(number!=0){
            sum+=number%10;
            number/=10;
        }
        return sum;
    }
}

代码效果图如图所示:

2、接下来我们用python将其实现:

class Solution:
	def movingCount(self, threshold, rows, cols):
		is_visited = [[0 for _ in range(cols)] for _ in range(rows)]
		count = 0
		count = self.movingCountCore(threshold, rows, cols, is_visited, 0, 0)
		return count
	def movingCountCore(self, threshold, rows, cols, is_visited, r, c):
		count = 0
		if(r >= 0 and r<rows and c>=0 and c<cols and r//10+r%10+c//10+c%10<=threshold and is_visited[r][c]==0):
			is_visited[r][c] = 1
			count = 1+ self.movingCountCore(threshold, rows, cols, is_visited, r, c-1)+self.movingCountCore(threshold, rows, cols, is_visited, r, c+1)+self.movingCountCore(threshold, rows, cols, is_visited, r-1, c)+self.movingCountCore(threshold, rows, cols, is_visited, r+1, c)
		return count

代码效果图如图所示:

思路二:   我们前面也介绍了广度优遍历,本题也可以用标准的广度优先搜索。这里需要我们注意的是已访问的点要记录,否则会重复访问。接下来用python实现:

class Solution:
	def movingCount(self, threshold, rows, cols):
		def sums(number):
			counts = 0
			while number % 10:
				n = number % 10
				counts += n
				number = number // 10
			return counts
		if rows <= 0 or cols <= 0 or threshold < 0 :
			return 0
		points = [(0, 0 )]
		direction = [(-1, 0), (0,1), (1,0), (0, -1)]
		visited = [[0 for _ in range(cols)] for _ in range(rows)]
		visited[0][0] = 1
		counts = 0
		while points:
			point = points.pop()
			counts += 1
			i, j = point[0],point[1]
			for d in direction:
				x,y = i + d[0], j + d[1]
				if x < 0 or y < 0 or x >= rows or y >= cols or visited[x][y] or (sums(x) + sums(y)>threshold): 
					continue
				points.append((x, y))
				visited[x][y] = 1
		return counts	

代码效果图如图所示:

思路三:   当然我们也可以用一种简单的思路:要先定义一个函数,返回值是行坐标与列坐标的数位和,再定义一个boolean数组,记录每个格子是否被访问过,因为题目中的机器人是在(0,0)出发,所以只需要在行坐标+1,或列坐标+1的位置找就行,返回结果还要加上1,因为要算上(0,0)这个点。接下来我们用java将其实现:

public class Solution{
	public int movingCount(int threshold, int rows, int cols){
		boolean[] flag = new boolean[rows*cols];
		return movingCount(threshold, rows, cols, 0, 0, flag);
	}
	public int movingCount(int threshold, int rows, int cols, int i, int j, boolean[] flag){
		int index = i * cols + j;
		if(i<0 || i>=rows || j<0 || j>=cols || flag[index] || (sum(i)+sum(j))>threshold)
			return 0;
		flag[index] = true;
		return 1+movingCount(threshold,rows,cols,i+1,j,flag)+movingCount(threshold,rows,cols,i,j+1,flag);
	}
	public int sum(int num){
		if (num <= 0)
			return 0;
		int sum = 0;
		while(num != 0){
			sum += num % 10;
			num = num / 10;
		}
		return sum;
	}
}

代码效果图如图所示:

思路四:   最后给大家介绍一种对于在找前后左右相连的点很有用的算法:将地图全部置1,遍历能够到达的点,将遍历的点置0并令计数+1.我们用python将其实现:

class Solution:
    def __init__(self):
        self.count = 0
    def movingCount(self, threshold, rows, cols):
        # write code here
        arr = [[1 for i in range(cols)] for j in range(rows)]
        self.findway(arr, 0, 0, threshold)
        return self.count
    def findway(self, arr, i, j, k):
        if i < 0 or j < 0 or i >= len(arr) or j >= len(arr[0]):
            return
        tmpi = list(map(int, list(str(i))))
        tmpj = list(map(int, list(str(j))))
        if sum(tmpi) + sum(tmpj) > k or arr[i][j] != 1:
            return
        arr[i][j] = 0
        self.count += 1
        self.findway(arr, i + 1, j, k)
        self.findway(arr, i - 1, j, k)
        self.findway(arr, i, j + 1, k)
        self.findway(arr, i, j - 1, k)

代码效果图如图所示:

总结

  本题其实和上一题考察的差不多,均是通过不同的背景考察我们对数据结构中的深度优先遍历以及其回溯法的了解。本文在做题之前给大家介绍了图的两种遍历的相关内容,另外本文给出了四种解题思路。首先就是用到的回溯算法,并且分别用java和python两门编程语言将其实现。其次我们用到了图的广度优先遍历将其实现。最后就是给出了一种在找前后左右相连的点常用的方法。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

参考文献

[1] HxxxxxxxU [2] 今天敲代码了么 [3]sniperken [4] Yenpo_Ma [5] qq_27668313 [6] 图的遍历 - 数据结构