给定一个单链表 L 的头节点 head ,单链表 L 表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
输入: head = [1,2,3,4] 输出: [1,4,2,3]
提示:
- 链表的长度范围为
[1, 5 * 104]
1 <= node.val <= 1000
【思路】
1,由题目要求,第n个结点插到第一个结点后,第n-1个结点插入第2个结点后...,可见重排后的链表末端是原先的最中间的结点。可将链表后半段(代码中后半段指针为L2)逆置后逐个插入前半段(L1)
2,链表无法随机存取,其逆置应当找到链表中间的结点和最后的结点,对后半段的每个结点,依次插入到最末尾结点的后面(头插法建立逆序链表)。具体实现使用快慢指针,且为了保证end指针最后不为空,我们让它每次只后移一位,且它独自后移了一次后,慢指针和快指针同时后移一次。使用move记录慢指针当前是否可以移动。
具体实现:若move=0,即不是慢指针移动的轮次,快指针end先后移一次,令记录变量move=1。然后由于move=1,慢指针middle与快指针end同时后移,再令move=0,直到end下一位为空,此时end指向最后一个结点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
void reorderList(struct ListNode* head){
struct ListNode* end;
struct ListNode* middle;//用于找中间结点和末尾结点的快慢指针
struct ListNode* l1;
struct ListNode* l2;//用于最后一步合并前后半段的两个指针
struct ListNode* p;//交换节点时用的临时指针
struct ListNode* end2;
int move=0;
end=head;
middle=head;
if(!head->next) return;
while(end->next){
if(move==0) {
end=end->next;
move=1;
}else{
middle=middle->next;
end=end->next;
move=0;
}
}//while 找到L[n/2]
l2=middle->next;
middle->next=NULL;//一定要把后半段彻底【摘下来】,敲代码时缺了这步导致进入死循环。。。
end->next=NULL;
while(l2!=end){
p=l2;
l2=l2->next;
p->next=end->next;
end->next=p;
}//while 逆置后半截
// middle->next=l2; //测试逆置代码段是否有问题的语句
l1=head;
while(l2){
p=l2;
l2=l2->next;
p->next=l1->next;
l1->next=p;
l1=p->next;
}//while 将逆置过的后半截链表l2插入l1
}
思路不是很难想(只要知道逆置链表这个操作),代码也不是多复杂,但是没有将middle的next置为空导致超时,还排查了好久。。。