编程总结
每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧
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); // 更新结果
}
}