简单迷宫和进阶迷宫问题【百度和滴滴笔试题】


一、简单迷宫(百度笔试题)

1.1 题目

题目链接

1.2 思路

从入口开始不断的四个方向探索,直到走到出口,走的过程中我们借助记录走过路径的坐标,栈记录坐标有两方面的作用,一方面是记录走过的坐标,一方面方便走到死路时进行回溯找其他的通路(后进先出的特性,走到死路直接可以出掉该坐标)。

1.3 具体实现

1.3.1 建立迷宫

由于牛客的编译器是支持c语言的变长数组(c99特性),但是vs编译器只支持c98,不能使用变长数组,所以为了在两个平台都能运行,就需要用动态开辟空间来完成变长数组的功能。


二维数组如何该如何动态开辟呢?

动态二维数组不能直接malloc出来,需要通过指针数组来完成该功能,指针数组的元素都是指针类型的,在通过指针在链接到一个数组,就能完成动态二维数组的功能。
在这里插入图片描述
动态开辟二维数组代码:

int N, M;
scanf("%d %d", &N, &M);

int** maze = (int**)malloc(N * sizeof(int*));
for (int i = 0; i < N; i++)
{
	maze[i] = (int*)malloc(M * sizeof(int));
}

释放动态二维数组注意事项
有申请就要有释放,不让内存泄漏
同样释放不能直接释放maze,需要先把数组指针指向的空间先给释放,在释放maze

// 释放动态二维数组空间
for (int i = 0; i < M; i++)
{
	free(maze[i]);
}
free(maze);

由于需要存坐标(两个变量),所以我们可以创建一个结构体
// 定义坐标结构体
typedef struct Position
{
    int row;   
    int col;
}Pos;

1.3.2 探测路径递归函数

结束条件就是找到入口,每一个位置就往上下左右四个方向探测,其中就会碰到两个问题

  1. 走过路就不在递归进去了,不然会死循环,如何处理走过位置?

走过的路需要封住,每次递归都会往四个方向探测,如:递归下方坐标,该次递归还会探测上方坐标,如果不标识,他就会在递归回去,造成死循环。

在这里插入图片描述

  1. 碰到四个方向都是死路怎么办?

四个方向都走不通(越界和墙壁),就返回上次递归的位置,探测上次递归还有没有路可以走,所以碰到死路,就会返回,返回之前把死路坐标给出栈

在这里插入图片描述
递归过程:

  • 结束条件:找到出口

  • 单层递归逻辑:

    1. 当前位置入栈
    2. 判断是否在出口位置,在就返回不在递归,不在就继续递归
    3. 当前位置给封住,不让下次递归走到该位置
    4. 依次探测上下左右四个方向,其中某一个方向能走的通,就递归到位置,在去探测路径
    5. 遇到死路把当前位置在出栈,返回到上次递归的位置

  • 函数返回值:bool类型,找到出口就不在递归(该题到达出口通路只有一条) 不是void类型就会找到出口后还会探测其他方向


1.3.3 打印路径

现在还有一个问题是,大家都直到栈的特性后进先出,但是题目要求输出的坐标按顺序,可我们栈输出的坐标是反着的,额外在创建一个临时栈来翻转路径,在输出临时栈的路径。
在这里插入图片描述
在这里插入图片描述

1.4 完整代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

// 定义坐标结构体
typedef struct Position
{
    int row;   
    int col;
}Pos;

// 栈的数据类型改为坐标结构体
typedef Pos STDataType;

typedef struct Stack
{
    STDataType* a;	// 数组地址
    int top;		// 数据个数
    int capacity;	// 容量
}Stack;

void StackInit(Stack* ps);

bool StackEmpty(Stack* ps);

void StackPush(Stack* ps, STDataType x);

void StackPop(Stack* ps);

STDataType StackTop(Stack* ps);

int StackSize(Stack* ps);

void StackDestroy(Stack* ps);

// 初始化
void StackInit(Stack* ps)
{
    assert(ps);

    ps->a = NULL;
    ps->top = ps->capacity = 0;
}

// 栈为空判断
bool StackEmpty(Stack* ps)
{
    assert(ps);

    return ps->top == 0;
}

// 入栈
void StackPush(Stack* ps, STDataType x)
{
    assert(ps);

    // 栈容量检查
    if (ps->top == ps->capacity)
    {
        int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
        assert(tmp);
        ps->a = tmp;
        ps->capacity = newCapacity;
    }

    // 插入数据
    ps->a[ps->top] = x;
    ps->top++;
}

// 出栈
void StackPop(Stack* ps)
{
    assert(ps);
    // 空栈不能出
    assert(!StackEmpty(ps));

    ps->top--;
}

// 取栈顶元素
STDataType StackTop(Stack* ps)
{
    assert(ps);
    // 空栈不能取
    assert(!StackEmpty(ps));

    return ps->a[ps->top - 1];
}

// 栈的大小
int StackSize(Stack* ps)
{
    assert(ps);

    return ps->top;
}

// 销毁
void StackDestroy(Stack* ps)
{
    assert(ps);

    free(ps->a);
    ps->a = NULL;
    ps->top = ps->capacity = 0;
}

// ===============以下为关键代码==================

// 打印迷宫
void PrintMaze(int** maze, int N, int M)
{
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < M; j++)
        {
            printf("%d ", maze[i][j]);
        }
        printf("\n");
    }
}

// 是否能探测函数
bool IsPass(int** maze, int N, int M, Pos pos)
{
    if (pos.row >= 0 && pos.row < N &&
        pos.col >= 0 && pos.col < M &&
        maze[pos.row][pos.col] == 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

// 探测路径函数
bool GetMazePath(int** maze, int N, int M, Pos cur, Stack* path)
{
    StackPush(path, cur);

    // 找到出口条件
    if (cur.row == N - 1 && cur.col == M - 1)
        return true;

    // 探测的过程,自己走过的地方标识下
    maze[cur.row][cur.col] = 2;

    Pos next;

    // 上
    next = cur;
    next.row -= 1;
    if (IsPass(maze, N, M, next))
    {
        if (GetMazePath(maze, N, M, next, path))
            return true;
    }

    // 下
    next = cur;
    next.row += 1;
    if (IsPass(maze, N, M, next))
    {
        if (GetMazePath(maze, N, M, next, path))
            return true;
    }

    // 左
    next = cur;
    next.col -= 1;
    if (IsPass(maze, N, M, next))
    {
        if (GetMazePath(maze, N, M, next, path))
            return true;
    }

    // 右
    next = cur;
    next.col += 1;
    if (IsPass(maze, N, M, next))
    {
        if (GetMazePath(maze, N, M, next, path))
            return true;
    }

	// 碰到死路就出掉入进去的坐标
    StackPop(path);

    // 四个方向都不会通就返回
    return false;
}

// 打印路径
void PrintPath(int** maze, int N, int M, Stack* path)
{
	// 创建临时栈把数据倒过去
    Stack tmp;
    StackInit(&tmp);
    while (!StackEmpty(path))
    {
        StackPush(&tmp, StackTop(path));
        StackPop(path);
    }
	
	// 输出临时栈数据,就能实现题目输出要求
    while (!StackEmpty(&tmp))
    {
        STDataType top = StackTop(&tmp);
        printf("(%d,%d)\n", top.row, top.col);
        StackPop(&tmp);
    }

    StackDestroy(&tmp);
}

int main()
{
    int N;	// 行
    int M;	// 列 
    scanf("%d %d", &N, &M);
	
	// 创建迷宫
    int** maze = (int**)malloc(N * sizeof(int*));
    for (int i = 0; i < N; i++)
    {
        maze[i] = (int*)malloc(M * sizeof(int));
    }

	// 初始化迷宫
    for (int i = 0; i < N; i++)
        for (int j = 0; j < M; j++)
            scanf("%d", &maze[i][j]);

    // 开始位置坐标为0,0
    Pos entry = { 0, 0 };
    Stack path;
    StackInit(&path);
    if (GetMazePath(maze, N, M, entry, &path))
    {
        // 打印路径
        PrintPath(maze, N, M, &path);
    }
    
    StackDestroy(&path);

	// 释放动态二维数组空间
    for (int i = 0; i < N; i++)
    {
        free(maze[i]);
    }
    free(maze);
    
    return 0;
}

在这里插入图片描述


二、进阶迷宫(滴滴笔试题)

2.1 题目

题目链接

2.2 思路

相比简单迷宫,多出了体力值,走到出口,体力值为负数,就不能逃出迷宫,并且有多条路径能到达出口。

遇到出口不直接返回结束,而是看之前走过的路是否还有方向优先能到出口。

  1. 不能再用返回值,因为找到第一条路径就会返回
  2. 由于需要找到多条路径,找最短路径,所以一些位置会被多次走,所以递归结束时需要把这些位置解开。
  1. 额外创建一个栈minPath,用来放到达出口的最短路径
  2. 走到出口,体力值为负数,所以不能逃出。
  3. 比较当前路径和最短路径的栈,当前路径短,就把数据复制给最短路径,前提是,到达出口时,生命值不能为负数,注意要深拷贝,不能直接赋值(浅拷贝)。
    为什么不能直接赋值(浅拷贝)?
    • 内存泄漏,直接赋值最开始申请最短路径的栈空间就找不到了。

    • 直接赋值,导致两个栈的地址都是一样的,每次用来栈在变化的时候,存放最短路径的栈也会跟着变化

    • 销毁两次一块空间

  4. 判断最短路径是否为空,不为空就能逃出去,为空就逃出去。

2.3 栈复制函数(深拷贝)

  1. dest:复制目的地, src:数据源
  2. 把目的地的的空间改成和数据源一样大
  3. 通过内存拷贝函数(memcpy)把数据源的数据全部过来,数据个数为capacity,不能是top,不然内存泄漏,free的时候会出问题。
  4. 把数据源的大小和存了多少数据的信息同样拷贝过来
// 栈复制函数
void StackCopy(Stack* dest, Stack* src)
{
    dest->a = (STDataType*)malloc(src->capacity * sizeof(STDataType));
    memcpy(dest->a, src->a, src->capacity * sizeof(STDataType));
    dest->capacity = src->capacity;
    dest->top = src->top;
}

2.4 完整代码

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <string.h>


typedef struct Position
{
    int row;
    int col;
}Pos;

typedef Pos STDataType;

typedef struct Stack
{
    STDataType* a;	// 数组地址
    int top;		// 数据个数
    int capacity;	// 容量
}Stack;

void StackInit(Stack* ps);

bool StackEmpty(Stack* ps);

void StackPush(Stack* ps, STDataType x);

void StackPop(Stack* ps);

STDataType StackTop(Stack* ps);

int StackSize(Stack* ps);

void StackDestroy(Stack* ps);

// 初始化
void StackInit(Stack* ps)
{
    assert(ps);

    ps->a = NULL;
    ps->top = ps->capacity = 0;
}

// 栈为空判断
bool StackEmpty(Stack* ps)
{
    assert(ps);

    return ps->top == 0;
}

// 入栈
void StackPush(Stack* ps, STDataType x)
{
    assert(ps);

    // 栈容量检查
    if (ps->top == ps->capacity)
    {
        int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
        STDataType* tmp = (STDataType*)realloc(ps->a, newCapacity * sizeof(STDataType));
        assert(tmp);
        ps->a = tmp;
        ps->capacity = newCapacity;
    }

    // 插入数据
    ps->a[ps->top] = x;
    ps->top++;
}

// 出栈
void StackPop(Stack* ps)
{
    assert(ps);
    // 空栈不能出
    assert(!StackEmpty(ps));

    ps->top--;
}

// 取栈顶元素
STDataType StackTop(Stack* ps)
{
    assert(ps);
    // 空栈不能取
    assert(!StackEmpty(ps));

    return ps->a[ps->top - 1];
}

// 栈的大小
int StackSize(Stack* ps)
{
    assert(ps);

    return ps->top;
}

// 销毁
void StackDestroy(Stack* ps)
{
    assert(ps);

    free(ps->a);
    ps->a = NULL;
    ps->top = ps->capacity = 0;
}

// =============== 以下为迷宫关键代码 ==================

// 打印迷宫
void PrintMaze(int** maze, int N, int M)
{
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < M; j++)
        {
            printf("%d ", maze[i][j]);
        }
        printf("\n");
    }
}

// 是否能探测函数
bool IsPass(int** maze, int N, int M, Pos pos)
{
    if (pos.row >= 0 && pos.row < N &&
        pos.col >= 0 && pos.col < M &&
        maze[pos.row][pos.col] == 1)
    {
        return true;
    }
    else
    {
        return false;
    }
}

// 栈复制函数
void StackCopy(Stack* dest, Stack* src)
{
    dest->a = (STDataType*)malloc(src->capacity * sizeof(STDataType));
    memcpy(dest->a, src->a, src->capacity * sizeof(STDataType));
    dest->capacity = src->capacity;
    dest->top = src->top;
}

// 探测递归函数
void GetMazePath(int** maze, int N, int M, Pos cur, Stack* path, Stack* minPath, int HP)
{
    StackPush(path, cur);

    // 找到出口条件
    if (cur.row == 0 && cur.col == M - 1)
    {
        // minPath栈为空,minPath直接设为第一次到达路径
        if (HP >= 0 && StackEmpty(minPath) ||
            StackSize(path) < StackSize(minPath))
        {
        	StackDestroy(minPath);
            StackCopy(minPath, path);
        }
    }
        
    // 探测的过程,自己走过的地方标识下
    maze[cur.row][cur.col] = 2;

    Pos next;

    // 上
    next = cur;
    next.row -= 1;
    if (IsPass(maze, N, M, next))
    {
        GetMazePath(maze, N, M, next, path, minPath, HP - 3);
    }

    // 下
    next = cur;
    next.row += 1;
    if (IsPass(maze, N, M, next))
    {
        GetMazePath(maze, N, M, next, path, minPath, HP);
    }

    // 左
    next = cur;
    next.col -= 1;
    if (IsPass(maze, N, M, next))
    {
        GetMazePath(maze, N, M, next, path, minPath, HP - 1);
    }

    // 右
    next = cur;
    next.col += 1;
    if (IsPass(maze, N, M, next))
    {
        GetMazePath(maze, N, M, next, path, minPath, HP - 1);
    }

    // 把当前位置解开,为了让下一条路径可以走
    maze[cur.row][cur.col] = 1;

    StackPop(path);
}

// 打印入口探测到出口的路线
void PrintPath(int** maze, int N, int M, Stack* path)
{
    Stack tmp;
    StackInit(&tmp);
    while (!StackEmpty(path))
    {
        StackPush(&tmp, StackTop(path));
        StackPop(path);
    }

    while (!StackEmpty(&tmp))
    {
        STDataType top = StackTop(&tmp);
        printf("[%d,%d]", top.row, top.col);
        if (StackSize(&tmp) > 1)
            printf(",");
        StackPop(&tmp);
    }

    StackDestroy(&tmp);
}

int main()
{
    int N, M, HP;
    scanf("%d %d %d", &N, &M, &HP);

    int** maze = (int**)malloc(N * sizeof(int*));
    for (int i = 0; i < N; i++)
    {
        maze[i] = (int*)malloc(M * sizeof(int));
    }

    for (int i = 0; i < N; i++)
        for (int j = 0; j < M; j++)
            scanf("%d", &maze[i][j]);

    // 开始位置坐标为0,0
    Pos entry = { 0, 0 };

    // 额外一个栈来存最短路径
    Stack path, minPath;   
    StackInit(&path);
    StackInit(&minPath);

    GetMazePath(maze, N, M, entry, &path, &minPath, HP);

    if (!StackEmpty(&minPath))
    {
        PrintPath(maze, N, M, &minPath);
    }
    else
    {
        printf("Can not escape!");
    }

    StackDestroy(&path);
    StackDestroy(&minPath);

    return 0;
}

在这里插入图片描述



总结

简单迷宫和进阶迷宫,核心重点还是在于递归方面,递归的探测和遇到死路回溯,而进阶迷宫特点就是最短路径,并且需要多加理解深拷贝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值