这两天笔试被反转链表折磨得不轻,明明在Leetcode上做反转链表时已经能够一分钟内BUGfree了,但是题目稍微变一点又变成憨憨了,究其原因是一些细节没有做到位,遂写文总结下。
最基础的反转链表
解法:head指针指向链表的第一个元素;tail指针,或者便于一般性反转过程的pre指针,则指向最后一个元素的下一个元素(这里的NULL)。然后就是一套经典组合拳了:
ListNode* ReverseList(ListNode *head) {
ListNode *pre = NULL, *cur = head;
while(cur) {
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
这里需要注意的点是:什么时候结束反转,以及结束后,哪个指针存储着新链表的头?
在这一套传统艺能中,执行完一轮循环体内容,pre和cur间是没有相连关系的,当cur等于NULL时,pre指向着最后一个结点,此时pre就是新链表的头指针。
稍微进阶:反转部分链表
现在来沿用最基本的反转链表中的思想:left指针指向需要反转的链表头,而right指针指向需要反转的链表的最后一个元素的下一个。
相比于上一题,这题需要注意的点是,链表反转完你得接回去啊!!right记录着反转链表的下一个了,因此这里需要添加一个指针,这个指针的下一个节点是反转链表的头。
还有一点,上一题反转后直接返回pre就是头结点了,在这里,要是一般情况返回个head也OK了。但特殊值,head结点可能是反转链表的头结点,这时它就不在head的位置了!于是通用的做法是定义一个哑结点dummyHead,dummyHead->next = head。返回时返回dummyHead->next。
ListNode *Reverse(ListNode *head, ListNode *tail) {
ListNode *pre = tail, *cur = head;
while(cur != tail) {
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
//重点:哑结点,解决头指针被反转的特殊情况
ListNode *dummyHead = new ListNode(-1, head);
//声明两个指针,用来保存反转的区间
ListNode *l = dummyHead, *r = head;
//注意到题目定义第一个结点的下标为1,于是声明一个临时下标,初始值为1
int i = 1;
//首先找到需要反转链表的头结点的前一个
while(i != left) {
l = l->next;
++i; //不要随便把++写进判断语句里
}
//此时反转链表头指针的位置是l->next,用同样的方法寻找右指针
//由于右指针是指向最后一个元素的下一个元素,因此判断条件为i == right + 2
r = l;
while(i++ != right + 2) {
r = r->next;
}
//拼接
l->next = Reverse(l->next, r);
return dummyHead->next;
}
再进阶:K个一组反转链表
其实也没什么进阶的,就是多反转几次而已。
总结
- 记住自己熟悉的一套反转模板,我是喜欢左闭右开,那么不管是原始的反转还是附加条件的反转,我都写好一个反转函数,接下来要做的就是找到传入的参数。
- 哑结点!永远不要忽略头指针作为反转链表一部分的情况。