1.指定范围的反转
1.1 题目
将一个链表m位置到n位置之间的区间反转,要求使用原地算法,并且在一次扫描之内完成反转。
例如:给出的链表为1->2->3->4->5->NULL, m = 2 ,n = 4, 返回1->4->3->2->5->NULL.
注意: 给出的m,n满足以下条件: 1 ≤ m ≤ n ≤ 链表长度
下面是C/C++语法实现
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *reverseBetween(ListNode *head, int m, int n) {
}
};
题目来源:
https://www.nowcoder.com/practice/b58434e200a648c589ca2063f1faf58c?tpId=46&tqId=29086&tPage=3&rp=3&ru=/ta/leetcode&qru=/ta/leetcode/question-ranking
1.2 题解
class Solution {
public:
ListNode *reverseBetween(ListNode *head, int m, int n) {
ListNode *dummy = (ListNode *)malloc(sizeof(ListNode));
dummy->next = *head;
ListNode* preStart = dummy->next; // 注意:preStart必须指向head
ListNode* start = (*head)->next; // 这里的head不是首元素,而是头结点
for(int i = 1; i < m; ++i){ // 执行后,start执行索引为m的节点
preStart = start; // preStart 指向start前一个节点
start = start->next;
}
for(int i = 0; i < (n-m); ++i){ // 每一次循环,把start后一节点放在m位置
LinkList tmp = start->next; // 循环过程中,start指向始终不变
start->next = tmp->next;
tmp->next = preStart->next;
preStart->next = tmp;
}
return dummy->next;
}
}
};
备注:这里的题解没有验证m和n范围的合法性/有效性,现实中肯定要考虑进去。
1.3 题析与测试用例
理解该方法的一个很好类别:
不妨拿出四本书,摞成一摞(自上而下为 A B C D),要让这四本书的位置完全颠倒过来(即自上而下为 D C B A):
盯住书A,每次操作把A下面的那本书放到最上面
初始位置:自上而下为 A B C D
第一次操作后:自上而下为 B A C D
第二次操作后:自上而下为 C B A D
第三次操作后:自上而下为 D C B A
来源:https://www.nowcoder.com/questionTerminal/b58434e200a648c589ca2063f1faf58c?f=discussion
以我的第2篇博客-链表基础(https://blog.csdn.net/qq_42800075/article/details/105270136)的单链表代码为例:
LinkList reverseBetween(LinkList* head, int m, int n){
LinkList dummy = (LinkList)malloc(sizeof(SingleLinkedList));
dummy->next = *head;
LinkList preStart = dummy->next;; // 意:preStart必须指向head,
LinkList start = (*head)->next; // 这里的head不是首元素,而是头结点
for(int i = 1; i < m; ++i){ // 执行后,start执行索引m的节点
preStart = start; // preStart 指向start前一个节点
start = start->next;
}
for(int i = 0; i < (n-m); ++i){ // 每一次循环,把start后一节点放在m位置
LinkList tmp = start->next; // 循环过程中,start指向始终不变
start->next = tmp->next;
tmp->next = preStart->next;
preStart->next = tmp;
}
return dummy->next;
}
在main.c的删除操作后面新增一行,如下所示
运行结果
该方法的时间复杂度为O(n),空间复杂度为O(1)
这种链表反转类似汉诺塔的堆叠,每一次操作(循环)的结果都把次大的堆叠在下面。
由此循环即可完成汉诺塔的移动或链表的反转。
2.链表全反转
2.1 题目
题目:定义一个函数,输入一个链表的头结点,反转该链表并输出反转后链表的头结点。链表节点定义如下
struct ListNode
{
int m_nKey;
ListNode* m_pNext;
}
2.2 题解
ListNode* ReverseList(ListNode* pHead)
{
ListNode* pReversedHead = nullptr;
ListNode* pPrev = nullptr;
while(pNode != nullptr)
{
ListNode* pNext = pNode->m_pNext;
if(pNext == nullptr)
pReversedHead = pNode;
pNode->m_pNext = pPrev;
pPrev = pNode;
pNode = pNext;
}
return pReversedHead;
}
2.3 题析与测试用例
上述题解对应的测试用例可参考如下链接:
https://github.com/zhedahht/CodingInterviewChinese2/edit/master/24_ReverseList/ReverseList.cpp
这里再贴出对应我上篇博客单链表实现对应的反转链表函数,如下
// 链表全反转
LinkList reverseAll(LinkList * head)
{
//备注:LinkList 是 SingleLinkedList *
LinkList dummy = (LinkList)malloc(sizeof(SingleLinkedList));
dummy->next = *head;
LinkList start = dummy->next->next; //注意这里!因为head是头结点,不是首元素!
LinkList preStart = NULL;
while(start != NULL){
LinkList nextStart = start->next;
if(nextStart == NULL)
dummy->next->next = start; // 注意这里,是next->next
start->next = preStart;
preStart = start;
start = nextStart;
}
return dummy->next;
}
测试结果
思路分析:相比指定范围的反转,思路是不同的。
指定范围的反转:每一次循环更新m位置后一节点到m位置,如此循环(n-m)次即可反转[m,n]范围的元素指向
全反转:三个指针分别记录当前节点,前一个节点,后一节点。每一次循环将当前节点指向前一节点。很明显,有
多少个元素就需反转多少次,即时间复杂度是O(n),空间复杂度O(1).
代码写完就完事啦???
其实不然,最后且最重要一点:一定要用几个“测试用例”验证。如
1)输入的链表头指针是nullptr/NULL
2)输入的链表只有一个节点
3)输入的链表有多个节点
总结一下:玩链表就是玩指针,玩数组就是玩下标(谱哥的精辟总结<可关注微信公众号“编程剑谱”>)。
同时,链表问题尤其需注意以下三个问题:
1)输入的链表头指针为nullptr或整个链表只有一个节点时,程序立即崩溃
2)反转后的链表出现断裂
3)返回的反转之后的头节点不是原始链表的尾节点
下一篇:继续链表练习…