轰击者 pat basic 练习五十 螺旋矩阵 碰壁法详解 绝对能讲明白

心得:

1.二维数组传参时,形参需要显式指定第二位的数量,即实参还是传数组名test(arr),形参是void test(int (*p)[4])较麻烦,所以应该避免二维数组传参

2.向上取整和向下取整的函数分别是ceil()floor()

3.记住处理的螺旋矩阵的方法,碰壁法

 

题目:

本题要求将给定的 N 个正整数按非递增的顺序,填入“螺旋矩阵”。所谓“螺旋矩阵”,是指从左上角第 1 个格子开始,按顺时针螺旋方向填充。要求矩阵的规模为 m 行 n 列,满足条件:m×n 等于 N;m≥n;且 m−n 取所有可能值中的最小值。

输入格式:

输入在第 1 行中给出一个正整数 N,第 2 行给出 N 个待填充的正整数。所有数字不超过 10​4​​,相邻数字以空格分隔。

输出格式:

输出螺旋矩阵。每行 n 个数字,共 m 行。相邻数字以 1 个空格分隔,行末不得有多余空格。

输入样例:

12
37 76 20 98 76 42 53 95 60 81 58 93

 

输出样例:

98 95 93
42 37 81
53 20 76
58 60 76

 

 

 

思路:

1.先将这些数字排序,以便后面填数时用

2.m,n的求法应该不是问题,题目的意思是m*n=N,且差值要最小,最符合此条件的莫过于sqrt(N)了,但是它可能不是整数,所以可以从sqrt(N)向上取整的数开始找m,如果找到期间第一个可以被N整除,那么它就是最符合条件的m,n也就能用N/m得出

3.然后开始填数,这里我使用的是碰壁法

何为碰壁法,就像走螺型迷宫,对着一个方向一直走,当遇到墙了,就转向,

这一题要求的是顺时针走,那么走的顺序就是右->下->左->上->右的循环,碰壁法好处就是不管矩阵的行数和列数是多少,只在乎是否碰墙,碰墙了才转向

 

在代码中的碰墙如何实现?就是给移动设置限制,

(1)如一直向右走,当下一步的坐标已经超过了列数,就认为是碰墙;

(2)定义一个bool数组,他的作用是查看一个格子是否已经走过,在某次走动中,发现下一个格子已经走过了,也认为碰墙了

 

至此,思路就是这样,代码中还会有注释说明,代码可能看着长一点,但其实很好懂

 

代码:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
void get_rc(int num, int &row, int &column)//求row(就是m),column(就是n)的个数
{
    for(int i=ceil(sqrt(num));i<=num;i++)
    {
        if(num%i==0)
        {
            row=i;
            column=num/row;
            break;
        }
    }
}
int main()
{
    int num,row,column;
    cin>>num;
    int a[num];
    for(int i=0;i<num;i++)
        cin>>a[i];
    sort(a,a+num,greater<int>());//给数组排序方便填数
    get_rc(num,row,column);
    int res[row][column]={0};//结果数组
    bool is_visit[row][column]={false};//意思为某格是否已经走过
    int x=0,y=-1,ori=0;//x,y为坐标,起点在(0,-1)这个位置上,ori是面朝向的意思,就是下一次走动的方向
    for(int i=0;i<num;i++)//规定0为右,1为下,2为左,3为上
//且一定是按照0123的顺序走动,否则是构不成螺旋的,可以想想为什么,虽然也没什么好想的。。
    {
        if(ori==0)
        {//面朝右时
            if(y+1<column&&!is_visit[x][y+1])//一直走下去,当下一步没有超出右边界并且下一步的格子没有走过
            {//就移动并填数,这个格子设置为访问过
                res[x][++y]=a[i];
                is_visit[x][y]=true;
            }
            else
            {//如果下一步超出了右边界或者下一步的格子已经走过说明已经碰墙了
                ori=1;//就立即转向,0转成1
                res[++x][y]=a[i];//然后走动并填数
                is_visit[x][y]=true;//此格子设置为访问过
            }//为什么碰墙的时候一定可以转向并走动?不怕走动是非法的吗?
     //一.还是那句话,走动顺序严格按照0123,否则构不成螺旋,如果格子没有全部走完,当一个方向没路时,下一个方向一定是有路的
        }//二.当走到最后一格时,格子都走完了,下一步确实没路了,但此时已经退出了for循环,不用担心非法走动
        else if(ori==1)
        {//其他的方向一样的道理
            if(x+1<row&&!is_visit[x+1][y])
            {
                res[++x][y]=a[i];
                is_visit[x][y]=true;
            }
            else
            {
                ori=2;
                res[x][--y]=a[i];
                is_visit[x][y]=true;
            }
        }
        else if(ori==2)
        {
            if(y-1>=0&&!is_visit[x][y-1])
            {
                res[x][--y]=a[i];
                is_visit[x][y]=true;
            }
            else
            {
                ori=3;
                res[--x][y]=a[i];
                is_visit[x][y]=true;
            }
        }
        else if(ori==3)
        {
            if(x-1>=0&&!is_visit[x-1][y])
            {
                res[--x][y]=a[i];
                is_visit[x][y]=true;
            }
            else
            {
                ori=0;
                res[x][++y]=a[i];
                is_visit[x][y]=true;
            }
        }
    }
    for(int i=0;i<row;i++)
    {//输出图形
        for(int j=0;j<column;j++)
        {
            if(j!=column-1)
                cout<<res[i][j]<<" ";
            else
                cout<<res[i][j]<<endl;
        }
    }
    return 0;
}

填数时就一个for循环,所以也不用担心超时 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值