铺瓷砖两则

两道铺瓷砖的题,是比较绕但是算法偏简单的题目(自我回顾)

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,从我做起。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值