两道铺瓷砖的题,是比较绕但是算法偏简单的题目(自我回顾)
H1: 你是一名室内装潢工程队的配料员。你的伙伴们喜欢采用“之”字型的方式铺大理石地砖
H2:你是一名室内装潢工程队的配料员。你的伙伴们喜欢“回”字型铺瓷砖
例:
H1:
算法上:首先应该要清楚,到达边界之后朝哪个方向移动,就比如第一幅图中 6 的位置,就需要右移一格, 15 的位置就要向下移一格。
然后就交给循环打表即可;
实现如下:
(1)如果斜向左下到第一列但是没有到达最后一行,那就是向下移动,否则向右。如果斜向右上到第一行但是没有到达最后一列,那就是向右移动,否则向下。
(2)循环终止条件:右下角的数字还是零(这里初始化整个数值为0)。
具体算法的代码主体如下:
关于回调数值的必要性和存在的意义,读者可以通过调试加深理解。
int a[12][12]={0};
int n=0;
scanf("%d",&n);
int i=0;
int j=0;
int k=1; // 填入的数字
a[i][j]=k; // 第一个数值填入左上角
while(a[n-1][n-1]==0)
{
if(i==0&&i<n-1&&j<n-1) // 斜向右上到第一行但是还没到最后一列
{
j+=1; // 右移
while(j>=0&&i<n)
{
a[i][j]=++k; // 向左下移动
i++;
j--;
}
j++; // 最后一次移动越界,于是要回调数值
i--;
}
else if(j==0&&i<n-1&&j<n-1) // 斜向左下到第一列但是还没到最后一行
{
i+=1; // 下移
while(i>=0&&j<n)
{
a[i][j]=++k;
i--;
j++;
}
i++;
j--;
}
else if(i==n-1) // 到最后一行
{
j+=1; // 右移
while(j<n)
{
a[i][j]=++k;
i--;
j++;
}
i++;
j--;
}
else if(j==n-1) // 到最后一列
{
i+=1; // 左移
while(i<n)
{
a[i][j]=++k;
i++;
j--;
}
i--;
j++;
}
else if((i==n-1&&j==n-2)||(i==n-1&&j==n-1))
{
a[n-1][n-1]=++k;
}
}
H2:
算法上:这里主要是转圈,相比于第一题更简单,但是这题整体上在对奇偶数的方阵有不同的难点。如果是奇数方阵,那最后一个填入的就是最中间的格子,很方便跳出循环。
对于偶数:
细心的话不难发现如下规律:行数上,总等于输入的边长数除2;列数,总等于行数-1.那这道题思路也明朗了,确定了固定的循环跳出点,剩下的转圈也只是在边界的判断上数值与方向微调即可。
具体算法的主体如下:
if(l%2==0) //偶数
{
int i=0; // 行
int j=0; // 列
int k=l/2; // 确定最后一个填数字的格子
int m=l; // 回转的行,列指标
int n=0; // 回转的行指标
int r=0; // 填入的数字
while(a[k][k-1]==0)
{
while(j<m) // 行从左向右填
{
a[i][j]=++r;
j++;
}
i++; // 这里向下走一步,下一个起点
j--; // 数值回调梅开二度
while(i<m) // 列从上往下填
{
a[i][j]=++r;
i++;
}
i--;
j--; // 向左走一步,下一个起点
while(j>=n) // 到最后一行,从右向左填
{
a[i][j]=++r;
j--;
}
j++;
i--; // 向上走一步
n++; // 调整行指标,因为第一行已经填满了
while(i>=n) // 从下往上填
{
a[i][j]=++r;
i--;
}
i++;
j++; // 往右走一步,新的起点
m--; // 最后一列已经填满了,指标相应减少
}
}
else // 奇数
{
int i=0;
int j=0;
int k=l/2;
int m=l;
int n=0;
int r=0;
while(a[k][k]==0)
{
while(j<m)
{
a[i][j]=++r;
j++;
}
i++;
j--;
while(i<m)
{
a[i][j]=++r;
i++;
}
i--;
j--;
while(j>=n)
{
a[i][j]=++r;
j--;
}
j++;
i--;
n++;
while(i>=n)
{
a[i][j]=++r;
i--;
}
i++;
j++;
m--;
}
}
关于第二题的解法,非常荣幸可以邀请到我的一个同学(c语言大佬)分享他的解题思路,他使用了类似向量的移动方式,以下是他对这道题的看法:
受前一道“之字形”填方阵的题目的影响,我选择一步一步来填这个数组。
定义n*n二维数组后,我用四个变量记录没有被填过的数组的边界:
mi(最大行数,下边界)、mj(最大列数、右边界)、i(最小行数、上边界)、j(左边界)。
先用变量pp(present position)记录“我”的位置,再用二元数组mv(movement)记录“我”前进的方向。“我”一开始在(0,0)位置,前进的方向为列+1,行不变,即mv=(0,1)。移动即是在当前坐标pp上加上mv,即“我”的下一个位置为(0+0,0+1)=(0,1)。
“我”每次移动之前,我会判断下一格是否“碰壁”了。
if(mv[1] == 1)
{ //向右走
if(pp[1]+1>mj)
{
} //碰壁了
在不碰到数组边界时,保持移动方向mv不变:如果碰当边界了就改变前进方向mv。
mv[0] = 1;
mv[1] = 0;//右转,并且让 刚刚走过的位置变成数组的边界
i++;//让数组的上边界变为 “1” 行,注意,对不同的前进方向,右转之后的方向不一样(我用了四个if)
//例如,mv=(0,1)时右转之后为(1,0);(1,0)右转之后变为(0,-1)。
else if(pp[0]+1>mi)
{ //判断向下(1,0)走是否撞墙
mv[0] = 0; //撞墙就右转
mv[1] = -1;//变为(0,-1)
mj--;
}
}
当“我”判断完了,并改变方向(或者没改变)之后,向mv所指方向移动一格,并填入数字。如此循环往复。
附上算法主体的代码:
int i = 0, j = 0; //i、j为当前数组左、上边界
int n , mi , mj; //mi、mj为当前数组右、下边界
scanf("%d", &n);
int nums[n][n];
int pp[] = {0,0}; //present position 当前位置
int mv[2] = {0,1}; // movement 前进方向
int num = 2;
int n2 = n*n;
nums[0][0] = 1; //在原点填上1
for(i = j = 0, mi = mj = n-1;num <= n2;num++)
{
if(mv[1] == 1)
{ //向右走
if(pp[1]+1>mj)
{
mv[0] = 1; // 调整向量的方向
mv[1] = 0;
i++;
}
}
else if(pp[0]+1>mi)
{ //判断向下走是否撞墙
mv[0] = 0; //撞墙就右转
mv[1] = -1; // 调整向量的方向
mj--;
}
else if(mv[1] == -1)
{ //向左走
if(pp[1]-1<j)
{
mv[0] = -1; // 调整向量的方向
mv[1] = 0;
mi--;
}
}
else if(mv[0] == -1)
{ //向上走
if(pp[0]-1<i)
{
mv[0] = 0; // 调整向量的方向
mv[1] = 1;
j++;
}
}
pp[0]+=mv[0];
pp[1]+=mv[1]; //我向movement所指向的方向前进一格
nums[pp[0]][pp[1]] = num;
continue;
}
写在最后 :
我的算法不一定是最高效的,顶多算是我第一时间能够想到的吧,更好的方法当然有,递归回调啥的,但是那不一定好被读懂,不一定好让我讲清楚、说明白。
由于是选做题,我就不给出全部AC的代码,只给出我认为重要的算法部分,然后就是结尾杀:小聪明编译器不给用变长数组给我改半天。
拒绝白piao,从我做起。