Reorder List
Problem:
Given a singly linked list L: L0 → L1 → ... → Ln-1 → Ln.
reorder it to: L0 → Ln → L1 → Ln-1 → ...
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}.
分析:
这个题目第一种做法是递归方式。每次只管头和尾的连接,然后递归做剩下的n-2个元素,又成了同样的问题。在这个算法中每次找尾部的元素都需要花费n的时间,而一共需要递归调用n/2次,所以算法的时间复杂度是O(n2)的。代码可以写得很简单,但AC不过。
1 class Solution { 2 public: 3 void reorderList(ListNode *head) { 4 // none or just one 5 if(head == NULL || head->next == NULL) 6 return; 7 8 // more than one, adjust the first and the last 9 ListNode *pre = head, *tail = head->next; 10 while(tail->next != NULL) { 11 pre = pre->next; 12 tail = tail->next; 13 } 14 pre->next = NULL; 15 tail->next = head->next; 16 head->next = tail; 17 18 // do it recursively 19 reorderList(tail->next); 20 } 21 };
第二种做法是将后一半的链表原地反转,然后再逐个遍历拼接到前一半的链表上。这种做法能够在O(n)的时间复杂度内完成。
寻找后一半的链表的算法在链表式的归并排序中也用过,思路是用快慢指针,一个一次走一步,另一个一次走两步,当走得快的到达末尾时,慢的也走到了中间。
链表原地反转的算法也很经典问题,三个指针,一个维护头部元素的指针(head2),一个维护新链表尾部的指针(tail),另外一个是将要插入到链表头部元素的指针(cur)。顺序从第二个元素开始,依次向后取每个元素,将其插入链表的头部。时间复杂度O(n)。
拼接两个链表的算法比较像归并两个集合的过程,只不过这里没有比较的过程,直接往后面接第二个链表。
1 class Solution { 2 public: 3 void reorderList(ListNode *head) { 4 // less than two elements 5 if(head == NULL || head->next == NULL || head->next->next == NULL) 6 return; 7 8 // find the second half 9 ListNode *slow, *fast; 10 slow = fast = head; 11 while(fast->next != NULL && fast->next->next != NULL) { 12 slow = slow->next; 13 fast = fast->next; 14 fast = fast->next; 15 } 16 17 ListNode *head2 = slow->next; 18 slow->next = NULL; 19 20 // reverse the second half 21 ListNode *tail = head2; 22 while(tail != NULL && tail->next != NULL) { 23 ListNode *cur = tail->next; 24 tail->next = cur->next; 25 cur->next = head2; 26 head2 = cur; 27 } 28 29 // merge two parts 30 ListNode *head1 = head; 31 while(head1 != NULL && head2 != NULL) { 32 ListNode *p = head1, *q = head2; 33 head1 = head1->next; 34 head2 = head2->next; 35 q->next = p->next; 36 p->next = q; 37 } 38 } 39 };