遇到一个面试题,大意是这样的:
希望对于链表 0,1,2,3,4,5,6
希望反转之后得到 0,6,1,5,2,4,3
对于链表 0,1,2,3,4,5, 反转之后得到 0,5,1,4,2,3
考虑O(n) 的时间复杂度和 O(1)的空间复杂度
思路
实际上很简单,只不过是反转链表的变式罢了
再回顾一下反转链表:
使用3个指针,一个指向前节点 pre, 一个指向当前节点cur, 一个指向下一节点next。
做以下循环:
- 获取 下一节点 next = cur.next
- 将当前节点反向 cur.next = pre
- 更新指针 pre = cur, cur = next
注意最后记得使头结点的next指向null, 否则会再头结点处留下一个环
代码如下:
LinkNode* reverse(LinkNode *phead){
if(phead == nullptr){
return nullptr;
}
LinkNode *pCur=phead->next, *pPre=phead, *pNext;
while (pCur!=nullptr){
pNext = pCur->next;
pCur->next = pPre;
pPre = pCur;
pCur = pNext;
}
phead->next = nullptr;
phead = pPre;
return phead;
}
回到这个问题, 实际只是把后半截的部分反向,然后再添加到前半截处
所以思路大概如下:
- 找到整个链表的中间节点
- 通过中间节点把链表一分为二,并把后半部分反向
- 合并两个子链表,把第二个反向的链表中的节点插入到第一个链表中
整个过程不会新创建链表或占用新的存储空间,总共遍历了 N + N/2 + N/2 也就是O(n)的时间复杂度
LinkNode* reverse2(LinkNode *phead){
if(phead == nullptr){
return nullptr;
}
LinkNode *pSlow = phead, *pFast = phead;
// 通过快慢指针,遍历1次找到中间节点
while(pFast->next!=nullptr && pFast->next->next != nullptr){
pSlow = pSlow->next;
pFast = pFast->next->next;
}
// 将整个链表一分为二并且将后半部分反向
LinkNode *pRev = pSlow->next, *pCur = phead, *pNext=pCur->next, *pRevNext;
// 一分为二
pSlow->next = nullptr;
// 反转后半部分
pRev = reverse(pRev); // 这个reverse实际就是前面的链表反转
// 合并两个链表
while(pRev!=nullptr && pNext!=nullptr){
pRevNext = pRev->next;
pNext = pCur->next;
// 因为插入了一个节点,所以pCur需要移动两步才是原本插入前的下一节点
pCur->next = pRev;
pCur = pCur->next;
pCur->next = pNext;
pCur = pCur->next;
pRev = pRevNext;
}
return phead;
}
最后结果:
prev link:--
node 0
node 5
node 1
node 4
node 2
node 3