单链表常见面试题(一)
2017-07-07 16:17
274 查看
1.比较顺序表和链表的优缺点,说说它们分别在什么场景下使用?
顺序表:类似于数组结构。是连续存储的。在读取的时候比较快。但是在插入和删除时移动的数据量比较大,非常麻烦。顺序表在开辟空间时在满的时候重新申请开辟大的空间,会存在空间浪费。
链表:是链式结构。对于插入和删除操作比较方便。在开辟空间时对单个节点申请空间,不会造成空间的浪费。
首先我们时间上来进行分析:
(1)对于顺序表。不论是静态的还是动态的,他们都是连续的存储空间,在读取上时间效率比较快,可以通过地址之间的运算来进行访问,但是在插入和删除操作会出现比较麻烦的负载操作。
(2)对于链表,因为他是链式存储。在我们需要的时候才在堆上开辟空间,对于插入查找的方式比较便携。但是对于遍历的话需要多次的空间跳转。
顺序表多用于遍历操作频繁时使用,链表多用于在删除插入多的时候使用。
2.从尾到头打印单链表
这个问题我们使用递归的思想来解决。从头结点开始,在打印头结点数据前先打印它前一个节点数据,以此类推,首先打印最后一个节点数据,调用结束返回上一层。
3.删除一个无头单链表的非尾节点
在这个问题上,要保证删除后前后节点依旧能够连接。但在单链表中,我们无法找到要删除的结点的前一个节点,但是我们可以找到下一个。因此交换当前要删除的结点和下一个节点的数据。再把前后链接关系写上,就可以来解决。
4.在无头单链表的一个节点前插入一个节点
在插入节点时,我们也要把插入后的前后链接关系写清楚。所以依旧用替换法。本来是插入到pos节点,但我们找到pos的next节点。交换数据。
5.单链表实现约瑟夫环
Node* JosephRing(Node* pList, int k)//5约瑟夫环
{
Node* cur = pList;
Node* next = NULL;
Node* tail = pList;
while (tail->next)
{
tail = tail->next;
}
tail->next = pList;
while (cur->next != cur)
{
int count = k;
while (--count)
{
cur = cur->next;
}
next = cur->next;
cur->data = next->data;
cur->next = next->next;
free(next);
next = NULL;
}
return cur;
}
6.逆置/反转单链表
void ReList(Node** pList)//6逆置反转单链表
{
if (*pList == NULL)
{
return 0;
}
Node* cur = *pList;
*pList = NULL;
while (cur)
{
Node* tmp = cur;
cur = cur->next;
PushFront(pList, tmp->data); //头插到pList中
}
}7单链表排序(冒泡排序&快速排序)
设置一个tail,用来表明已经有序的界限。每次把cur和next进行比较。若next->data 大于 cur->data,则交换两个值。继续走,直到遇到tail
void SortList(Node* pList)// 7冒泡排序
{
if (pList == NULL || pList->next == NULL)
{
return;
}
Node* tail = NULL;
while (tail != pList)//总体循环条件 一共排序多少次
{
Node* cur = pList;
Node* next = cur->next;
int flag = 0;
while (next != tail) //在每一次中的循环条件
{
if (cur->data > next->data)
{
int data = cur->data;
cur->data = next->data;
next->data = data;
flag = 1;
}
cur = next;
next = cur->next;
}
tail = next;
if (flag == 0) //用来优化冒泡排序,若flag值没有变化,则表明已经有序。直接return
{
return;
}
}
}
8合并两个有序链表,合并后依然有序
Node* Merge(Node* List1, Node* List2)//8合并两个有序单链表
{
if (List1 == NULL) //如果List1为空,则返回List2
{
return List2;
}
else if(List2 == NULL) //如果List2为空,则返回List1
{
return List1;
}
else
{
//找出新链表的头结点 此时List1和List2都不为空,比较两个链表第一个节点的值,新链表指向值较小的那个链表
Node* head = NULL;
if (List1->data > List2->data)
{
head = List2;
List2 = List2->next; //此时,新链表指向List2
}
else
{
head = List1;
List1 = List1->next; //此时,新链表指向List1
}
//尾插
Node* tail = head;
while (List1&&List2) //List1和List2都不为空
{
if (List1->data <List2->data) 选择List1和List2中指向节点中值较小的那个节点,链接到新链表中
{
tail->next= List1;
List1 = List1->next;
}
else
{
tail->next= List2;
List2 = List2->next;
}
tail = tail->next;
}
9.查找单链表的中间节点,要求只能遍历一次链表
这里我们使用快慢指针。快指针每次比慢指针多走一步。当fast走到最后时,slow当好指向是中间节点
。
当节点数为偶数个,slow指向中间后面的那个。
当节点数为奇数个,slow刚好指向最中间。
Node* FindMiddle(Node* pList)//9遍历一遍找中间数
{
if (pList == NULL)
{
return NULL;
}
Node* slow = pList;
Node* fast = pList;
while (fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}10.查找单链表的倒数第k个节点,要求只能遍历一次链表
求倒数第K个,同样使用快慢指针。不过让快指针先走K-1.步,此时再让快慢指针同时走。当快指针走到最后一个节点,慢指针刚好走到倒数第K个
Node* CountBackward(Node* pList, int k)//10找倒数第K个节点
{
Node* fast = pList;
Node* slow = pList;
while (--k) //让快指针先走K-1
{
fast = fast->next;
}
while (fast&&fast->next)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
顺序表:类似于数组结构。是连续存储的。在读取的时候比较快。但是在插入和删除时移动的数据量比较大,非常麻烦。顺序表在开辟空间时在满的时候重新申请开辟大的空间,会存在空间浪费。
链表:是链式结构。对于插入和删除操作比较方便。在开辟空间时对单个节点申请空间,不会造成空间的浪费。
首先我们时间上来进行分析:
(1)对于顺序表。不论是静态的还是动态的,他们都是连续的存储空间,在读取上时间效率比较快,可以通过地址之间的运算来进行访问,但是在插入和删除操作会出现比较麻烦的负载操作。
(2)对于链表,因为他是链式存储。在我们需要的时候才在堆上开辟空间,对于插入查找的方式比较便携。但是对于遍历的话需要多次的空间跳转。
顺序表多用于遍历操作频繁时使用,链表多用于在删除插入多的时候使用。
2.从尾到头打印单链表
这个问题我们使用递归的思想来解决。从头结点开始,在打印头结点数据前先打印它前一个节点数据,以此类推,首先打印最后一个节点数据,调用结束返回上一层。
void RePrintf(Node* pList)//2逆序打印 { if (pList) { RePrintf(pList->next); //递归调用 printf("%d->", pList->data); } }
3.删除一个无头单链表的非尾节点
在这个问题上,要保证删除后前后节点依旧能够连接。但在单链表中,我们无法找到要删除的结点的前一个节点,但是我们可以找到下一个。因此交换当前要删除的结点和下一个节点的数据。再把前后链接关系写上,就可以来解决。
我们在这里本来是删除cur,这里用next替换删除。
void EraseNontail(Node**pList, Node* pos)//删除无头单链表的非尾节点 { assert(pos); assert(*pList); Node* cur = pos->next; pos->data=cur->data ; pos->next = cur->next; free(cur); cur = NULL; }
4.在无头单链表的一个节点前插入一个节点
在插入节点时,我们也要把插入后的前后链接关系写清楚。所以依旧用替换法。本来是插入到pos节点,但我们找到pos的next节点。交换数据。
void InsertNonHead(Node**pList, Node* pos,DataType x)//4无头单链表前插入 { assert(pos); assert(*pList); Node* cur = BuyNode(x); Node* next = pos->next; DataType tmp = cur->data; cur->data = pos->data; pos->data = tmp; pos->next = cur; cur->next = next; }
5.单链表实现约瑟夫环
Node* JosephRing(Node* pList, int k)//5约瑟夫环
{
Node* cur = pList;
Node* next = NULL;
Node* tail = pList;
while (tail->next)
{
tail = tail->next;
}
tail->next = pList;
while (cur->next != cur)
{
int count = k;
while (--count)
{
cur = cur->next;
}
next = cur->next;
cur->data = next->data;
cur->next = next->next;
free(next);
next = NULL;
}
return cur;
}
6.逆置/反转单链表
void ReList(Node** pList)//6逆置反转单链表
{
if (*pList == NULL)
{
return 0;
}
Node* cur = *pList;
*pList = NULL;
while (cur)
{
Node* tmp = cur;
cur = cur->next;
PushFront(pList, tmp->data); //头插到pList中
}
}7单链表排序(冒泡排序&快速排序)
设置一个tail,用来表明已经有序的界限。每次把cur和next进行比较。若next->data 大于 cur->data,则交换两个值。继续走,直到遇到tail
void SortList(Node* pList)// 7冒泡排序
{
if (pList == NULL || pList->next == NULL)
{
return;
}
Node* tail = NULL;
while (tail != pList)//总体循环条件 一共排序多少次
{
Node* cur = pList;
Node* next = cur->next;
int flag = 0;
while (next != tail) //在每一次中的循环条件
{
if (cur->data > next->data)
{
int data = cur->data;
cur->data = next->data;
next->data = data;
flag = 1;
}
cur = next;
next = cur->next;
}
tail = next;
if (flag == 0) //用来优化冒泡排序,若flag值没有变化,则表明已经有序。直接return
{
return;
}
}
}
8合并两个有序链表,合并后依然有序
Node* Merge(Node* List1, Node* List2)//8合并两个有序单链表
{
if (List1 == NULL) //如果List1为空,则返回List2
{
return List2;
}
else if(List2 == NULL) //如果List2为空,则返回List1
{
return List1;
}
else
{
//找出新链表的头结点 此时List1和List2都不为空,比较两个链表第一个节点的值,新链表指向值较小的那个链表
Node* head = NULL;
if (List1->data > List2->data)
{
head = List2;
List2 = List2->next; //此时,新链表指向List2
}
else
{
head = List1;
List1 = List1->next; //此时,新链表指向List1
}
//尾插
Node* tail = head;
while (List1&&List2) //List1和List2都不为空
{
if (List1->data <List2->data) 选择List1和List2中指向节点中值较小的那个节点,链接到新链表中
{
tail->next= List1;
List1 = List1->next;
}
else
{
tail->next= List2;
List2 = List2->next;
}
tail = tail->next;
}
//while循环出来,可能是List1为空或者List2为空。把不为空的链表直接链接到新表的尾 if (List1) { tail->next = List1; } else { tail->next = List2; } return head; } }
9.查找单链表的中间节点,要求只能遍历一次链表
这里我们使用快慢指针。快指针每次比慢指针多走一步。当fast走到最后时,slow当好指向是中间节点
。
当节点数为偶数个,slow指向中间后面的那个。
当节点数为奇数个,slow刚好指向最中间。
Node* FindMiddle(Node* pList)//9遍历一遍找中间数
{
if (pList == NULL)
{
return NULL;
}
Node* slow = pList;
Node* fast = pList;
while (fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}10.查找单链表的倒数第k个节点,要求只能遍历一次链表
求倒数第K个,同样使用快慢指针。不过让快指针先走K-1.步,此时再让快慢指针同时走。当快指针走到最后一个节点,慢指针刚好走到倒数第K个
Node* CountBackward(Node* pList, int k)//10找倒数第K个节点
{
Node* fast = pList;
Node* slow = pList;
while (--k) //让快指针先走K-1
{
fast = fast->next;
}
while (fast&&fast->next)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
相关文章推荐
- /*****/单链表常见面试题
- C语言常见单链表面试题(2)
- 单向链表的基本操作(常见面试题详解)
- 【数据结构】链表的原理及与其相关的常见面试题总结
- C++实现链表常见面试题
- c++实现单向单链表及常见面试题
- 链表常见面试题
- 链表常见面试题一:基本问题
- 程序员常见面试题之合并两个升序排列的链表
- 链表常见面试题
- 数据结构学习笔记 --- 线性表 (一些常见的关于链表的算法和面试题)
- 常见链表面试题之从尾到头打印链表
- 常见的单链表面试题——进阶篇
- 常见面试题手写双向循环链表
- 数据结构之链表常见面试题
- 前端常见算法面试题之 - 从尾到头打印链表[JavaScript解法]
- 单链表常见面试题
- 常见的链表面试题大汇总:
- 几个常见的链表面试题<一>
- 有关链表的一些常见面试题