在笔试面试考数据结构时,因为时间有限,所出的题不会是红黑树、平衡二叉树等比较复杂的数据结构。链表结构简单,题目规模小但须要仔细考虑细节,所以称为笔试面试中的高频考点。所以,下面总结出链表相关题目,以供复习。面试
1.比较顺序表和链表的优缺点,说说他们分别在什么场景下使用?算法
2.从尾到头打印单链表(剑指offer第五题)数据结构
3.删除一个无头单链表的非尾节点dom
4.在无头单链表的一个非头结点前插入一个节点指针
5.单链表实现约瑟夫环code
6.逆置/反转单链表排序
7.单链表排序(冒泡排序&快速排序)递归
8.合并两个有序链表合并后依然有序内存
9.查找链表的中间节点,要求只能遍历一次链表io
10.查找单链表倒数第K个节点,要求只能遍历一次链表
11.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每一个算法的时间复杂度&空间复杂度。
12.判断两个链表是否相交,若相交,求交点(假设链表不带环)
13.判断两个链表是否相交,若相交,求交点(假设链表带环)
14.复杂链表的复制,一个链表的每一个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,如今要求实现复制这个链表
15.求两个已排序链表中相同的数据。void UnionSet(ListNode* l1,ListNode* l2);
/////////////////////////////////////////////////////////////////////////////////////////////////////////(分割线)///////////////////////////////////////////////////
1.比较顺序表和链表的优缺点,说说他们分别在什么场景下使用?
首先咱们从顺序表和链表的结构上来进行分析:
(1)对于顺序表,不管是动态的仍是静态的,他们都是连续的存储空间,在读取上时间效率比较高,可经过地址之间的运算来访问,可是在插入和删除时会出现比较麻烦的负载操做。
(2)对于顺序表,由于是链式存储。所以在咱们须要的时候咱们才在堆上为他们开辟空间,链表对于插入删除比较简单,可是遍历的话须要屡次跳转。
其次,咱们从顺序表和链表的空间申请方式来看:
(1)对于顺序表,空间开辟是在顺序表已满的时候开辟,开辟次数较多的时候会出现较大的空间浪费
(2)对于链表,空间是针对单个节点的,不存在多余的空间浪费。而且在碎片内存池的机制下,能够有效的利用空间。
综上所述:顺序表通常用于查找遍历操做比较频繁的状况下使用,链表则针对于数据删除修改操做比较多的状况下使用。
2.从尾到头打印单链表
从尾到头打印单链表有两种解法,一种是利用栈把节点从头至尾push进去,利用栈先进后出的特色,从尾到头打印单链表节点,一种是利用递归,在输出现有节点以前输出下一个节点,循环直至最后一个节点,而后再将节点从尾到头依次打印。
code1:利用栈
void PrintTailToHead(ListNode* head)
{
stack<int> st;
ListNode* p = head;
while (p != NULL)
{
st.push(p->_data);
p = p->_next;
}
while (!st.empty())
{
printf("%d->", st.top());
st.pop();
}
}
code2:利用递归
void PrintTailToHead(ListNode* head)
{
if (head != NULL)
{
while (head->_next != NULL)
{
PrintTailToHead(head->_next);
}
}
printf("%d->", head->data);
}
3.删除一个无头单链表的非尾节点
因为链表无头,因此用常规方法删除节点是不可能的。因此咱们能够换种思路,将要删除的节点后面的节点的值赋给要删的节点,而后再把要删除节点的后面的节点删除,等于经过转换,为被删除节点创造了一个头结点。代码以下:
void DeleteNotTailNode(ListNode* p)
{
ListNode* s = p->_next;
assert(s);
p->_data = s->_data;
p->_next = s->_next;
free(p);
}
4.在无头单链表的一个非头结点前插入一个节点
这个题目跟上一个题目很像。在这个非头结点后面插入一个节点,把这个非头节点的值赋给新插入的节点,而后再把要插入的值赋给这个非头节点便可。
void InsertNotHeadNode(ListNode* p, int data)
{
ListNode* s = (ListNode)malloc(sizeof(&ListNode));
assert(s);
s->_next =p->_next;
p->_next = s;
s->_data = p->_data;
p->_data = data;
}
5.单链表实现约瑟夫环(剑指offer第45题)
6.逆置/反转单链表(剑指offer第16题)
7.单链表排序(冒泡排序&快速排序)
8.合并两个有序链表合并后依然有序(剑指offer第17题)
这个题比较简单,分别用指针指向两个链表,比较两个链表指针所指向节点的值,而后将节点取下来从新组成一个链表便可,代码以下:
ListNode Merge(ListNode* head1, ListNode* head2)
{
if (head1 == NULL)
return head2;
if (head2 == NULL)
return head1;
ListNode* newhead = NULL;
if (head1->_data < head2->_data)
{
newhead = head1;
newhead->_next=Merge(head1->_next, head2);
}
if (head1->_data>head2->data)
{
newhead = head2;
newhead->_next = Merge(head1, head2->_next);
}
}
9.查找链表的中间节点,要求只能遍历一次链表
查找链表的中间节点,但只能遍历一次链表,因此咱们会想到用快慢指针来解决这个问题。定义一个快指针,每次走两步,载定义一个慢指针,每次走一步。等到快指针走到链表尾,慢指针所指向的节点就是链表的中间节点。代码以下:
ListNode* FindMidNode(ListNode* head)
{
ListNode* fast;
ListNode* slow;
fast = head;
slow = head;
while (fast&&fast->_next)
{
slow = slow->_next;
fast = fase->_next->_next;
}
retrun slow;
}
10.查找单链表倒数第K个节点,要求只能遍历一次链表(剑指offer第15题)
其实这个题跟上面的题很像,稍微转化一下就能想出思路。咱们能够定义两个指针,一个指针先走K步,而后两个指针同时移动,等到先走的指针走到链表尾部,后走的指针所指向的节点就是倒数第K个节点。要注意考虑链表的各类状况。代码以下:
ListNode* FindKthNode(ListNode* head,int k)
{
if (head == NULL || k == 0)
return NULL;
ListNode* fast;
ListNode* slow;
fast = head;
slow = head;
for (int i = 0; i < k - 1; ++i) //要注意链表长度比K短的状况
{
if (fast->_next != NULL)
fast = fast->_next;
else retrun NULL;
}
while (fast->_next != NULL)
{
fast = fast->_next;
slow = slow->_next;
}
return slow;
}
11.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每一个算法的时间复杂度&空间复杂度。(剑指offer第56题)
12.判断两个链表是否相交,若相交,求交点(假设链表不带环)
13.判断两个链表是否相交,若相交,求交点(假设链表带环)
14.复杂链表的复制,一个链表的每一个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,如今要求实现复制这个链表(剑指offer第26题)
15.求两个已排序链表中相同的数据。void UnionSet(ListNode* l1,ListNode* l2);
16.在已排序的链表中删除链表中重复的结点(剑指offer第57题)