3种方法:计算机器人的运动范围

文章目录

    • 题目
    • 题解
    • 解法一(深度优先搜索-递归)
    • 解法二(深度优先搜索-栈循环)
    • 解法三(广度优先搜索-队列循环)


题目

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

示例 1:

输入:m = 2, n = 3, k = 1
输出:3

示例 1:

输入:m = 3, n = 1, k = 0
输出:1

提示:

1 <= n,m <= 100
0 <= k <= 20


题解

看题目第一反应,只需要计算横纵坐标和,将和不大于k的值做累加,但其实有个隐藏的坑,就是需要跟原点坐标(0,0)是连通状态。

因此,需要从原点坐标分别向四个方向递归来查看,实际上,也有一个隐藏的优化点,就是其实只需要向XY递增的方向,而不需要向反方向运动:

  1. 从原点起步就是先递增方向运动
  2. 一旦到达大于k的临界点,相当于已经撞墙了,无法继续前进了,而原始XY两个递增的方向会覆盖其他可以前进的路线。

解法一(深度优先搜索-递归)

思路:直接向四个方向递归遍历,使用visit数组来保存访问记录,防止重复递归。

  1. 动态申请一个m*n的二维数组,等于1表示已经访问过
  2. 递归中如果到达数组边界、访问过或不满足k的条件,则不进入递归计数
  3. 分别向x、y的正方方向(4个方向)进行递归求和
  • 时间复杂度:O(mn)
  • 空间复杂度:O(mn)
//author: [email protected]
int checkCnt(int** v, int r,int c,int mr,int mc,int k)
{//递归查看
    if(!(r < mr && r >= 0 && c < mc && c >= 0) || v[r][c] == 1 || (r%10+r/10+r/100+c/100+c/10+c%10>k))
        return 0;
    v[r][c] = 1;
    return 1+checkCnt(v,r+1,c,mr,mc,k) + checkCnt(v,r-1,c,mr,mc,k) +
    checkCnt(v,r,c+1,mr,mc,k) + checkCnt(v,r,c-1,mr,mc,k);
}

int movingCount(int m, int n, int k){
    int **visit = (int **)malloc(sizeof(int *) * m);
	for (int i = 0; i < m; i++){//动态申请二维数组
		visit[i] = (int *)malloc(sizeof(int) * n);
	}
    return checkCnt(visit,0,0,m,n,k);//返回递归结果
}

解法二(深度优先搜索-栈循环)

思路:使用栈来作为循环的载体代替上面算法的递归,使用列表实现栈省去申请一个3维数组。

  1. 定义一个结构体作为链表节点,先对原点(0,0)入栈,并在计数(cnt=1)后标记已访问
  2. 定义四个方向迭代dx,dy,用于分别向四个方向递增(基于题解可以优化为两个)
  3. 每次入栈都是新申请结构体,并放在链表头作为入栈
  • 时间复杂度:O(mn)
  • 空间复杂度:O(mn)
//author: [email protected]
typedef struct Node{
    int x;
    int y;
    struct Node * next;
}Stack;//定义链表实现栈,结构体存储xy坐标
int movingCount(int m, int n, int k){
    int **visit = (int **)malloc(sizeof(int *) * m);
	for (int i = 0; i < m; ++i){//动态申请二维数组
		visit[i] = (int *)malloc(sizeof(int) * n);
	}
    visit[0][0] = 1;
    int dx[4] = {1,0,-1,0};//可以只用{1,0}
    int dy[4] = {0,1,0,-1};//可以只用{0,1}
    Stack* node = (Stack*)malloc(sizeof(Stack));
    node->x = 0;
    node->y = 0;
    node->next = NULL;
    int tx,ty,cnt=1;
    visit[0][0] = 1;//原点默认已经访问
    while(node != NULL){
        for(int i=0;i<4;i++){//4个方向
            tx = node->x + dx[i];
            ty = node->y + dy[i];
            if(!(tx < m && tx >= 0 && ty < n && ty >= 0) ||
                (visit[tx][ty] == 1) ||
                (tx%10+tx/10+tx/100+ty/100+ty/10+ty%10 > k))
                continue;
            visit[tx][ty] = 1;
            Stack* newst = (Stack*)malloc(sizeof(Stack));//新节点申请空间
            newst->x = tx;
            newst->y = ty;
            newst->next = NULL;
            newst->next = node->next;//指针后移
            node->next = newst;//链表头为最新数据,即实现压栈操作
        }
        Stack* tmpNode = node;
        node=node->next;//踢出队尾元素
        free(tmpNode);//释放被踢出元素的空间
        cnt++;//出栈时候累加计数
    }

    return cnt-1;
}

解法三(广度优先搜索-队列循环)

思路:基于解法二,将栈改为队列,使得算法变为广度优先搜索。代码仅有34、35两行不同,代表压栈/入队,其它模块完全一样,即实现了从深度向广度的调整,可见两种算法总是一起拿出来也是有其相似之处。

  1. 压栈操作是将新节点放在链表头,而队列的入队操作是将新节点放在链表尾
  2. 出栈和出队一样,都是访问链表头
  3. 使用一个指针始终指向最后一个链表(队头),省去每次的链表查找
  • 时间复杂度:O(mn)
  • 空间复杂度:O(mn)
//author: [email protected]
typedef struct Node{
    int x;
    int y;
    struct Node * next;
}Stack;//定义链表实现队列,结构体存储xy坐标
int movingCount(int m, int n, int k){
    int **visit = (int **)malloc(sizeof(int *) * m);
	for (int i = 0; i < m; ++i){//动态申请二维数组
		visit[i] = (int *)malloc(sizeof(int) * n);
	}
    visit[0][0] = 1;
    int dx[4] = {1,0,-1,0};//可以只用{1,0}
    int dy[4] = {0,1,0,-1};//可以只用{0,1}
    Stack* node = (Stack*)malloc(sizeof(Stack)), *tailNode;
    node->x = 0;
    node->y = 0;
    node->next = NULL;
    tailNode = node;
    int tx,ty,cnt=1;
    visit[0][0] = 1;//原点默认已经访问
    while(node != NULL){
        for(int i=0;i<4;i++){//4个方向
            tx = node->x + dx[i];
            ty = node->y + dy[i];
            if(!(tx < m && tx >= 0 && ty < n && ty >= 0) ||
                (visit[tx][ty] == 1) ||
                (tx%10+tx/10+tx/100+ty/100+ty/10+ty%10 > k))
                continue;
            visit[tx][ty] = 1;
            Stack* newst = (Stack*)malloc(sizeof(Stack));//新节点申请空间
            newst->x = tx;
            newst->y = ty;
            newst->next = NULL;
            tailNode->next = newst;//链表尾为最新数据,即实现入队操作
            tailNode = tailNode->next;//指针后移
        }
        Stack* tmpNode = node;
        node=node->next;//踢出队尾元素
        free(tmpNode);//释放被踢出元素的空间
        cnt++;//出栈时候累加计数
    }

    return cnt-1;
}

你可能感兴趣的:(算法,个人杂谈)