您的位置:首页 > 其它

一道网易笔试题(加入最优解法)

2006-11-27 19:34 260 查看
最近看完了表、栈、队列、二叉树、二叉搜索树、堆、Huffuman树的数据结构。突然想试试自己的程序水平有没有提高(似乎有点太急于求成^_^)。恰逢自己刚给网易投了一份简历,于是,在百度上搜了一些网易的笔试题。结果,其中这道题,一下把我难住了:

如图:



 
设“1”的坐标为(0,0) “7”的坐标为(-1,-1) 编写一个小程序,使程序做到输入坐标(X,Y)之后显示出相应的数字。

这个图有点像螺旋,它是按顺时针方向(由1->2->3->4->5…),逐渐向外膨胀。显然,如果以1为原点(0,0),以向右为x轴的正方向,以向下为y轴的正方向:



那么用人眼很容易就看出7的坐标为(-1,-1),这正与题目的假设一致。其他,如5的坐标应为(-1,1)。
但是,怎么让计算机知道呢?我的大脑突然不知所措,我在想,如果真出了这道题我挂定了^_^。但是,既然现在我是安然的坐在自己的卧室里,我绝对不会向它投降。
于是,躺在床上,望着天花板,想象着从这个螺旋的原点1出发,一起来看看:
Step1:从1向左走1步到达2。坐标(0,0)->(1,0),即x加1。
Step2:从2向下走1步到达3,坐标(0,1)->(1,1),即y加1。
Step3:从3向右走1步到达4,坐标(1,1)->(0,1),即x减1。
Step4:从4向右走1步到达5,坐标(0,1)->(-1,1),即x减1。
Step5:从5向上走1步到达6,坐标(-1,1)->(-1,0),即y减1。
Step6:从6向上走1步到达7,坐标(-1,0)->(-1,-1),即y减1。
……
当在大脑中想象着这一步一步的异同,一个灵感闪灵了。你是否发现了其中的规律?好吧,让我总结一下它的规律:
1、每向左(left)走一步,x就加1。每向右(right)走一步,x就减1。每向下(down)走一步,y就加1。每向上(up)走一步,y就减1。
这一条规律,只要有点直角坐标的知识,是显而易见的。当我们用程序去模拟这样一步一步地行走的时候,另外一个问题,就浮出水面了:怎样告诉计算机,让它按照一个顺时针的螺旋方式,去遍历每一个结点?比如,当计算机来到结点2的时候,它怎么知道,下一步应该往下走呢?当计算机来到结点5的时候,它怎么知道,下一点应该往上走呢?其实,这里还藏着另一条规律:
我把它叫做状态转换的规律。这个规律包含两个方面:
2.1、从结点1开始,
A、向右走
B、向下走
C、向左走
D、向上走
又回到A。形成一个循环,即:A->B->C->D->A->B……。
在每一个方向上,应该走多少步,才改变方向呢?现假设,现在开始改变方向,且已知上一次向右走了rNum步,向下走了dNum步,向左走了lNum步,向上走了uNum步。那么从现在开始,
2.2、沿新方向所走的步数,应该等于上一次相反方向所走的步数加1。例如,现在处于结点3,刚才从2->3的时候是往下走,现在要改变方向,向左走了。这个时候,由于上一次向右走了1步(即由1->2)。所以,这次应该向左走1+1=2步。

找到以上的两个规律,就可以写程序了。
我们可以用枚举类型,来标识四个方向(right,down,left,up)。
用四个整形变量,来记录上一次各个方向所走的步数(rNum,dNum,lNum,uNum)。
用四个整形变量,来记录在每一个方向累积所走的步数(rSum,dSum,lSum,uSum)。
这样,结点值就由这个公式确定:nodeVal = lSum+dSum+rSum+uSum+1。之所以加1,是因为原点的值为1,而不是0。
而坐标值可以这样确定:因为向右为x轴正方向,故x = rSum-lSum。因为向下为y轴的正方向,故y = dSum-uSum。
以下,是我用C++写的程序:

#include <iostream>
#include <conio.h>
using namespace std;

//输入坐标,返回结点值
int Val(int x, int y)
{
if (x==0 && y==0)
return 1;
//四种行走方向
enum Order{right, down, left, up};
//上一次的行走的方向
//从状态循环思考,显然由1到2的上一步的状态应该是up。
Order oState = up;

//上一次 右、下、左、上移动的次数
//皆初始化为0
int rNum=0, dNum=0, lNum=0, uNum=0;

//左、下、右、上累积移动的次数,
//坐标满足:因为向右为x轴正方向,故x=rSum-lSum;
//因为向下为y轴正方向,故y=dSum-uSum;
//坐标所在结点的数值为:lSum+rSum+uSum+dSum+1;
//皆初始化为0
int lSum=0, dSum=0, rSum=0, uSum=0;

//一个无限循环,一旦找到与x、y相等的坐标,则结束。
while (true)
{
//状态转换
switch (oState)
{
case up: //up->right
{
rNum = 0;
for (int i=0; i<lNum+1; i++)
{
++rNum;
++rSum;
if (x == rSum-lSum && y == dSum-uSum)
return (lSum+rSum+uSum+dSum+1);

}
oState = right;
break;
}
case right: //right->down
{
dNum = 0;
for (int i=0; i<uNum+1; i++)
{
++dNum;
++dSum;
if (x == rSum-lSum && y == dSum-uSum)
return (lSum+rSum+uSum+dSum+1);
}
oState = down;
break;
}
case down: //down->left
{
lNum = 0;
for (int i=0; i<rNum+1; i++)
{
++lNum;
++lSum;
if (x == rSum-lSum && y == dSum-uSum)
return (lSum+rSum+uSum+dSum+1);
}
oState = left;
break;
}
case left: //left->up
{
uNum = 0;
for (int i=0; i<dNum+1; i++)
{
++uNum;
++uSum;
if (x == rSum-lSum && y == dSum-uSum)
return (lSum+rSum+uSum+dSum+1);
}
oState = up;
break;
}
}
}

}

int main(int argc,char * argv[])
{
int x, y;
cout <<"请输入坐标(x y):";
while (cin>>x>>y)
{
cout <<"坐标所在的结点值为:"<< Val(x, y)<<endl;
cout <<"请输入坐标(x y):";
}
return 0;
}

补充:此题最优秀的解法―――根据alula朋友的精彩回贴整理。能够根据规律挖掘出一个求解公式,就是最快速的方法。下面这个程序,做了充分的注释,只要在草稿上,按照注释,作一个图,就一目了然了:

#include <iostream>
#include <conio.h>
#include <math.h>
using namespace std;

int newVal(int x, int y)
{
//以结点1为原点
//以相邻两结点间的距离为单位(如结点2与结点3的之间线段)
//结点7所在的正方形(由结点2、3、4、5、6、7、8构成)的边长
//的一半为1,即结点7到原点1的最大投影距离为1。
//于是由结点坐标,可以求出此结点所在的正方形的投影距离:
int r = max(abs(x),abs(y));

//进行坐标变换,即把坐标原点移动到正方形的一个角结点上,
//使整个正方形落在第一象限,例如,当r=1时,将把坐标原点从结点1
//移动到结点7。
x += r;
y += r;

//正方形的边长,等于投影距离的两倍
int d = 2*r;

int s; //s为结点在自己的正方形的偏移量
if (y == 0)
s = 3*d + x;
else if (x == 0)
s = 2*d + (d-y);
else if (y == d)
s = d + (d-x);
else
s = y;

//pow((r+1),2)为内层的结点数。
//例如,结点9的内层由结点1和正方形A(2、3、4、5、7、8)构成
//这些内层的总结点数恰为:(正方形A的边长+1)的平方,
//因为:正方形A的边长 =(结点9所在正方形的半径-1)*2
//故:内层结点数 = (结点9所在正方形的边长-1)的平方
//结点值 = 在当前正方形的偏移量 + 内层的结点数
s += pow((d-1),2);

return s;

}
int main(int argc,char * argv[])
{
int x, y;
cout <<"请输入坐标(x y):";
while (cin>>x>>y)
{
cout <<"坐标所在的结点值为:"<<f(x, y)<<endl;
cout <<"请输入坐标(x y):";
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: