Given a singly linked list L: L0→L1→…→Ln-1→Ln,
reorder it to: L0→Ln→L1→Ln-1→L2→Ln-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}
.
分析:先用快慢指针找到链表的中点,然后翻转链表后半部分,再和前半部分组合。需要注意的是把链表分成两半时,前半段的尾节点要置为NULL,翻转链表时也要把尾节点置为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) {
// IMPORTANT: Please reset any member data you declared, as
// the same Solution instance will be reused for each test case.
if(head == NULL || head->next == NULL)return;
ListNode *fastp = head, *lowp = head, *tail = NULL;
while(fastp != NULL && fastp->next != NULL)
{//利用快慢指针找到链表的中点
tail = lowp;
fastp = fastp->next->next;
lowp = lowp->next;
}
tail->next = NULL; //此时tail 指向前半段的结尾
reverseList(lowp);//翻转链表后半段
fastp = head;
tail = NULL;
while(fastp != NULL)
{
ListNode *tmp = lowp->next;
lowp->next = fastp->next;
fastp->next = lowp;
tail = lowp;
fastp = lowp->next;
lowp = tmp;
}
if(lowp != NULL)
tail->next = lowp;
}
void reverseList(ListNode* &head)
{//翻转链表
if(head == NULL || head->next == NULL)return;
ListNode *pre = head, *p = pre->next;
while(p != NULL)
{
ListNode *tmp = p->next;
p->next = pre;
pre = p;
p = tmp;
}
head->next = NULL;
head = pre;
}
};
9.19更新
--------------
思路:
很经典的题,考到了链表的很多特性和技巧,要迅速写对写好不容易。大致思路分三步:
(1) 找到链表后一半。
(2) 将后一半节点进行反转。
(3) 将后一半节点依次插入前一半。
(1) 找到链表后一半。
(2) 将后一半节点进行反转。
(3) 将后一半节点依次插入前一半。
解法实现时有不少要注意的地方:
I. 奇偶链表长度:
偶数长度:1->2->3->4
(1) 1->2->3<-4
| |
h1 h2
(2) 1->4->2->3
| |
h1 h2
奇数长度:1->2->3->4->5
(1) 1->2->3<-4<-5
| |
h1 h2
(2) 1->5->2->3<-4
| |
h1 h2
(3) 1->5->2->4->3
|
h1, h2
规律:如果链表长度为L,无论L是奇数还是偶数,都从第k = L/2+1 个节点起反转。
II 如何找到第k=L/2+1个节点?
可以扫描两遍,第一遍得到L,并计算k。而第二遍扫描找到节点k。而更好的方法则是采用快慢双指针的策略:f每次走两步,s每次走一步。当f到达最后一个节点(而不是NULL)时,s指向k。同样分奇偶长度来验证:
II 如何找到第k=L/2+1个节点?
可以扫描两遍,第一遍得到L,并计算k。而第二遍扫描找到节点k。而更好的方法则是采用快慢双指针的策略:f每次走两步,s每次走一步。当f到达最后一个节点(而不是NULL)时,s指向k。同样分奇偶长度来验证:
偶数长度:1->2->3->4
(1) 1->2->3->4->NULL
| |
s f
(2) 1->2->3->4->NULL
| |
s f
奇数长度:1->2->3->4->5
(1) 1->2->3->4->5->NULL
| |
s f
(2) 1->2->3->4->5->NULL
| |
s f
III 如何将后一半反转的链表节点依次插入前一半中?
(1) 用双指针left/right分别从左右两头开始向中间扫描。依次将right所指节点插入left节点后面。每次插入后,left需要移动两次。
(2) 终止条件:当right扫描到最后一个节点(right->next==NULL)时结束,而该节点就是原链表中的第k=L/2+1个节点。
由于步骤比较复杂,写的时候尽量分成子程序写。
class Solution {
public:
void reorderList(ListNode *head) {
ListNode *mid = findMid(head);
ListNode *right = reverseList(mid);
ListNode *left = head;
while(right && right->next) {
ListNode *target = right;
right = right->next;
target->next = left->next;
left->next = target;
left = left->next->next;
}
}
ListNode *findMid(ListNode *head) {
ListNode *fast = head;
ListNode *slow = head;
while(fast && fast->next) {
fast = fast->next;
if(fast->next) fast = fast->next;
slow = slow->next;
}
return slow;
}
ListNode *reverseList(ListNode *head) {
if(!head) return head;
ListNode *pre = head, *cur = head->next;
while(cur) {
ListNode *temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
head->next = NULL;
return pre;
}
};