单链表的一些常考的面试题——基础篇

来源:互联网 发布:淘宝售前客服是做什么? 编辑:程序博客网 时间:2024/05/22 07:55

============================================================================================================================================

单链表的面试题

基础篇:

  • 逆序打印问题 
    就是将单链表逆序输出在屏幕上。一般思路是先将链表逆序再打印输出到屏幕上,但这样就改变了链表,如果只是需要逆序打印这种方法固然可取。但往往要求不能改变链表,这就需要用到递归的方法了。递归的思路如下:

Alt text

实现代码:

void ReversePrint(ListNode* pList)
{
if (pList == NULL)
return;
else if (pList != NULL)
{
ReversePrint(pList->next);
printf("%d->", pList->data);
}
}
  • 删除一个无头链表的非尾节点 
    如何删除一个不知道头结点的链表中的非尾节点,就是说不是没有头节点,只是没告诉你头结点,同时也不会传来一个尾节点让你删除。一般情况下都是要知道链表的头结点,遍历链表找到要删除的节点的前一个节点,让这个节点的next指向要删除节点的next,然后释放掉要删除的节点,这就完成了删除一个节点。但这个问题不知道头结点所以无法找到要删除节点的前一个节点,所以不能用上述的一般情况的方法。其使用的方法的思路就是,删除要删除节点的下一个节点,先将下一个节点的值覆盖到要删除节点,再将要删除节点的next链到要删除节点的next的next上,然后释放掉要删除节点的下一个节点,这样就完成了删除无头节点的非尾节点。

实现代码:

void DelNodeAtNoHead_L(ListNode* pos)//传一个要删除的非尾节点
{
ListNode* cur = pos;//
ListNode* next = cur->next;
assert(pos);
if (pos->next == NULL)
return;
cur->data = next->data;//把数据覆盖过去
cur->next = next->next;
free(next);
next = NULL;
}
  • 在无头链表中的某节点前插入一个节点 
    与上一个的解题思路一样,既然找不到前一个节点那就在后面插一个节点,将插入节点的数据与这个位置节点的数据交换(或者直接插入一个数据为位置节点数据的节点,然后将位置节点的数据改为要插入节点的数据)

实现代码

void InsertNodeInNoHead_L(ListNode* pos, DataType x)
{
assert(pos);
ListNode* cur = pos;
ListNode* tmp = BuyNode(pos->data);
tmp->next = pos->next;
pos->next = tmp;
pos->data = x;
}
  • 约瑟夫环 
    据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是商量了一个自杀方式:41个人排成一个圆圈,由第1个人 开始报数,每数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从,Josephus要 他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。这个人数学思维实在是非同一般! 
    对其理解如图:Alt text

实现代码:

ListNode* JosephRing(ListNode* list, int k)
{
ListNode* pre = NULL;
ListNode* cur = list;
if (list == NULL || list->next == NULL)
return NULL;
while (cur->next)
{
cur = cur->next;
}
cur->next = list;
cur = list;
while (cur != cur->next)
{
int count = k;
while (--count)
{
pre = cur;
cur = cur->next;
}
pre->next = cur->next;
free(cur);
cur = NULL;
cur = pre->next;
}
cur->next = NULL;
return cur;
}
  • 链表的反转/逆置 
    对链表进行逆置,这里可不能像顺序表那样通过下标访问后交换数据,链表的访问只能从头到尾遍历,所以这实现起来就不像顺序表那样方便了。我在这里采用头插法去逆置链表,就是定义一个新的头,链表上摘节点插到新头上,然后在将新头往前移。以此类推就得到了一个新的链表,返回这个新链表的头,就得到了一个被逆置的链表。

实现代码:

ListNode* ReverseList(ListNode* list)
{
ListNode* newHead = NULL;
if (list == NULL)
return NULL;
while (list)
{
ListNode* cur = list;
cur->next = newHead;
list = list->next;
newHead = cur;
}
return newHead;
}
  • 链表的冒泡排序 
    冒泡排序的思想不难理解,但重要的是其趟数(外层循环)的停止条件以及每趟(内层循环)的停止条件的判定。 
    条件判定思路如图:                                       Alt text

实现代码:

void BubbleSort(ListNode* list)
{
ListNode* mem = NULL;
if (list == NULL)
return;
while (mem!=list->next)
{
ListNode* pre = list;
ListNode* tail = list;
while (tail->next != mem)
{
ListNode* next = tail->next;
if (tail->data > next->data)
{
DataType tmp = tail->data;
tail->data = next->data;
next->data = tmp;
}
pre = tail;
tail = tail->next;
}
mem = pre->next;
}
}
  • 找一个链表的中间节点 
    问题是考虑到效率问题只能遍历一次链表,这就有些麻烦了。但这里定义快慢指针,就可以非常简便的解决这个问题。思路是这样,让快指针每次走两个节点,慢指针每次走一个节点,当快指针来到尾时慢指针刚好走到中间。但又有一个问题偶数个节点的中间节点有两个,但奇数个节点的中间节点只有一个,其实这两种情况可以用一个逻辑来描述,让奇数个节点的中间节点走准确就可以了,偶数个节点的中间节点返回两个中的前一个。 
    具体思路如图:

实现代码:

ListNode* FindMidNode(ListNode* list)
{
ListNode* fast = list;

ListNode* slow = list;
assert(list);
while (fast->next)
{
ListNode* next = fast->next;
next = next->next;
fast = next;
slow = slow->next;
}
slow->next = NULL;
return slow;
}
  • 找链表的倒数第K个节点 
    其思路和上述找中间节点的思路一样,不过是先让快指针和慢指针之间相差K个节点后两个指针再一起走,走到快指针结束慢指针就来到了第K个节点。

实现代码:

ListNode* FindBkNode(ListNode* list, int k)
{
ListNode* left = list; //相当于慢指针
ListNode* right = list;//相当于快指针
int i = 1;
while (right->next)
{
right = right->next;
if (i >= k)// 让快慢指针相差K步后再一起走
left = left->next;
i++;
}
if (i < k)//排除K值大于链表长度,这时就没有倒数第K个节点这一说了
return NULL;
left->next = NULL;
return left;
}
  • 两个有序链表的融合 
    问题要求两个有序链表要融合成一个有序链表, 
    解题思路如图:             Alt text

实现代码:

ListNode* Merge(ListNode* list1, ListNode* list2)
{
ListNode* head = NULL;
if (list1 == NULL)// 如果为空就返回另一个
return list2;
if (list2 == NULL)
return list1;
if (list1->data > list2->data)// 决定让头指针指向谁
{
head = list2;
list2 = list2->next;
}
else
{
head = list1;
list1 = list1->next;
}
ListNode* tail = head;
while (list1 && list2)// 以摘完某其中一个链表为结束条件
{
if (list1->data > list2->data)// 按升序融合
{
tail->next = list2;//摘取list2的节点
list2 = list2->next;
}
else
{
tail->next = list1;
list1 = list1->next;
}
tail = tail->next;
}
if (list1 == NULL)// list1节点摘完了将list2剩下的节点都链在尾节点上
tail->next = list2;
else
tail->next = list1;
return head;
}

总结

这些题不难但非常考验思考问题的能力,有些问题常规思路想太麻烦而且对计算机来说效率低,这时可以考虑换个角度来思考这个问题也许可以很轻松的解决问题。

原创粉丝点击