链表常见笔试题
2013-09-01 20:43
267 查看
转帖地址:http://xinklabi.javaeye.com/blog/699034
先什么也不说,假设链表节点的数据结构为:
struct node
{
int data;
struct node* next;
};
创建单链表的程序为:
struct node* create(unsigned int n)
{
//创建长度为n的单链表
assert(n > 0);
node* head;
head = new node;
head->next = NULL;
cout << "请输入head节点的值(int型):";
cin >> head->data;
if (n == 1)
{
return head;
}
node* p = head;
for (unsigned int i = 1; i < n; i++)
{
node* tmp = new node;
tmp->next = 0;
cout << "请输入第" << i+1 << "个节点的值(int):";
cin >> tmp->data;
p->next = tmp;
p = tmp;
}
return head;
}
问题1:链表逆置
思想为:head指针不断后移,指针反向即可,代码为:
void reverse(node*& head)
{
if (head != NULL && head->next != NULL)
{
node* p = head;
node* q = head->next;
p->next = NULL;
while (q->next != NULL)
{
head = q->next;
q->next = p;
p = q;
q = head;
}
head->next = p;
}
return;
}
(
NODE *reverse_list(NODE *p_head)
{
NODE *p1, *p2;
p1 = p_head->pNext;
p_head->pNext = NULL;
while (NULL!=p1)
{
p2 = p1->pNext;
p1->pNext = p_head->pNext;
p_head->pNext = p1;
p1 = p2;
}
return p_head;
}
)
问题2:删除不知头结点链表的某个节点
如果单向链表不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指向的节点?
思想为:把这个节点的下一个节点的值复制给该节点,然后删除下一个节点即可。
问题3:怎么判断链表中是否有环?
思想为:设置两个指针,一个步长为1,另一个步长为2,依次后移,如果相遇且都不为空,则有环。
与这个类似的问题包括:怎么快速检测出一个巨大的链表中的死链?或者如何找出一个单链表的中间节点?
代码为:
bool loop(node* head)
{
bool flag = true;
if (head == NULL)
{
flag = false;
}
node* one = head;
node* two = head->next;
if (two == NULL)
{
flag = false;
}
while (one != two)
{
if (one != NULL)
{
one = one->next;
}
if (two != NULL)
{
two = two->next;
}
if (two == NULL)
{
break;
}
two = two->next;
if (one == NULL || two == NULL)
{
break;
}
}
if (one == NULL || two == NULL)
{
flag = false;
}
return flag;
}
问题4:如果一个单向链表,其中有环,怎么找出这个链表循环部分的第一个节点?
思想为:假设该节点在x位置处,假设步长为1的指针和步长为2的指针相遇在x+z处,循环的长度为y,那么2(x+z)-(x+z)=k*y,
那么当一个指针再从开始出后移时,另一个指针从相遇点开始后移时,这两个指针就会在循环开始处相遇。
(
1。使用2个步长不同的指针,设指针a每步走1个节点,指针b每步走2个节点,它们同时开始遍历链表
2。当指针a,b第一次相遇时,记录下此时的节点为node,此时a走了x个节点。a和b此时都在环里。
3。当指针a,b第二次相遇时,节点一定还是node,此时a走了y个节点,y-x即为环里的节点数。设y-x=k
4。保持a,b不动。再使用一个新的指针c,每步走1个节点,走x - k步,此时c离node的距离为k,a离node的距离也为k。
5。a,c同时往前走,当它们相遇时,位置即是第一个循环节点了。
)
代码为:
node* findLoopPlace(node* head, unsigned int* place = NULL)
{
//查找循环的位置,place存储位置
if (!loop(head))
{
return NULL;
}
node* one = head;
node* two = head->next;
unsigned int count = 1;
while (one != two)
{
one = one->next;
two = two->next->next;
}
one = head;
while (one != two)
{
if (count != 1)
{
one = one->next;
}
two = two->next;
count++;
}
*place = count;
return one;
}
问题5:如何查找链表中倒数第k个节点?
思想为:两个指向头结点的指针,一个先向后移动k位,然后两个同时向后面移动直到一个节点到达链尾,前面一个指针的位置就是了。
node* findLastK(node* head,unsigned int k)
{
//查找单链表倒数第k个位置
node* p = head;
unsigned int count = 0;
while (p != NULL)
{
p = p->next;
count++;
}
if (count < k)
{
return NULL;
}
p = head;
node* q = head;
for (unsigned int i = 0; i < k; i++)
{
p = p->next;
}
while (p != NULL)
{
q = q->next;
p = p->next;
}
return q;
}
问题6:编程序判断两个链表是否相交。
这个问题的精彩解说请参见《编程之美》一书之《编程判断两个链表是否相交》,这里就不写了,该书的pdf文档在网上很好下。
分析与解法
这样的一个问题,也许我们平时很少考虑。但在一个大的系统中,如果出现两个链表相交的情况,而且释放了其中一个链表的所有节点,那样就会造成信息的丢失,并且另一个与之相交的链表也会受到影响,这是我们不希望看到的。在特殊的情况下,的确需要出现相交的两个链表,我们希望在释放一个链表之前知道是否有其他链表跟当前这个链表相交。
【解法一】直观的想法
看到这个问题,我们的第一个想法估计都是,“不管三七二十一”,先判断第一个链表的每个节点是否在第二个链表中。这种方法的时间复杂度为O(Length(h1) * Length(h2))。可见,这种方法很耗时间。
【解法二】利用计数的方法
很容易想到,如果两个链表相交,那么这两个链表就会有共同的节点。而节点地址又是节点的唯一标识。所以,如果我们能够判断两个链表中是否存在地址一致的节点,就可以知道这两个链表是否相交。一个简单的做法是对第一个链表的节点地址进行hash排序,建立hash表,然后针对第二个链表的每个节点的地址查询hash表,如果它在hash表中出现,那么说明第二个链表和第一个链表有共同的节点。这个方法的时间复杂度为O(max(Length(h1) + Length(h2)))。但是它同时需要附加O(Length(h1))的存储空间,以存储哈希表。虽然这样做减少了时间复杂度,但是是以增加存储空间为代价的。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?
【解法三】
由于两个链表都没有环,我们可以把第二个链表接在第一个链表后面,如果得到的链表有环,则说明这两个链表相交。否则,这两个链表不相交(如图3-9所示)。这样我们就把问题转化为判断一个链表是否有环。
图3-9 链表有环的情况
判断一个链表是否有环,也不是一个简单的问题,但是需要注意的是,在这里如果有环,则第二个链表的表头一定在环上,我们只需要从第二个链表开始遍历,看是否会回到起始点就可以判断出来。最后,当然可别忘了恢复原来的状态,去掉从第一个链表到第二个链表表头的指向。
第2/3页
这个方法总的时间复杂度也是线性的,但只需要常数的空间。
【解法四】
仔细观察题目中的图示,如果两个没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表所共有的。那么我们能否利用这个特点简化我们的解法呢?困难在于我们并不知道哪个节点必定是两个链表共有的节点(如果它们相交的话)。进一步考虑“如果两个没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。
先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得到了一个时间复杂度,它为O((Length(h1) + Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法比解法三更胜一筹。
扩展问题
1. 如果链表可能有环呢?上面的方法需要怎么调整?
2. 如果我们需要求出两个链表相交的第一个节点呢?
文章后面给了两个扩展问题:
(1)如果链表可能有环,如何做判断?
思想为:首先应该明白,只有一个链表有环的情况下是不会相交的,只有都有环或者都没有环的情况下才可能相交,都没有环的情况下最简便的方法就是判断链尾是否相交即可;都有环的情况下,分别找到环上的任一点,一个不动,另一个步进,即可判断是否相交。
(2)如何求相交链表的第一个节点?应该为单链表情况
思想为:方法一是先把任一个链表连成环,即从表尾接到表头,按照问题4的解法;方法二是计算两个链表的长度,而两个链表是按照尾部对齐的,那么从短链表的第一个位置从长链表的第长度差+1的位置依次比较指针值,相等的位置即是。
相关程序包括:单链表中在某个位置插入环以及销毁链表等,代码如下:
void insertCircle(node* head, unsigned int n)
{
//在第n个位置形成环,记head为n=1
node* p = head;
node* q = head;
unsigned int count = 1;
while(p->next != NULL)
{
p = p->next;
count++;
}
if (n <= count)
{
for (unsigned int i = 1; i < n; i++)
{
q = q->next;
}
p->next = q;
}
return;
}
void destroy(node* head)
{
//销毁链表
if (loop(head))
{
node *q = findLoopPlace(head);
while (head != q)
{
node* p = head;
head = head->next;
delete p;
}
head = head->next;
q->next = NULL;
destroy(head);
}
else
{
while (head != NULL)
{
node* p = head;
head = head->next;
delete p;
}
}
}
---------------------------------------------------
1求两个单链表是否相交,如果相交,求出交点
a: 所有结点地址比较
b:对链表1使用hash算法存储,依次查找链表2结点的地址,如果找到,则相交,否则不相交,此方法虽然效率尚可,但是占用空间很大,属于使用空间换效率的方法。
c:将链表2接在链表1后面,判断链表是否有环,如果有,则相交,最后,当然不要忘记恢复原来状态,去掉第一个链表到第二个链表头的指向。
d:判断交点d1:分别求链表1,2的长度x,y,链表1逆序,遍历链表2记下其长度z,则可列出一个x,y,z的方程,求出链表1或者2到结点的距离。
e: 判断交点d2:分别求链表1,2的长度x,y,假设x>y,则让链表1先前进x-y部,然后两个链表同时单步前进,并比较地址,如果相等,则为交点,返回。
2求两个链表是否相交,如果相交,求出交点(存在环)
a) 链表1单步前进,链表2双步前进,当链表2的next指针等于链表一的当前地址时,两者相交
b)求交点,从a中的一点,将换断开,逆序链表1,此时相当于生成3个新的链表,只要求出这三个链表的交点,链表1还原,再判断生成的两个不同交点,是否在链表2上
3己知两个链表相交(如Y的形状),找出其第一个交点
假设两条链表有公共节点Node, 那么从Node往后的节点必定都是公共节点.也就是这两个链表成"Y"的形状.那么两个链表的长度差只会出现在Node之前.因此,我们求出两个链表的长度,然后用长的减去短的长度,然后让长的那个先跑这个差值后,然后两个表在开始时行比较
http://blog.csdn.net/fty8788/article/details/6857020
------------------------------------------------------------------------------------------------------------------
先什么也不说,假设链表节点的数据结构为:
struct node
{
int data;
struct node* next;
};
创建单链表的程序为:
struct node* create(unsigned int n)
{
//创建长度为n的单链表
assert(n > 0);
node* head;
head = new node;
head->next = NULL;
cout << "请输入head节点的值(int型):";
cin >> head->data;
if (n == 1)
{
return head;
}
node* p = head;
for (unsigned int i = 1; i < n; i++)
{
node* tmp = new node;
tmp->next = 0;
cout << "请输入第" << i+1 << "个节点的值(int):";
cin >> tmp->data;
p->next = tmp;
p = tmp;
}
return head;
}
问题1:链表逆置
思想为:head指针不断后移,指针反向即可,代码为:
void reverse(node*& head)
{
if (head != NULL && head->next != NULL)
{
node* p = head;
node* q = head->next;
p->next = NULL;
while (q->next != NULL)
{
head = q->next;
q->next = p;
p = q;
q = head;
}
head->next = p;
}
return;
}
(
NODE *reverse_list(NODE *p_head)
{
NODE *p1, *p2;
p1 = p_head->pNext;
p_head->pNext = NULL;
while (NULL!=p1)
{
p2 = p1->pNext;
p1->pNext = p_head->pNext;
p_head->pNext = p1;
p1 = p2;
}
return p_head;
}
)
问题2:删除不知头结点链表的某个节点
如果单向链表不知道头节点,一个指针指向其中的一个节点,问如何删除这个指针指向的节点?
思想为:把这个节点的下一个节点的值复制给该节点,然后删除下一个节点即可。
问题3:怎么判断链表中是否有环?
思想为:设置两个指针,一个步长为1,另一个步长为2,依次后移,如果相遇且都不为空,则有环。
与这个类似的问题包括:怎么快速检测出一个巨大的链表中的死链?或者如何找出一个单链表的中间节点?
代码为:
bool loop(node* head)
{
bool flag = true;
if (head == NULL)
{
flag = false;
}
node* one = head;
node* two = head->next;
if (two == NULL)
{
flag = false;
}
while (one != two)
{
if (one != NULL)
{
one = one->next;
}
if (two != NULL)
{
two = two->next;
}
if (two == NULL)
{
break;
}
two = two->next;
if (one == NULL || two == NULL)
{
break;
}
}
if (one == NULL || two == NULL)
{
flag = false;
}
return flag;
}
问题4:如果一个单向链表,其中有环,怎么找出这个链表循环部分的第一个节点?
思想为:假设该节点在x位置处,假设步长为1的指针和步长为2的指针相遇在x+z处,循环的长度为y,那么2(x+z)-(x+z)=k*y,
那么当一个指针再从开始出后移时,另一个指针从相遇点开始后移时,这两个指针就会在循环开始处相遇。
(
1。使用2个步长不同的指针,设指针a每步走1个节点,指针b每步走2个节点,它们同时开始遍历链表
2。当指针a,b第一次相遇时,记录下此时的节点为node,此时a走了x个节点。a和b此时都在环里。
3。当指针a,b第二次相遇时,节点一定还是node,此时a走了y个节点,y-x即为环里的节点数。设y-x=k
4。保持a,b不动。再使用一个新的指针c,每步走1个节点,走x - k步,此时c离node的距离为k,a离node的距离也为k。
5。a,c同时往前走,当它们相遇时,位置即是第一个循环节点了。
)
代码为:
node* findLoopPlace(node* head, unsigned int* place = NULL)
{
//查找循环的位置,place存储位置
if (!loop(head))
{
return NULL;
}
node* one = head;
node* two = head->next;
unsigned int count = 1;
while (one != two)
{
one = one->next;
two = two->next->next;
}
one = head;
while (one != two)
{
if (count != 1)
{
one = one->next;
}
two = two->next;
count++;
}
*place = count;
return one;
}
问题5:如何查找链表中倒数第k个节点?
思想为:两个指向头结点的指针,一个先向后移动k位,然后两个同时向后面移动直到一个节点到达链尾,前面一个指针的位置就是了。
node* findLastK(node* head,unsigned int k)
{
//查找单链表倒数第k个位置
node* p = head;
unsigned int count = 0;
while (p != NULL)
{
p = p->next;
count++;
}
if (count < k)
{
return NULL;
}
p = head;
node* q = head;
for (unsigned int i = 0; i < k; i++)
{
p = p->next;
}
while (p != NULL)
{
q = q->next;
p = p->next;
}
return q;
}
问题6:编程序判断两个链表是否相交。
这个问题的精彩解说请参见《编程之美》一书之《编程判断两个链表是否相交》,这里就不写了,该书的pdf文档在网上很好下。
分析与解法
这样的一个问题,也许我们平时很少考虑。但在一个大的系统中,如果出现两个链表相交的情况,而且释放了其中一个链表的所有节点,那样就会造成信息的丢失,并且另一个与之相交的链表也会受到影响,这是我们不希望看到的。在特殊的情况下,的确需要出现相交的两个链表,我们希望在释放一个链表之前知道是否有其他链表跟当前这个链表相交。
【解法一】直观的想法
看到这个问题,我们的第一个想法估计都是,“不管三七二十一”,先判断第一个链表的每个节点是否在第二个链表中。这种方法的时间复杂度为O(Length(h1) * Length(h2))。可见,这种方法很耗时间。
【解法二】利用计数的方法
很容易想到,如果两个链表相交,那么这两个链表就会有共同的节点。而节点地址又是节点的唯一标识。所以,如果我们能够判断两个链表中是否存在地址一致的节点,就可以知道这两个链表是否相交。一个简单的做法是对第一个链表的节点地址进行hash排序,建立hash表,然后针对第二个链表的每个节点的地址查询hash表,如果它在hash表中出现,那么说明第二个链表和第一个链表有共同的节点。这个方法的时间复杂度为O(max(Length(h1) + Length(h2)))。但是它同时需要附加O(Length(h1))的存储空间,以存储哈希表。虽然这样做减少了时间复杂度,但是是以增加存储空间为代价的。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?
【解法三】
由于两个链表都没有环,我们可以把第二个链表接在第一个链表后面,如果得到的链表有环,则说明这两个链表相交。否则,这两个链表不相交(如图3-9所示)。这样我们就把问题转化为判断一个链表是否有环。
图3-9 链表有环的情况
判断一个链表是否有环,也不是一个简单的问题,但是需要注意的是,在这里如果有环,则第二个链表的表头一定在环上,我们只需要从第二个链表开始遍历,看是否会回到起始点就可以判断出来。最后,当然可别忘了恢复原来的状态,去掉从第一个链表到第二个链表表头的指向。
第2/3页
这个方法总的时间复杂度也是线性的,但只需要常数的空间。
【解法四】
仔细观察题目中的图示,如果两个没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表所共有的。那么我们能否利用这个特点简化我们的解法呢?困难在于我们并不知道哪个节点必定是两个链表共有的节点(如果它们相交的话)。进一步考虑“如果两个没有环的链表相交于某一节点的话,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。
先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得到了一个时间复杂度,它为O((Length(h1) + Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法比解法三更胜一筹。
扩展问题
1. 如果链表可能有环呢?上面的方法需要怎么调整?
2. 如果我们需要求出两个链表相交的第一个节点呢?
文章后面给了两个扩展问题:
(1)如果链表可能有环,如何做判断?
思想为:首先应该明白,只有一个链表有环的情况下是不会相交的,只有都有环或者都没有环的情况下才可能相交,都没有环的情况下最简便的方法就是判断链尾是否相交即可;都有环的情况下,分别找到环上的任一点,一个不动,另一个步进,即可判断是否相交。
(2)如何求相交链表的第一个节点?应该为单链表情况
思想为:方法一是先把任一个链表连成环,即从表尾接到表头,按照问题4的解法;方法二是计算两个链表的长度,而两个链表是按照尾部对齐的,那么从短链表的第一个位置从长链表的第长度差+1的位置依次比较指针值,相等的位置即是。
相关程序包括:单链表中在某个位置插入环以及销毁链表等,代码如下:
void insertCircle(node* head, unsigned int n)
{
//在第n个位置形成环,记head为n=1
node* p = head;
node* q = head;
unsigned int count = 1;
while(p->next != NULL)
{
p = p->next;
count++;
}
if (n <= count)
{
for (unsigned int i = 1; i < n; i++)
{
q = q->next;
}
p->next = q;
}
return;
}
void destroy(node* head)
{
//销毁链表
if (loop(head))
{
node *q = findLoopPlace(head);
while (head != q)
{
node* p = head;
head = head->next;
delete p;
}
head = head->next;
q->next = NULL;
destroy(head);
}
else
{
while (head != NULL)
{
node* p = head;
head = head->next;
delete p;
}
}
}
---------------------------------------------------
1求两个单链表是否相交,如果相交,求出交点
a: 所有结点地址比较
b:对链表1使用hash算法存储,依次查找链表2结点的地址,如果找到,则相交,否则不相交,此方法虽然效率尚可,但是占用空间很大,属于使用空间换效率的方法。
c:将链表2接在链表1后面,判断链表是否有环,如果有,则相交,最后,当然不要忘记恢复原来状态,去掉第一个链表到第二个链表头的指向。
d:判断交点d1:分别求链表1,2的长度x,y,链表1逆序,遍历链表2记下其长度z,则可列出一个x,y,z的方程,求出链表1或者2到结点的距离。
e: 判断交点d2:分别求链表1,2的长度x,y,假设x>y,则让链表1先前进x-y部,然后两个链表同时单步前进,并比较地址,如果相等,则为交点,返回。
2求两个链表是否相交,如果相交,求出交点(存在环)
a) 链表1单步前进,链表2双步前进,当链表2的next指针等于链表一的当前地址时,两者相交
b)求交点,从a中的一点,将换断开,逆序链表1,此时相当于生成3个新的链表,只要求出这三个链表的交点,链表1还原,再判断生成的两个不同交点,是否在链表2上
3己知两个链表相交(如Y的形状),找出其第一个交点
假设两条链表有公共节点Node, 那么从Node往后的节点必定都是公共节点.也就是这两个链表成"Y"的形状.那么两个链表的长度差只会出现在Node之前.因此,我们求出两个链表的长度,然后用长的减去短的长度,然后让长的那个先跑这个差值后,然后两个表在开始时行比较
http://blog.csdn.net/fty8788/article/details/6857020
------------------------------------------------------------------------------------------------------------------
相关文章推荐
- 链表常见笔试题
- 笔试常见链表题
- 单向链表常见的笔试面试题
- 编程之美8:链表常见面试笔试题集合
- 链表的一些常见笔试面试问题总结及代码
- 单链表常见笔试面试题总结
- 【应聘笔记系列】三个常见链表笔试题
- 链表常见笔试题
- 笔试面试常见的链表操作
- 常见笔试面试题:实现一个递增排序的单链表
- 美团笔试-链表逆置
- 链表后半部分反转(2016亚信实习生笔试题)
- 常见C++笔试题目整理(含答案)6
- 链表常见面试题一:基本问题
- Android 笔试/面试,常见问题整理
- 常见的几个小笔试题
- 笔试中常见的位运算案例分析
- 链表常见面试题四:解决链表相交问题
- ⑴125条常见的java面试笔试题大汇总
- (转)C/C++ 程序设计员应聘常见 面试笔试 试题深入剖析