请设计时间和空间上都尽可能高效的算法,在不改变链表的前提下,求链式存储的线性表的倒数第m(>0)个元素。
函数接口定义:
ElementType Find( List L, int m );
其中List
结构定义如下:
typedef struct Node *PtrToNode;
struct Node {
ElementType Data; /* 存储结点数据 */
PtrToNode Next; /* 指向下一个结点的指针 */
};
typedef PtrToNode List; /* 定义单链表类型 */
L
是给定的带头结点的单链表;函数Find
要将L
的倒数第m
个元素返回,并不改变原链表。如果这样的元素不存在,则返回一个错误标志ERROR
。
裁判测试程序样例:
#include <stdio.h>
#include <stdlib.h>
#define ERROR -1
typedef int ElementType;
typedef struct Node *PtrToNode;
struct Node {
ElementType Data;
PtrToNode Next;
};
typedef PtrToNode List;
List Read(); /* 细节在此不表 */
void Print( List L ); /* 细节在此不表 */
ElementType Find( List L, int m );
int main()
{
List L;
int m;
L = Read();
scanf("%d", &m);
printf("%d\n", Find(L,m));
Print(L);
return 0;
}
/* 你的代码将被嵌在这里 */
输入样例:
5
1 2 4 5 6
3
输出样例:
4
1 2 4 5 6
代码长度限制 16 KB
时间限制 400 ms
内存限制 64 MB
解法一:
设置两个指针p和q,让 p 指针比 q 指针先走m步,当p指针的p-next=NULL 时,q正好到达倒数第m个元素的位置上。
具体代码如下:
ElementType Find( List L, int m ){
PtrToNode p,q;
p=q=L;
for(int i=0;i<m;i++){//此循环最终结果是p在q的m位之后
p=p->Next;
if(!p)return ERROR;//当p为空时,此时i<m,说明m比链表长度还要长,不存在
}
while(p){//当p为空时循环终止,此时q在倒数m位的位置上
q=q->Next;
p=p->Next;
}
return q->Data;
}
解法二:
暴力解法,先求出链表的总长度n,再用总长度n减去m,再进行一次遍历即可,但是需要对链表进行两次遍历,时间复杂度较高,当n是从0开始计算时,需要n-m+1,还要根据i从哪开始而定,两种遍历:for(int i=0;i<n-m;i++) 或 for(int i=1;i<n-m+1;i++)
具体代码如下:
ElementType Find( List L, int m ){
PtrToNode p;
p=L->Next;//定义p为链表的首元结点
int n=0;
while(p){ //此循环用来计算链表的总长度n
p=p->Next;
n++;
}
if(n<m)return ERROR; //当n的长度<m,返回错误
p=L->Next; //重新定义p为链表的首元结点
for(int i=0;i<n-m;i++){ //此循环中再次遍历,直到p指向n-m的位置
p=p->Next;
}
return p->Data;
}
解法三:
用一个足够大的数组保存链表中的元素值,此方法较易理解,但是空间复杂度较高
也是数组模拟链表的一种应用,当处理数据较为简单时,可以选择数组模拟链表而不选择结构体
详细见:数组模拟的链表
ElementType Find( List L, int m ){
int a[100000];
int n=0;
L=L->Next;
while(L){ //对L进行遍历,将数据全部存入数组a中
a[n++]=L->Data;
L=L->Next;
}
if(m>n) return ERROR;
else return a[n-m];//返回数组a[n-m]
}
见原文,这里对文章进行了一下修改和补充