1.15 LeetCode总结(基本算法)_DFS之二维平面上的搜索问题

编程总结

每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧

79. 单词搜索

思路:DFS遍历下去,都是套路
1)递归终止条件:越界、之前访问过、字符不相等
2)循环退出条件:字符长度已经和输入字符长度相等,返回true
3)逻辑处理,注意回溯的变量处理,赋值后,需要再减回来,visit数组

给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
在这里插入图片描述

#define NUM_1001 1001
bool visited[NUM_1001][NUM_1001] = { 0 };
int  direction[4][2] = { {0,1},{0,-1},{1,0},{-1,0} };
bool searchWord(char **board, int boardSize, int *boardColSize, char *word, int index, int startx, int starty)
{
	bool result = 0;
	// 位置越界
	if ((startx < 0 || startx >= boardSize || starty < 0 || starty >= (*boardColSize))) {
		return false;
	}
	// 之前已经访问过
	if (visited[startx][starty] == 1) {
		return false;
	}
	// 判断字符相等
	if (board[startx][starty] != word[index]) {
		return false;
	}
	// 此时已经说明 [0, strlen(word)-1] 相等,因为 index 的范围就是 [0, strlen(word)-1]
	if (index + 1 == strlen(word)) {
		return true;
	}
	visited[startx][starty] = 1;
	for (int i = 0; i < 4; i++) {
		result = searchWord(board, boardSize, boardColSize, word, index + 1, startx + direction[i][0], starty + direction[i][1]);
		if (result == true) {
			return true;
		}
	}
	visited[startx][starty] = 0;
	return result;
}
bool exist(char **board, int boardSize, int *boardColSize, char *word)
{
	for (int v = 0; v < NUM_1001; v++) {
		memset(visited[v], 0, sizeof(visited[0]));
	}
	// 手法1:需要在任意位置去搜索遍历,起始点需要遍历,搜到即可.
	for (int i = 0; i < boardSize; i++) {   // 行
		for (int j = 0; j < *boardColSize; j++) { // 列
			if (searchWord(board, boardSize, boardColSize, word, 0, i, j)) {
				return true;
			}
		}
	}
	return false;
}

迷宫_基础

玩走迷宫的游戏.在一个n*m格子状的迷宫中,有一个入口S和多个出口E(一个迷宫中可能有多个出口,都用E表示).0表示这个格子可以走,1表示这个格子不可以走.小华希望可以最快的走出迷宫.每次只能走一步(在格子可以走的情况下,向上下左右移动一个格子), 他想让你计算最少的步数.
如:
1 0 1
0 S 1
0 0 E
输出2
如果无法到达出口输出 “No way”

思路:DFS遍历下去,都是套路
1)递归终止条件:越界、之前访问过、字符不相等
2)循环退出条件:字符长度已经和输入字符长度相等,返回true
3)逻辑处理,注意回溯的变量处理,赋值后,需要再减回来,visit数组

思路:通过DFS四个方向来做,gStep使用全局变量更新状态,step随递归深度进入到形参里
同时,加了gLen用于剪枝,gLen不为0时,同时超过之前存储的步数直接返回。

#define MAX_N   101
#define MAX_M   101
#define MAX_W   101
#define NUM_BIG 0x7FFFFFFF
char gMaze[MAX_N][MAX_M]  = {0}; // 二维矩阵,存储矩阵字符串
int  gVisit[MAX_N][MAX_M] = {0}; // 记录已经访问过的点,用int类型存储
int  gLen[MAX_N][MAX_M]   = {0}; // 记录访问过的点的步数
int  gResult = 0;
int  gStep = NUM_BIG;
int  gDirection[4][2] = {{1,0}, {-1,0}, {0,1}, {0,-1}};

void DFS(int x, int y, int step, int n, int m)
{
    // 如果已经满足条件,到达出口E,直接返回1,退出
    if (gMaze[x][y] == 'E') {
        gResult = step;
        gStep = gStep < gResult ? gStep : gResult; // 更新step值
        return ;
    }
    // 1.已经访问过该点,直接返回0,退出
    if (gVisit[x][y] == 1) {
        return ;
    }
    // 2.坐标越界,直接返回0,退出
    if (x < 0 || y < 0 || x >= n || y >= m) {
        return ;
    }
    // 3.格子不能走,直接返回0,退出
    if (gMaze[x][y] == '1') {
        return ;
    }
    // 4.剪枝,通过构造数组记录之前达到每一点需要的步数,如果超过可以步骤直接返回,不用再遍历
    if (gLen[x][y] && gLen[x][y] <= step) { // gLen不为空且step大于gLen
        return ;
    }
    // 存贮步数点
    gLen[x][y] = step;
    // 标记为已访问点
    gVisit[x][y] = 1;
    // 以下上右左的顺序进行遍历查找
    for (int i = 0; i < 4; i++) {
        DFS(x + gDirection[i][0], y + gDirection[i][1], step + 1, n, m);
    }
    // 如果都没有的话,将标记删除,返回0, 退出
    gVisit[x][y]  = 0;
}

490. 迷宫_进阶1(不是一步步走)

由空地(用 0 表示)和墙(用 1 表示)组成的迷宫 maze 中有一个球。球可以途经空地向 上、下、左、右 四个方向滚动,且在遇到墙壁前不会停止滚动。当球停下时,可以选择向下一个方向滚动。
给你一个大小为 m x n 的迷宫 maze ,以及球的初始位置 start 和目的地 destination ,其中 start = [startrow, startcol] 且 destination = [destinationrow, destinationcol] 。请你判断球能否在目的地停下:如果可以,返回 true ;否则,返回 false 。

在这里插入图片描述

思路:首先,这题不是一步步走,而是“不碰壁不停球”的走法,其他迷宫的题目是走一步,这里需要特殊处理一下,也没啥
手法1::寻找是否有一条路径,所以这里我们的DFS是有返回值的,能到到终点即返回true,然后层层退出来。
手法2:GetNext函数,通过一个方向的数值一直自增到越界为止来做碰壁的操作
手法3:IsNextWall函数,判断该点为能使球停下来的点(越界和墙)

#define DIRECT 4
#define MAX_N 128
int **matrix;
int m, n;
int visited[MAX_N][MAX_N];
int gDirection[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};

bool IsValid(int i, int j)
{
    if (i < 0 || i >= m || j < 0 || j >= n) {
        return false;
    }
    return true;
}
bool IsNextWall(int i, int j, int direct)
{
    int nextX, nextY;
    nextX = i + gDirection[direct][0];
    nextY = j + gDirection[direct][1];
    // 手法1:这里还是要先判断IsValid,防止matrix数组越界
    if (IsValid(nextX, nextY) == false) {
        return true; // 越界也能使皮球停下来
    }
    if (matrix[nextX][nextY] == 1) {
        return true;
    }
    return false;
}

void GetNext(int i, int j, int direct, int *posX, int *posY)
{
    int nextX, nextY;
    nextX = i + gDirection[direct][0];
    nextY = j + gDirection[direct][1];
    // 走到这个方向最远的地方
    while (IsValid(nextX, nextY) && matrix[nextX][nextY] == 0) {
        nextX = nextX + gDirection[direct][0];
        nextY = nextY + gDirection[direct][1];
    }
    *posX = nextX - gDirection[direct][0];
    *posY = nextY - gDirection[direct][1];
}

bool DFS(int i, int j, int endX, int endY, int direct)
{
    bool ret;
    int k;
    int nextX, nextY;
    // 到达终点了,并且在个点后面有墙壁
    if (i == endX && j == endY && IsNextWall(i, j, direct)) {
        return true;
    }
    if (i < 0 || i >= m || j < 0 || j >= n) { // 越界
        return false;
    }
    if (matrix[i][j] == 1) {   // 墙壁
        return false;
    }
    if (visited[i][j] == 1) {  // 已经访问过
        return false;
    }
    visited[i][j] = 1;         // 记录访问过的点
    for (k = 0; k < DIRECT; k++) {
        GetNext(i, j, k, &nextX, &nextY); // 走到这个方向最远的地方
        ret = DFS(nextX, nextY, endX, endY, k);
        if (ret) {
            return true;
        }
    }
	visited[i][j] = 1;         // 记录访问过的点
    return false;
}

505. 迷宫Ⅱ_进阶2(计算出最优路径)

由空地和墙组成的迷宫中有一个球。球可以向上下左右四个方向滚动,但在遇到墙壁前不会停止滚动。当球停下时,可以选择下一个方向。
给定球的起始位置,目的地和迷宫,找出让球停在目的地的最短距离。距离的定义是球从起始位置(不包括)到目的地(包括)经过的空地个数。如果球无法停在目的地,返回 -1。
迷宫由一个0和1的二维数组表示。 1表示墙壁,0表示空地。你可以假定迷宫的边缘都是墙壁。起始位置和目的地的坐标通过行号和列号给出。
在这里插入图片描述

手法1:此题和前一个题的区别在于这个题要求最短路径
手法2:此题为求最短路径,将所有的路径最小值都存了下来,最后返回终点值的step即可,需要更多地练习此题!

// DFS深度优先搜索, 每个点进行上下左右搜索
// 上下左右4个方向
#define  MAXVAL  INT_MAX
#define  MAXN  101        // 行
#define  MAXM  101        // 列
int dirs[4][2] = { {1, 0}, {0, 1}, {-1, 0}, {0, -1} };
int gDistance[MAXN][MAXM] = { 0 };
void DFS(int **maze, int mazeSize, int *mazeColSize, int *start)
{
	for (int i = 0; i < 4; i++) {   // 循环4次,得到4个相邻位置
		int nextX = start[0] + dirs[i][0]; // 挑选一个方向;Note:该步未进行count计数
		int nextY = start[1] + dirs[i][1];
		int step = 0;     // 记录当前的步数
		while (nextX >= 0 && nextX < mazeSize && nextY >= 0 && nextY < mazeColSize[0] && maze[nextX][nextY] == 0) {
			nextX += dirs[i][0]; // 继续相同方向
			nextY += dirs[i][1];
			step++;
		}
		if (gDistance[start[0]][start[1]] + step < gDistance[nextX - dirs[i][0]][nextY - dirs[i][1]]) { // note:此处为到达nextX和nextY的前一个点
			// 符合条件则更新step
			gDistance[nextX - dirs[i][0]][nextY - dirs[i][1]] = gDistance[start[0]][start[1]] + step;
			int subStart[2] = { nextX - dirs[i][0], nextY - dirs[i][1] }; // 记录当前位置
			DFS(maze, mazeSize, mazeColSize, start);
		}
	}
}

int shortestDistance(int **maze, int mazeSize, int *mazeColSize, int *start, int startSize, int *destination, int destinationSize) {
	for (int i = 0; i < MAXN; i++) {
		for (int j = 0; j < MAXM; j++) {
			gDistance[i][j] = MAXVAL; // 初始赋大值
		}
	}
	gDistance[start[0]][start[1]] = 0; // 起始点dis为0
	DFS(maze, mazeSize, mazeColSize, start);
	return gDistance[destination[0]][destination[1]] == MAXVAL ? -1 : gDistance[destination[0]][destination[1]];
}
int main()
{
    int i,ret,j,step = 0;
    int n,m;
    int gMaze[9][7] = {{0,0,0,0,1,0,0},{0,0,1,0,0,0,0},{0,0,0,0,0,0,0},{0,0,0,0,0,0,1},{0,1,0,0,0,0,0},
    {0,0,0,1,0,0,0},{0,0,0,0,0,0,0},{0,0,1,0,0,0,1},{0,0,0,0,1,0,0}};
    int **maze = (int **)malloc(sizeof(int *));
    for (i = 0; i < 9; i++) {
        maze[i] = (int *)malloc(sizeof(int) * 7);
        maze[i] = (int *)gMaze[i];
    }
    int mazeColSize = 7;
    int *start = (int *)malloc(sizeof(int) * 2);
    int startSize = 9;
    int *destination = (int *)malloc(sizeof(int) * 2);;
    int destinationSize = 7;
    start[0] = 0;
    start[1] = 0;
    destination[0] = 8;
    destination[1] = 6;
    ret = shortestDistance(maze, startSize, &mazeColSize, start, startSize, destination, destinationSize);

    return 0;
}

463. 岛屿的周长

在这里插入图片描述

思路:DFS,可以遍历,但怎么求边长呢?如下:
岛就一个,我们从第一个遇到的土地开始深搜。
对于每个土地节点,做一些事情,基于它,递归上下左右四个点,做同样的事情。做什么事情呢?
从土地到土地,之间不会产生周长,但从土地迈入海洋,之间会产生 1 个周长,从土地迈出矩阵边界,也会产生 1 个周长。
dfs 的过程中,对当前点的上下左右递归,下一个递归的点又对上下左右递归,就会造成重复访问,造成周长的重复计算。
遍历过的土地节点,将值改为 2,区分于 1 和 0,代表访问过了。下面画了个图来说明:
在这里插入图片描述
只看箭头处,由土地到海洋,增加红框一条边。同理,土地到边界也是增加一条边。

int direction[4][2] = { {1,0}, {-1,0}, {0,1}, {0,-1}};
int dfs(int x, int y, int **grid, int n, int m)
{
	if (x < 0 || x >= n || y < 0 || y >= m || grid[x][y] == 0) {
		return 1; // 由土地迈向海洋或者边界周长+1.
	}
	if (grid[x][y] == 2) { // 标记访问过
		return 0; // 土地迈向土地不加
	}
	grid[x][y] = 2;
	int res = 0;
	for (int i = 0; i < 4; ++i) {
		int nextX = x + direction[i][0];
		int nextY = y + direction[i][1];
		res += dfs(nextX, nextY, grid, n, m);
	}
	return res;
}

int islandPerimeter(int **grid, int gridSize, int *gridColSize)
{
	int n = gridSize;
	int m = gridColSize[0];
	int ans = 0;
	for (int i = 0; i < n; ++i) {
		for (int j = 0; j < m; ++j) {
			if (grid[i][j] == 1) {
				ans += dfs(i, j, grid, n, m);
			}
		}
	}
	return ans;
}

200. 岛屿数量

在这里插入图片描述
DFS思路:

int  direction[4][2] = { {1,0}, {-1,0}, {0,1}, {0,-1} };  // dfs的路径方向:上下右左

// DFS函数本身是不带返回值的,void 类型
void DFS(char **grid, int gridSize, int *gridColSize, int i, int j)
{
	int nextX = 0;
	int nextY = 0;

	if (i < 0 || i >= gridSize || j < 0 || j >= gridColSize[0]) {
		return;   // 边界防护
	}
	if (grid[i][j] != '1') {  // 访问过直接返回
		return;
	}
	// 未访问过,则访问,但先置位
	grid[i][j] = '2';
	// 作 4 个方向的dfs
	for (int k = 0; k < 4; k++) {
		nextX = i + direction[k][0];
		nextY = j + direction[k][1];
		DFS(grid, gridSize, gridColSize, nextX, nextY);
	}
}

int numIslands(char **grid, int gridSize, int *gridColSize)
{
	int count = 0;
	if (gridSize == 0) {
		return 0;
	}
	for (int i = 0; i < gridSize; i++) {
		for (int j = 0; j < gridColSize[0]; j++) {
			if (grid[i][j] == '1') {
				// 当从一个点入手进去后,真正再return回来时一定是将本岛屿遍历完了.
				DFS(grid, gridSize, gridColSize, i, j);
				// 上面是遍历一片一片的岛屿,遍历完一个则岛屿数量+1.
				count++;
			}
		}
	}

	return count;
}

BFS思路:

#define FLAG_NUM  2
typedef struct node {
	int x;
	int y;
} Node;

// BFS : 思路是线性扫描整个二维网络,如果一个节点包含1,则以其为根节点启动BFS.
// 并将其设为2(FLAG_NUM)以标记访问过该节点,迭代地搜索队列中的每一个节点,直到队列为空 -- 凡是能遍历到的都是一个岛上的.
// 继续此方法遍历二维网络其他点找其他岛,遍历完后就能找到所有“岛”的数量
void BFS(char **grid, int gridSize, int *gridColSize, int i, int j, int flag)
{
	Node *queue = (Node *)malloc((gridSize*(gridColSize[0]) + 1) * sizeof(Node));
	Node tmp;
	int step[4][2] = { {1,0},{-1,0},{0,1},{0,-1} }; // 下上右左
	int head = 0;
	int tail = 0;
	tmp.x = i;
	tmp.y = j;
	queue[tail++] = tmp;                // 首元素入队列

	// 循环迭代直到队列为空
	while (tail != head) {
		int x = queue[head].x;
		int y = queue[head++].y;        // 当前点,出队列
		for (int i = 0; i < 4; i++) {
			int xNext = x + step[i][0];
			int yNext = y + step[i][1]; // 遍历四个点
			// 有符合条件的元素则加入队列
			if (xNext >= 0 && xNext < gridSize && yNext >= 0 && yNext < gridColSize[0] && grid[xNext][yNext] == '1') {
				grid[xNext][yNext] = '0' + flag;
				tmp.x = xNext;
				tmp.y = yNext;
				queue[tail++] = tmp;   // 入队列
			}
		}
	}
}

int numIslands(char **grid, int gridSize, int *gridColSize)
{
	int flag = FLAG_NUM;
	int result = 0;

	for (int i = 0; i < gridSize; i++) {
		for (int j = 0; j < gridColSize[0]; j++) {
			if (grid[i][j] == '1') {
				BFS(grid, gridSize, gridColSize, i, j, flag);
				result++;
			}
		}
	}

	return result;
}

1020. 飞地的数量

在这里插入图片描述

与之前的岛屿题目区别在于,飞地要求不和边界相连,凡是相连的,都不算是岛屿。
手法就是去掉和边界相连通的点,剩下的点就是飞地!

int  direction[4][2] = { {1,0}, {-1,0}, {0,1}, {0,-1} };  // dfs的路径方向:上下右左

void DFS(int **grid, int m, int n, int **visited, int row, int col)
{
	// 达到边界或者已经来过了,则返回.
	if (row < 0 || row >= m || col < 0 || col >= n || grid[row][col] == 0 || visited[row][col] == 1) {
		return;
	}
	// 标记已访问.
	visited[row][col] = 1;
	for (int i = 0; i < 4; i++) {
		DFS(grid, m, n, visited, row + direction[i][0], col + direction[i][1]);
	}
}

int numEnclaves(int** grid, int gridSize, int* gridColSize)
{
	int m = gridSize;
	int n = gridColSize[0];
	int cnt = 0;
    int **visited = (int **)malloc(sizeof(int  *) * m);
	for (int i = 0; i < m; i++) {
		visited[i] = (int  *)malloc(sizeof(int) * n);
		memset(visited[i], 0, sizeof(int) * n);
	}
	// 1)遍历最左端核最右端的俩列边界 | | ,排除掉以列边界为起点的所有联通点
	for (int i = 0; i < m; i++) {
		DFS(grid, m, n, visited, i, 0);
		DFS(grid, m, n, visited, i, n - 1);
	}
	// 2)遍历最上端核最下端的俩行边界 二,排除掉以行边界为起点的所有联通点
	for (int j = 0; j < n; j++) {
		DFS(grid, m, n, visited, 0, j);
		DFS(grid, m, n, visited, m - 1, j);
	}
	// 3)遍历完边界后,剩余的部分都是飞地,直接统计即可
	for (int i = 1; i < m - 1; i++) {
		for (int j = 1; j < n - 1; j++) {
			if (grid[i][j] == 1 && visited[i][j] == 0) {
				cnt++;
			}
		}
	}
	// 4)free mem.
	for (int i = 0; i < m; i++) {
		free(visited[i]);
	}
	free(visited);
	return cnt;
}

int main()
{
	int grid[4][4] = { {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0} };
	int gridSize = 4;
	int gridColSize = 4;
	int *grid_a[4]; // 指针数组
	grid_a[0] = grid[0];
	grid_a[1] = grid[1];
	grid_a[2] = grid[2];
	grid_a[3] = grid[3];
	numEnclaves(&grid_a[0], gridSize, &gridColSize);

	return 0;
}

2684. 矩阵中移动的最大次数

在这里插入图片描述

int maxMoves(int **grid, int gridSize, int *gridColSize)
{
	g_res = 0;
	for (int i = 0; i < 1000; i++) {
		memset(g_visited[i], 0, sizeof(int) * 1000);
	}
	int res = 0;
	for (int i = 0; i < gridSize; i++) {
		res  = 0;
		dfs(i, 0, -1, &res, grid, gridSize, gridColSize); // 实际上就遍历列就行
	}
	return g_res - 1;
}
void dfs(int m, int n, int pre, int *res, int **grid, int gridSize, int *gridColSize)
{
	// 要素1:终止条件
	if ((m >= gridSize) || (m < 0) || (n >= *gridColSize) || (n < 0)) {
		g_res = (int)fmax(*res, g_res); // 更新结果
		return;
	}
	// 要素2:遍历记录的数组
	if (g_visited[m][n] == 1) {
		return;
	}
	// 要素3:DFS遍历
	if (grid[m][n] > pre) {
		(*res)++; // 递归每次加1
		g_visited[m][n] = 1;
		dfs(m - 1, n + 1, grid[m][n], res, grid, gridSize, gridColSize);
		dfs(m,     n + 1, grid[m][n], res, grid, gridSize, gridColSize);
		dfs(m + 1, n + 1, grid[m][n], res, grid, gridSize, gridColSize);
		(*res)--; // 回溯要与前面加平衡,减1
	} else {
		g_res = (int)fmax(*res, g_res); // 更新结果
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值