单链表热点面试题
单链表结构 (注:有些题需要用到单链表基本实现中的函数,本文不再重新实现)
typedef int DataType;
typedef struct ListNode
{
DataType _data; //数据
struct ListNode * _next; //指向下一个节点的指针
}ListNode;
1.比较顺序表和单链表的优缺点
1)顺序表支持随机访问,单链表不支持。
2)顺序表插入/删除数据效率很低,时间复杂度为O(N)(除尾插、尾删)
单链表插入/删除数据效率高,时间复杂度为O(1)。
3)顺序表的CPU高速缓存效率更高,单链表的CPU高速缓存效率更低。
2.从尾到头打印单链表
void PrintTailToHead(ListNode * pHead)
{
if (pHead)
{
PrintTailToHead(pHead->_next);
printf("%d <- ", pHead->_data);
}
}
3.删除一个无头单链表的非尾节点
void DelNonTailNode(ListNode * pos)
{
assert(pos && pos->_next);
pos->_data = pos->_next->_data;
ListNode * next = pos->_next->_next;
free(pos->_next);
pos->_next = next;
}
解析:由于没有头指针,无法遍历单链表删除节点,所以可以换一种思想。将pos的下一个节点的数据赋给pos节点,令pos指向下一个的下一个,这样就可以转换为删除pos的下一个节点。
4.在无头单链表的一个非头结点前插入一个节点
void InsertFrontNode(ListNode * pos, DataType x)
{
assert(pos);
ListNode * tmp = BuyNode(x);
tmp->_next = pos->_next;
pos->_next = tmp;
DataType tmpData = tmp->_data;
tmp->_data = pos->_data;
pos->_data = tmpData;
}
解析:与第三题类似,可以在pos节点后插入一个新节点,将pos与新节点的数据值交换一下,就实现了在pos前插入节点了。
5.单链表实现约瑟夫环
void JosephCycle(ListNode * &pHead, int m)
{
ListNode* cur = pHead;
if (cur == NULL)
{
return ;
}
while (1)
{
if (cur == cur->_next) //只剩下一个节点
{
return ;
}
int x = m;
while (--x)
{
cur = cur->_next;
//替换法进行删除
cur->_data = cur->_next->_data;
ListNode* del = cur->_next;
cur->_next = del->_next;
free(del);
}
}
}
6.逆置/反转单链表
void Reverse(ListNode * &pHead)
{
ListNode * cur = pHead;
ListNode * newHead = NULL;
while (cur)
{
ListNode * tmp = cur;
cur = cur->_next;
tmp->_next = newHead;
newHead = tmp;
}
pHead = newHead;
}
解析:cur指针用来遍历单链表,同时用tmp指针指向cur节点,将cur向后指,此时可类似于摘下tmp节点,令tmp的下一个节点是newHead,以此类推,最后,newHead即为逆置后的单链表。
7.单链表排序(冒泡)
void SortList(ListNode *pHead)
{
if (pHead == NULL || pHead->_next == NULL) //为空或有一个节点
{
return;
}
ListNode * cur = pHead->_next;
ListNode * prev = pHead;
ListNode * tail = NULL;
while (tail != pHead)
{
prev = pHead;
cur = pHead->_next;
int exchange = 0;
while (cur != tail)
{
if (prev->_data > cur->_data) //前面的数据大于后面的数据,交换值
{
DataType tmp = prev->_data;
prev->_data = cur->_data;
cur->_data = tmp;
exchange = 1;
}
prev = cur;
cur = cur->_next;
}
if (exchange == 0)
{
return;
}
tail = prev;
}
}
8.合并两个有序链表,合并后依然有序
ListNode * MergeList(ListNode * pHead1, ListNode * pHead2)
{
if (pHead1 == NULL)
{
return pHead2;
}
if (pHead2 == NULL)
{
return pHead1;
}
ListNode* newHead;
ListNode* tail;
ListNode* cur1 = pHead1;
ListNode* cur2 = pHead2;
if (cur1->_data < cur2->_data)
{
newHead = cur1;
cur1 = cur1->_next;
}
else
{
newHead = cur2;
cur2 = cur2->_next;
}
tail = newHead;
while (cur1 && cur2)
{
if (cur1->_data < cur2->_data)
{
tail->_next = cur1;
cur1 = cur1->_next;
}
else
{
tail->_next = cur2;
cur1 = cur2->_next;
}
tail = tail->_next;
}
if (cur1)
{
tail->_next = cur1;
}
if (cur2)
{
tail->_next = cur2;
}
return newHead;
}
解析:由于两个单链表都已经有序,只需要先将两个链表的第一个数据比较大小,加入新链表,接下来将两个链表中的数据依次比较大小,依次放入新链表即可。
9.查找单链表的中间节点,要求只遍历一次
ListNode * FindMid(ListNode *pHead)
{
ListNode * slow = pHead, *fast = pHead;
while (fast && fast->_next)
{
slow = slow->_next;
fast = fast->_next->_next;
}
return slow;
}
解析:查找中间节点,可以利用快慢指针的思想,快指针走两步,慢指针走一步,这样,当快指针走到尾节点时,慢指针刚好在链表的中间。
10.查找单链表的倒数第K个节点,要求只能遍历一次
ListNode * FindKTailNode(ListNode * pHead, int k)
{
ListNode * slow = pHead, *fast = pHead;
while (k--)
{
if (fast == NULL) //没有k这么长,返回空
{
return NULL;
}
fast = fast->_next; //fast先走k步
}
while (fast)
{
slow = slow->_next;
fast = fast->_next;
}
return slow;
}
解析:依然利用快慢指针,先令fast走k步,在令slow与fast同时向后走,则当fast走到尾时,slow为倒数第二个节点。
11.判断单链表是否带环,求长度、入口点
解析:要判断单链表是否带环,利用快慢指针,快指针走两步,慢指针走一步,若最后会相遇,则带环,否则不带环。
bool HasCycle(ListNode *pHead)
{
ListNode * slow = pHead, *fast = pHead;
while (fast && fast->_next)
{
fast = fast->_next->_next;
slow = slow->_next;
if (slow == fast)
{
return true;
}
}
return false;
}
ListNode * MeetNode(ListNode *pHead) //找到快慢指针的相遇点
{
ListNode * slow = pHead, *fast = pHead;
while (fast && fast->_next)
{
fast = fast->_next->_next;
slow = slow->_next;
if (slow == fast)
return slow;
}
return NULL;
}
int GetCycleLength(ListNode * meetNode) //求环的长度
{
ListNode * slow = meetNode->_next, *fast = meetNode->_next->_next;
int count = 1;
while (slow != fast)
{
slow = slow->_next;
fast = fast->_next->_next;
count++;
}
return count;
}
ListNode * GetEntryNode(ListNode *pHead, ListNode * meetNode) //求入口点
{
while (pHead && meetNode)
{
if (pHead == meetNode)
{
return pHead;
}
pHead = pHead->_next;
meetNode = meetNode->_next;
}
return NULL;
}
12.判断两个链表是否相交,求交点(假设链表不带环)
解析:两个链表,如果他们相交的话,那么他们最后的一个节点一定是相同的,否则是不相交的。因此判断两个链表是否相交就很简单了,分别遍历到两个链表的尾部,然后判断他们是否相同,如果相同,则相交;否则不相交。
bool CheckCross(ListNode * l1, ListNode * l2)
{
ListNode * tail1 = l1, *tail2 = l2;
while (tail1->_next != NULL)
{
tail1 = tail1->_next;
}
while (tail2->_next != NULL)
{
tail2 = tail2->_next;
}
if (tail1 == tail2)
{
return true;
}
return false;
}
ListNode *CrossNode(ListNode * l1, ListNode * l2) //求交点
{
ListNode * cur1 = l1, *cur2 = l2;
ListNode * tail1 = l1, *tail2 = l2;
int len1 = 0, len2 = 0;
while (tail1->_next != NULL) //分别求出单链表长度
{
len1++;
tail1 = tail1->_next;
}
while (tail2->_next != NULL)
{
len2++;
tail2 = tail2->_next;
}
int x;
if (len1 > len2) //若l1长,先走x步
{
x = len1 - len2;
while (x--)
{
cur1 = cur1->_next;
}
}
else //若l2长,先走x步
{
x = len2 - len1;
while (x--)
{
cur2 = cur2->_next;
}
}
while (cur1 != cur2)
{
cur1 = cur1->_next;
cur2 = cur2->_next;
}
return cur1;
}
13.判断两个链表是否相交,求交点(假设链表带环)
bool CheckCross1(ListNode * l1, ListNode * l2)
{
ListNode * meetNode1 = MeetNode(l1);
ListNode * meetNode2 = MeetNode(l2);
if (meetNode1 == NULL && meetNode2 == NULL) //不带环
{
ListNode * tail1 = l1, *tail2 = l2;
while (tail1->_next != NULL)
{
tail1 = tail1->_next;
}
while (tail2->_next != NULL)
{
tail2 = tail2->_next;
}
if (tail1 == tail2)
return true;
else
return false;
}
if (meetNode1 && meetNode2) //带环
{
if(meetNode1 == meetNode2)
{
return true;
}
ListNode * cur = meetNode1->_next;
while (cur != meetNode1)
{
if (cur == meetNode2)
return true;
cur = cur->_next;
}
return false;
}
return false;
}
ListNode * CrossNode1(ListNode * l1, ListNode * l2) //求交点
{
ListNode * meetNode1 = MeetNode(l1);
ListNode * meetNode2 = MeetNode(l2);
ListNode * entryNode1 = GetEntryNode(l1, meetNode1);
ListNode * entryNode2 = GetEntryNode(l2, meetNode2);
if (entryNode1 == entryNode2) //入口点一样,说明在入环之前就相交
{
entryNode1 = NULL;
ListNode * crossNode = CrossNode(l1, l2);
return crossNode;
}
else //入口点不同,说明在环内相交(应有两个交点,现在假设l2交l1)
{
return entryNode2;
}
}
14.求两个已排序链表中相同的数据
void UnionSet(ListNode* l1, ListNode* l2)
{
while (l1 && l2)
{
if (l1->_data == l2->_data)
{
printf("%d ", l1->_data);
l1 = l1->_next;
l2 = l2->_next;
}
else if (l1->_data < l2->_data)
{
l1 = l1->_next;
}
else
{
l2 = l2->_next;
}
}
}