链表面试题:
- 从尾到头打印单链表
- 删除一个无头单链表的非尾节点(不能遍历链表)
- 在无头单链表的一个节点前插入一个节点(不能遍历链表)
- 单链表实现约瑟夫环(JosephCircle)
- 逆置/反转单链表
- 单链表排序(冒泡排序&快速排序)
- 合并两个有序链表,合并后依然有序
- 查找单链表的中间节点,要求只能遍历一次链表
- 查找单链表的倒数第k个节点,要求只能遍历一次链表
- 删除链表的倒数第K个结点
- 判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算 每个算法的时间复杂度&空间复杂度。
- 判断两个链表是否相交,若相交,求交点。(假设链表不带环)
- 判断两个链表是否相交,若相交,求交点。(假设链表可能带环) 【升级版】
- 复杂链表的复制。一个链表的每个节点,有一个指向next指针指向 下一个节点,还有一个random指针指向这个链表中的一个随机节点 或者NULL,现在要求实现复制这个链表,返回复制后的新链表。
- 求两个已排序单链表中相同的数据。void UnionSet(Node* l1, Node* l2);
从尾到头打印单链表
问题分析:
方法一:利用两层循环完成 ,外循环用 end 指针控制边界,从最后一个节点指向的NULL开始走,每一次循环向前走一步;内循环完成输出任务,让内层循环的指针 cur 每次从链表的头指针开始不断往后走,直到指向 end 指针指向的前一个节点,完成对该节点数据域的打印工作
void SListPrintTailToHead(SListNode* pHead)
{
assert(pHead);
SListNode* end = NULL;
while (end != pHead) { //控制边界,每次从后往前走一步
SListNode* cur = pHead;
while (cur->_next != end) { //从头开始遍历,让cur指向给end的前一个节点
cur = cur->_next;
}
printf("%d ", cur->_data);
end = cur;
}
printf("\n");
}
方法二:递归打印,实质是从第一个节点开始在栈空间中不断向下建立函数栈帧,直到为最后一个节点建立完栈帧,然后从下往上不断销毁栈帧,这个过程中节点的遍历其实是从后向前的,所以可以利用递归完成逆序打印
void SListPrintTailToHeadR(SListNode* pHead)
{
if (pHead == NULL)
return;
SListPrintTailToHeadR(pHead->_next);
printf("%d ",pHead->_data);
}
删除一个无头单链表的非尾节点(不能遍历链表)
问题分析:题目中说的无头链表不是链表没头的意思,只是命题者没给你头指针而已。这个题目中直接删除 pos 指向的节点是无法实现的,需要做适当处理
void SListDelNonTailNode(SListNode* pos)
{
assert(pos && pos->_next != NULL); //pos不应该指向最后一个节点
SListNode* next = pos->_next; //pos指向的节点的下一个节点
DataType tmp = pos->_data; //进行数据交换
pos->_data = next->_data;
next->_data = tmp;
pos->_next = next->_next; //将pos指向的节点和next指向的节点的下一个节点连接起来
delete next;
next = NULL;
}
在无头单链表的一个节点前插入一个节点(不能遍历链表)
问题分析:同样题目中说的无头链表不是链表没头的意思,只是命题者没给你头指针而已。很明显在 pos 指向的节点前直接插入是无法实现的,需要做适当处理
void SListInsertFrontNode(SListNode* pos, DataType x)
{
assert(pos);
SListNode* newnode = BuySListNode(pos->_data); //给的数据是pos指针指向节点的
newnode->_next = pos->_next; //将新节点连到Pos指向的节点之后
pos->_next = newnode;
pos->_data = x; //将pos指针指向的节点的数据改为新节点的初值
}
单链表实现约瑟夫环(JosephCircle)
问题分析:约瑟夫问题(有时也称为约瑟夫斯置换,是一个出现在计算机科学和数学中的问题。在计算机编程的算法中,类似问题又称为约瑟夫环。又称“丢手绢问题”.)
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
SListNode* SListJosephCircle(SListNode* pHead, int k)
{
assert(pHead && k>0);
SListNode* end = pHead;
while (end->_next != NULL) { //最终让end指向给链表的最后一个节点
end = end->_next;
}
end->_next = pHead; //构成环
SListNode* cur = pHead;
while (cur->_next != cur){ //这层循环在环状链表只剩下一个节点时才不会进入
int count = k;
SListNode* prev = NULL;
while (--count) { //这层循环每次从刚被删除的节点的下一个节点开始找到下一个要删除的节点
prev = cur; //记录即将要被删除的节点的前一个节点
cur = cur->_next;
}
prev->_next = cur->_next;
delete cur;
cur = NULL;
cur = prev->_next; //更新下次寻找要删除的节点时的起点
}
return cur; //返回最终没被删除的那个幸运的节点
}
逆置/反转单链表
问题描述:
首先认识链表的逆转
方法一:三指针法,这种方法的逆转过程直接看下面的图,需要注意的是,整个逆转结束的标志是n2 = NULL而不是n3 = NULL
下图实现的是具有五个节点的链表的逆转,你再可以走走偶数个节点链表的逆转,完成下图中的过程后再对链表的头指针作个简单处理就好了
SListNode* SListReverseOne(SListNode* pHead)
{
assert(pHead);
if (pHead->_next == NULL) //只有一个节点
return pHead;
SListNode* n1, *n2,*n3;
n1 = pHead;
n2 = n1->_next;
n3 = n2->_next;
while (n2 != NULL) { //逆转结束的标志是n2 = NULL
n2->_next = n1;
n1 = n2;
n2 = n3;
if (n3 != NULL) //这个判断是要有的
n3 = n3->_next;
}
pHead->_next = NULL; //对头指针的处理
pHead = n1;
return pHead;
}
方法二:
SListNode* SListReverseTwo(SListNode* pHead)
{
if (pHead == NULL || pHead->_next == NULL)
return pHead;
SListNode* newhead = NULL;
SListNode* cur = pHead;
while (cur != NULL){
SListNode* prev = cur;
cur = cur->_next;
prev->_next = newhead;
newhead = prev;
}
return newhead;
}
单链表排序(冒泡排序&快速排序)