题目描述
Given a singly linked list L: L 0→L 1→…→L n-1→L n, reorder it to: L 0→L n →L 1→L n-1→L 2→L n-2→…
You must do this in-place without altering the nodes' values.
For example, Given{1,2,3,4}, reorder it to{1,4,2,3}.
思考
拿到本题,第一想法就是一个一个的从后面往前面插入不就行了,但这需要我们多次遍历链表,显然这是很暴力的一种做法,那比较好的做法应该如何呢?仔细观察题目要求,本质上就是从前面拿一个,再从最后面拿一个,再从前面拿一个......所以可以想到一种解题方法:将链表从中间对半拆开,分为两个链表,并把后半段链表反序,最后按照要求合并为一个链表即可。将链表从中间拆开可以采用常用的快慢指针方法,也即定义两个指针p、q,p步进长度为1,q步进长度为2,当q到达链表末端时,p即为链表中间元素。
首先实现一下快慢指针(其实我们在《LeetCode(1):sort-list》中就已经用过了,但稍微有点不同):
ListNode* p = head;
ListNode* q = head;
while(q->next != NULL && q->next->next != NULL){
p = p->next;
q = q->next->next;
}
我们要知道的是,在LeetCode中如果没有特别声明,给出的head指针直接指向第一个节点,并没有头节点H,换句话说head->val就是第一个节点的值。
在上面这段代码中,我们首先对快慢指针进行初始化,让p、q均等于head(也即指向第一个节点),然后当q->next和q->next->next均不为空的时候,对二者进行移动。举个例子,假设我们的链表是A->B->C->NULL,一开始p->A,q->A,且q->next != NULL,q->next->next != NULL,所以p->B,q->C,此时q->next为NULL,不满足要求,跳出循环,中间节点为B。再举个例子,假设我们的链表是A->B->NULL,开始的时候p->A,q->A,此时有q->next->next = NULL,所以跳出循环,返回中间节点A,当然,我们在这种偶数节点的情况下,也可以取A和B的均值,但本文采用前一种方法。
实际上快慢指针也可以这样写:
ListNode* p = head;
ListNode* q = head->next;
while(q != NULL && q->next != NULL){
p = p->next;
q = q->next->next;
}
举例说明,假设我们的链表是A->B->C->NULL,一开始p->A,q->B,且满足循环条件,所以p->B,q->NULL,跳出循环,即B为中间节点;假设我们的链表是A->B->NULL,则有p->A,q->B,此时q->next = NULL,所以直接跳出循环,A为中间节点。因为我们并不关心q的值,所以q的起始值是可以选为head或者head->next的,这将影响循环的判断条件。q = head->next加上q != NULL && q->next != NULL的判断条件就相当于变成了q = head时q->next != NULL && q->next->next != NULL。
关于链表的反转,可以参考博文《链表翻转的图文详解(递归与迭代两种实现)》。
给出代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
void reorderList(ListNode *head) {
if(head == NULL || head->next == NULL || head->next->next == NULL){
return;
}
ListNode* p = head;
ListNode* q = head;
while(q->next != NULL && q->next->next != NULL){
p = p->next;
q = q->next->next;
}
ListNode* newH = NULL;
ListNode* pr = p->next;
while(pr != NULL){
ListNode* tmp = pr->next;
pr->next = newH;
newH = pr;
pr = tmp;
}
p->next = NULL;
merge(head, newH);
}
void merge(ListNode* head, ListNode* newH){
ListNode* p = head;
ListNode* q = newH;
while(p != NULL && q != NULL){
ListNode* pr = p->next;
ListNode* qr = q->next;
p->next = q;
q->next = pr;
p = pr;
q = qr;
}
}
};
我想,本文最需要注意的一点就是,在没有特别声明的情况下,LeetCode中是没有设置头节点的。为什么一般情况下我们要设置头节点?为了保持在链表第一个节点之前插入时的操作与后面的操作能保持一致,比如,A->B->C->NULL,在B与C之间插入T的操作是T->next = C,B->next = T,而在A前面插入T的操作只有T->next = A;而如果我们引入头节点H,则链表变为H->A->B->C->NULL,此时在A之前插入T的操作为T->next = A,H->next = T,与在后面插入一致。
好了,本文就到这里结束,希望对大家有所帮助。