题目来源:csdn博主帅地,原文链接:https://blog.csdn.net/m0_37907797/article/details/103063236
网上已有java的实现版本,其实原理类似,通过自己的学习和理解,现提供C++版本供大家参考(指正),希望对即将面试或准备换工作的你,提供一点点帮助。
题目
这其实是一道变形的链表反转题,大致描述如下:
给定一个单链表的头节点 head,实现一个调整单链表的函数,使得每K个节点之间为一组进行逆序,并且从链表的尾部开始组起,头部剩余节点数量不够一组的不需要逆序。(不能使用队列或者栈作为辅助) 例如: 链表:1->2->3->4->5->6->7->8->null, K = 3。那么 6->7->8,3->4->5,1->2各位一组。调整后:1->2->5->4->3->8->7->6->null。其中 1,2不调整,因为不够一组。
分析
这道题咋一看,挺简单的,就是单链表分段逆序问题。正常情况下,遍历链表都是从头结点开始,但题中指定从尾部开始分组,而单链表是没法反向遍历的。所以,把此题分解成几个子问题,理解起来就简单了,分解后如下:
- 对单链表逆序,由尾部——头部遍历;
- 正向分段逆序;
- 对逆序结果再次逆序,还原原始顺序;
这里先实现2个功能作为备用:链表逆序和分段逆序。
链表逆序
这里用递归来实现,应用求解子问题
的思路,来拆分实现。
(1)拆分链表。把f(n)拆分成f(n-1) + 1
(2)求解f(n-1)。
(3)将结点1
链接道f(n-1)尾部。
用代码实现为:
// 逆序单链表
Node* InverseTab(Node *pHead)
{
if (pHead->next == NULL) return pHead;
Node *pNewHead = InverseTab(pHead->next);
// 把头部链到表尾,并将next置空
pHead->next->next = pHead;
pHead->next = NULL;
return pNewHead;
}
分段逆序
分段的过程,可以顺序遍历求解,也可以应用递归求解,前者属于常规方法,再次不再赘述。以下描述的是递归方式:
(1)将链接拆分为2部分。一段长度为m的链表和一段长度为n-m的链表。
(2)前半段直接调用InverseTab
,后半段按子问题求解。
用代码实现为:
// 从头部开始,分段逆序
Node* SectionInverseFromHead(int cLen, Node *pHead)
{
if (cLen <= 1 || pHead == NULL) return pHead;
Node *pcHead = NULL; // 子链表的头部
Node *pTemp = pHead;
for (int i = 1; i < cLen; i++)
{
if (pTemp->next == NULL)
{
// cLen大于表长,不作逆序
return pHead;
}
pTemp = pTemp->next;
}
// 后半段逆序
pcHead = pTemp->next;
pcHead = SectionInverseFromHead(cLen, pcHead);
// 前半段逆序
pTemp->next = NULL;
Node *pNewHead = InverseTab(pHead);
// 连起来
pHead->next = pcHead;
return pNewHead;
}
解决问题
回到原问题,可分解为:逆序 —— 分段逆序 —— 逆序。这样看,问题便迎刃而解了,直接上代码:
// 从尾部开始,分段逆序
Node* SectionInverseFromTail(int cLen, Node *pHead)
{
pHead = InverseTab(pHead);
pHead = SectionInverseFromHead(cLen, pHead);
pHead = InverseTab(pHead);
return pHead;
}