一、题目描述
输入: N M k
输出: 转换后的小数(不超过 k )
要求: 仅编写将分数转换为小数的函数 change( int n, int m, NODE * head ) 。
预设代码
/* PRESET CODE BEGIN - NEVER TOUCH CODE BELOW */
#include
#include
typedef struct node
{ int data;
struct node * next;
} NODE;
void output( NODE *, int );
void change( int, int, NODE * );
void output( NODE * head, int kk )
{ int k=0;
printf("0.");
while ( head->next != NULL && knext->data );
head = head->next;
k ++;
}
printf("\n");
}
int main()
{ int n, m,k;
NODE * head;
scanf("%d%d%d", &n, &m, &k);
head = (NODE *)malloc( sizeof(NODE) );
head->next = NULL;
head->data = -1;
change( n, m, head );
output( head,k );
return 0;
}
/* PRESET CODE END - NEVER TOUCH CODE ABOVE */
测试输入 期待的输出 时间限制 内存限制 额外进程 测试用例 1 以文本方式显示
- 1 8 50↵
以文本方式显示
- 0.125↵
1秒 64M 0 测试用例 2 以文本方式显示
- 29 33 10↵
以文本方式显示
- 0.8787878787↵
1秒 64M 0
二、 思路分析
循环小数,对其非循环部分,采用单向的线性链表;对于其循环部分,采用循环链表。因此,最终的存储结构就很清晰了。
那么现在只剩两个问题
其一、怎么将小数部分分为非循环部分和循环部分?
其二、怎么确定循环的长度和内容?
为了解决第一个问题,我采用另一个视角去看一个循环小数。
例如0.285714285714......,我可以说没有非循环部分,循环部分是285714; 我也可以说非循环部分是2,循环部分是857142; 我也可以说非循环部分是28,循环部分是571428......我的意思是循环部分可以分几个数字给非循环部分,反正不破坏最终的结果,对吧?
要解决第一个问题,我还需要再补充一项假设。
假设一:n/m的非循环部分不超过10位(循环部分不分数字给非循环部分的情况下)。嗯,也许实际上是不超过5位,也许是不会超过20位,那有什么关系呢?这只是一个大胆的假设罢了。
这样做,虽然我不能直接确定非循环部分的长度或者内容,但是我可以从循环部分“借”几个数字给非循环部分,让其扩充到10位,这也算是解决了第一个问题了。
具体的 *** 作流程是这样的:
①小数部分的前十位组成一个单向的链式表
②第十一位之后的内容一定是循环的,我将其整成一个单向循环链表。
第二个问题相对于第一个问题就更好解决了。
首先引入一个假设。
假设二:n是被除数,m是除数,n÷m的小数部分如果有循环部分,则循环部分长度一定小于m。
为了验证这个假设,我当时立刻列举了好几个例子,例如
除数为68,循环部分的长度是16.
除数为79,循环部分的长度是13.
极端的情况有
除数为97,循环部分的长度则是整整达到了96。但无论如何,96仍然小于97。
这还没有结束。不难看出,这96个数字组成的序列中,如果任取相邻的两个数字,你无法在这个序列的其他任何地方找到同样的相邻两个数字。
于是我提出下面的假设。
假设三:n是被除数,m是除数,d是m的位数,n÷m的小数部分如果有循环部分,而且它的循环部分的长度大于等于d,则循环部分的任意连续d个数字,在同一个循环内找不着第二个。
以上三个假设都是我的观察结论,我不会数论,如果有大佬知道这些怎么证明的话还望不吝赐教!
有了上面的一个视角和三个假设,思路已经了然。
三、代码示例
这道题目的话,我总共写了四版。
第一版是初代尝试时的一个大胆的想法:我直接建立一个长度为一百万的线性链表,干就完了!
void change( int n, int m, NODE * head ) {
NODE *p;
p = (NODE*) malloc (sizeof(NODE));
p->next = NULL;
head->next = p;
p->data = 10*n/m;
n = n*10 - p->data *m;
if (n!=0)
for (int i=0;i<1000000;i++) {
NODE *q;
q = (NODE*) malloc (sizeof(NODE));
q->next = NULL;
q->data = 10*n/m;
n = n*10 - q->data *m;
p->next = q;
p = q;
if (n==0) break;
}
return;
}
这种写法十分简单、无脑,甚至可以说是取巧。它对了前10个用例,但是对于第11个用例,一百万根本不够,盲目开内存也会直接爆。
第二版相对来说是一个比较新的思路,很长,也没什么太大的意义,建议不要感兴趣。
void change( int n, int m, NODE * head ) {
int circle = 0;
//circle=0表示循环,否则表示不循环
//check用来核对,当出现与check一致的序列时,说明上个循环结束,新的循环开始,它的长度由m的位数digit决定,它的内容是loop的前digit位
//fraction是小数部分,loop是循环部分
//例如小数0.28571428571428...,fraction为28571428571428,loop是824175,check是8
int fraction[m+11],loop[m];
int digit=0;
{
int M = m;
while (M>0) {
M/=10; digit++;
}
}
for (int i=0;i=0;i--) {
if (i==m+9) {
loop[j] = fraction[i];
j++;
continue;
}
if (fraction[i]==check[0]) {
flag = 1;
for (int r=0;rdata = loop[j-1];
p->next = NULL;
phead = (NODE*) malloc (sizeof(NODE));
phead = p;
for (int i=j-2;i>=0;i--) {
NODE *q;
q = (NODE*) malloc (sizeof(NODE));
q->next = NULL;
q->data = loop[i];
p->next = q;
p = p->next;
}
p->next = phead;
if (circle>0) {
p = head;
for (int i=0;inext = NULL;
q->data = fraction[i];
p->next = q;
p = p->next;
}
}
else {
p = head;
for (int i=0;inext = NULL;
q->data = fraction[i];
p->next = q;
p = p->next;
}
NODE *q;
q = phead;
while (q->data!=fraction[m-1]&&q->next->data!=fraction[m]) {
q = q->next;
}
p->next = q->next;
}
return;
}
第三版则是完全依照我们在思路分析中的内容。
void change( int n, int m, NODE * head ) {
NODE *p;
p = (NODE*) malloc (sizeof(NODE));
p = head;
//计算出小数部分的前十位,并建立线性表
for (int i=0;i<10;i++) {
NODE *q;
q = (NODE*) malloc (sizeof(NODE));
q->next = NULL;
q->data = n*10/m;
n = n*10 - q->data*m;
p->next = q;
p = p->next;
if (n==0) return;
}
//计算m的位数
int digit=0;
{
int M = m;
while (M>0) {
M/=10; digit++;
}
}
//loop是循环的内容,j用来计数
int loop[m+digit],j=0;
NODE *phead;
phead = p;
{
int flag = 0; //flag=1时表示循环结束,这时已经找到了需要的循环内容,退出while
while (flag==0) {
loop[j] = n*10/m;
n = n*10 - loop[j]*m;
if (j>=digit)
for (int i=digit-1;i>=0;i--) {
flag = 1;
if (loop[j-digit+1+i]!=loop[i]) {
flag = 0; break;
}
}
j++;
}
}
j-=digit; //只有loop[0]到loop[j-digit]才是有效的循环部分
//建立闭环
for (int i=0;inext = NULL;
q->data = loop[i];
p->next = q;
p = p->next;
}
p->next = phead->next;
return;
}
即便是这种方法,第十个样例依然过不去,错误原因是内存引用错误。究其原因,应该是m非常大,导致我开的loop数组太大,爆内存了。
第四版也就是最终AC的版本适当地结合了第一版和第三版的内容。前面分析地足够清楚,故第四版就不放了,自己写吧。以上。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)