Day65:矩阵中的路径

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

剑指Offer_编程题——矩阵中的路径

题目描述:

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串”bcced”的路径,但是矩阵中不包含”abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

具体要求:

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

具体实现:

背景知识介绍:   在做题之前,首先给大家详细介绍回溯法。回溯法是数据结构五大算法中较为常用的一种,在维基百科中,回溯法是暴力搜索法中的一种。对于某些计算问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,尤其适用于约束满足问题(在解决约束满足问题时,我们逐步构造更多的候选解,并且在确定某一部分候选解不可能补全成正确解之后放弃继续搜索这个部分候选解本身及其可以拓展出的子候选解,转而测试其他的部分候选解)。在经典的数据结构的教科书中,八皇后问题展示了回溯法的用例。(八皇后问题是在标准国际象棋棋盘中寻找八个皇后的所有分布,使得没有一个皇后能攻击到另外一个。)回溯法采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的递归方法来实现,在反复重复上述的步骤后可能出现两种情况: 1、找到一个可能存在的正确的答案 2、在尝试了所有可能的分步方法后宣告该问题没有答案   在最坏的情况下,回溯法会导致一次复杂度为指数时间的计算。回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。它的基本思想类似于图的深度优先搜索(dfs),也类似于二叉树的后序遍历。   回溯法按深度优先策略搜索问题的解空间树。首先从根节点出发搜索解空间树,当算法搜索至解空间树的某一节点时,先利用剪枝函数判断该节点是否可行(即能得到问题的解)。如果不可行,则跳过对该节点为根的子树的搜索,逐层向其祖先节点回溯;否则,进入该子树,继续按深度优先策略搜索。 回溯法的基本行为是搜索,搜索过程使用剪枝函数来为了避免无效的搜索。剪枝函数包括两类:1. 使用约束函数,剪去不满足约束条件的路径;2.使用限界函数,剪去不能得到最优解的路径。问题的关键在于如何定义问题的解空间,转化成树(即解空间树)。解空间树分为两种:子集树和排列树。两种在算法结构和思路上大体相同。   当问题是要求满足某种性质(约束条件)的所有解或最优解时,往往使用回溯法。因此它也有“通用解题法”之美誉。通常情况下: 回溯法的实现方法有两种:递归和递推(也称迭代)。一般来说,一个问题两种方法都可以实现,只是在算法效率和设计复杂度上有区别。常见的经典问题有:装载问题、0-1背包问题、旅行售货员问题、八皇后问题、迷宫问题以及图的m着色问题等经典问题。 思路一:   通过我们对回溯法的介绍,可以很清晰的指导本题很明显是用回溯法。具体的过程如下: 1、首先,在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。 2、由于回朔法的递归特性,路径可以被开成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。 3、 由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入每个格子。 当矩阵中坐标为(row,col)的格子和路径字符串中相应的字符一样时,从4个相邻的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下一个字符 4、如果4个相邻的格子都没有匹配字符串中下一个的字符,表明当前路径字符串中字符在矩阵中的定位不正确,我们需要回到前一个,然后重新定位。 5、 一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到合适的位置   具体我们分别用java和python两种语言 将其实现。 1、首先用java将其实现:

public class Solution{
	public boolean hasPath(char[] matrix, int rows, int cols, char[] str){
		if(matrix == null || rows <=0 || cols <= 0 || str == null)
			return false;
		if(str.length == 0)
			return true;
		boolean[] visited = new boolean[matrix.length];
		for(int i = 0; i < rows; i++){
			for(int j = 0; j < cols; j++){
				if(findPath(matrix, i, j, rows, cols, 0, visited, str)){
					return true;
				}
			}
		}
		return false;
	}
	public boolean findPath(char[] matrix, int row, int col, int rows, int cols, int k, boolean[] visited, char[] str){
		 if(row<0 || row>=rows || col<0 || col>=cols || str[k]!=matrix[row*cols+col] || visited[row*cols+col])
            return false;
         if(k == str.length - 1)
         	return true;
         visited[row*cols+col] = true;
         if(findPath(matrix, row+1, col, rows, cols, k+1, visited, str) || findPath(matrix, row, col+1, rows, cols, k+1, visited, str) || findPath(matrix, row-1, col, rows, cols, k+1, visited, str) || findPath(matrix, row, col-1, rows, cols, k+1, visited, str)){
         	return true;
         }
         visited[row*cols+col] = false;
         return false;
	}
}

代码效果图如图所示:

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

class Solution:
	def dfs(self, matrix, flag, rows, cols, r, c, s):
		if s == '':
			return True
		dx = [-1, 1, 0, 0]
		dy = [0, 0, -1, 1]
		for k in range(4):
			x = dx[k] + r
			y = dy[k] + c
			if x >= 0 and x < rows and y >= 0 and y < cols and flag[x][y] and matrix[x*cols+y]==s[0]:
				flag[x][y] = False
				if self.dfs(matrix, flag[:], rows, cols, x, y, s[1:]):
					return True
				flag[x][y] = True
		return False
	def hasPath(self, matrix, rows, cols, path):
		if path == '':
			return True
		flag = [[True for c in range(cols)] for r in range(rows)]
		for r in range(rows):
			for c in range(cols):
				if matrix[r * cols + c] == path[0]:
					flag[r][c] = False
					if self.dfs(matrix,flag[:],rows,cols, r, c,path[1:]):
					 	return True
					flag[r][c] = True
		return False

代码效果图如图所示:

思路二:   我们可以使用一个栈和一个数组作辅助来解决该题: 1、遍历整个字符串数组,将和str首个字符匹配的字符下标push到栈里(可能有多个) 2、出栈,将出栈的下标的元素标位已访问,查找其周围有无str的下个字符,若有,都入栈; 3、直到找完所有str的字符,返回true; 4、或者str没找完,栈已空,返回fasle。   具体我们可以用java将其实现:

import java.util.Stack;
public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix==null||str == null||matrix.length==0) return false;
        if(str.length == 0) return true;
        boolean result = false;
        Stack<Integer> stack = new Stack<Integer>();
        boolean[] visited = new boolean[rows*cols];
        for (int i = 0; i < matrix.length; i++) {
            if(matrix[i]==str[0]){
                stack.push(i);
                result = true;
            }
        }
        for(int j = 1;!stack.empty()&&j<str.length;){
            result = false;
            int index = stack.pop();
            visited[index] = true;
            if(hasNext(matrix, rows, cols, index, stack, str[j], visited)){
                if(++j == str.length) result = true;
            }
        }
        return result;

    }
    public boolean hasNext(char[] matrix,int rows,int cols,int index,Stack<Integer> stack,char c,boolean[] visited){
        int up = index-cols;
        int down = index+cols;
        int left = index-1;
        int right = index+1;
        boolean hasNext = false;
        if(up>=0&&matrix[up] == c&&!visited[up]) {
            stack.push(up);
            hasNext = true;
        }
        if(down<matrix.length&&matrix[down] == c&&!visited[down]) {
            stack.push(down);
            hasNext = true;
        }
        if(index%cols != 0&&matrix[left] == c&&!visited[left]) {
            stack.push(left);
            hasNext = true;
        }
        if(right%cols != 0&&matrix[right] == c&&!visited[right]) {
            stack.push(right);
            hasNext = true;
        }
        return hasNext;
    }
}

代码效果图如图所示:

思路三:   这是一个走路径的问题,需要判断这个矩阵中的每一个结点是否可以走一条路径,在走的过程中,设置一个和矩阵大小相同的整型数组表示是否已经访问,如果某个结点访问了,那么该结点的是否访问则为1。每次遍历一个结点的时候,我们可以用递归的方式分别向左、向右、向上、向下。接下来我们分别用java和python将其实现。 1、首先让我们用java将其实现:

public class Solution{
	public boolean hasPath(char[] matrix, int rows, int cols, char[] str){
		int[] flag = new int[matrix.length];
		for(int i = 0; i < rows; i++){
			for(int j = 0; j < cols; j++){
				if(helper(matrix, rows, cols, i, j, str, 0, flag))
					return true;
			}
		}
		return false;
	}
	public static boolean helper(char[] matrix, int rows, int cols, int i, int j, char[] str, int k, int[] flag){
		int index = i * cols + j;
		if(i < 0 || i >= rows || j < 0 || j >= cols || matrix[index] != str[k] || flag[index] == 1)
			return false;
		if(k == str.length - 1)
			return true;
		flag[index] = 1;
		if(helper(matrix, rows, cols, i - 1, j, str, k + 1, flag)
          ||helper(matrix, rows, cols, i + 1, j, str, k + 1, flag)
          ||helper(matrix, rows, cols, i, j - 1, str, k + 1, flag)
          ||helper(matrix, rows, cols, i , j + 1, str, k + 1, flag)){
            return true;
        }
        flag[index] = 0;
        return false;
	}
}

代码效果图如图所示:

2、接下来用python实现:

class Solution:
	def hasPath(self, matrix, rows, cols, path):
		assistMatrix = [True] * rows * cols
		for i in range(rows):
			for j in range(cols):
				if(self.hasPathAtAStartPoint(matrix, rows, cols, i, j, path, assistMatrix)):
					return True
		return False
	def hasPathAtAStartPoint(self, matrix, rows, cols, i, j, path, assistMatrix):
		if not path:
			return True
		index = i * cols + j
		if i<0 or i>=rows or j<0 or j>=cols or matrix[index]!=path[0] or assistMatrix[index]==False:
			return False
		assistMatrix[index] = False
		if(self.hasPathAtAStartPoint(matrix,rows,cols,i+1,j,path[1:],assistMatrix) or
               self.hasPathAtAStartPoint(matrix,rows,cols,i-1,j,path[1:],assistMatrix) or
               self.hasPathAtAStartPoint(matrix,rows,cols,i,j-1,path[1:],assistMatrix) or
               self.hasPathAtAStartPoint(matrix,rows,cols,i,j+1,path[1:],assistMatrix)):
			return True
		assistMatrix[index] = True
		return False

代码效果图如图所示:

思路四:   当然,我们可以根据思路三,也可以利用递归的思想可以解决问题,总体来说,就是超左右前后四个方向的深度优先搜索的问题。接下来我们用python将其实现:

# -*- coding:utf-8 -*-
class Solution:
    def hasPath(self, matrix, rows, cols, path):
        data = []
        start = []
        for r in range(rows):
            data.append(matrix[r*cols:(r+1)*cols])
            for c in range(cols):
                data[-1][c] == path[0]
                start.append([r, c])
        res_set = []
        for ptr in start:
            visited = []
            res_set.append(self.dfs(data, path, ptr, visited))
        for res in res_set:
            if res:
                return True
        return False
        
    def dfs(self, data, path, ptr, visited):
        if len(path) == 0:
            return True
        next_node = []
        if ptr[0] == 0:
            next_node.append([ptr[0]+1, ptr[1]])
        elif ptr[0] == len(data) - 1:
            next_node.append([ptr[0]-1, ptr[1]])
        else:
            next_node.append([ptr[0]-1, ptr[1]])
            next_node.append([ptr[0]+1, ptr[1]])
        if ptr[1] == 0:
            next_node.append([ptr[0], ptr[1]+1])
        elif ptr[1] == len(data[0]) - 1:
            next_node.append([ptr[0], ptr[1]-1])
        else:
            next_node.append([ptr[0], ptr[1]-1])
            next_node.append([ptr[0], ptr[1]+1])
        if ptr not in visited and data[ptr[0]][ptr[1]] == path[0]:
            visited.append(ptr)
            res_set = []
            for next_ptr in next_node:
                res_set.append(self.dfs(data, path[1:], next_ptr, visited))
            for res in res_set:
                if res:
                    return True
        return False

代码效果图如图所示:

总结

  本题主要通过矩阵中的路径的来考察我们对深度优先遍历以及回溯算法的掌握和理解。本文在做题之前给大家介绍了回溯法的相关内容,另外本文给出了四种解题思路。首先就是用到的回溯算法,并且分别用java和python两门编程语言将其实现。其次我们用到了一个辅助栈来将期实现。最后就是通过递归的方式分别向左、向右、向上、向下遍历实现。因此,我们在做题的时候,应该多次尝试各种方法,扩展自己的思维,写出优质的代码。总之,我们要继续加油,争取早日找到工作,Good Luck!!!

参考文献

[1] ouyangyanlan [2] 乖乖的函数 [3] 疯狂1024 [4] jiangjiane [5] v_nlp [6] [回溯算法] 五大常用算法之回溯法 [7] 回溯法