原题链接: https://leetcode.com/problems/reorder-list/
1. 题目介绍
Given a singly linked list L: L0→L1→…→Ln-1→Ln,
reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→…
You may not modify the values in the list’s nodes, only nodes itself may be changed.
给出一个单项链表,L0→L1→…→Ln-1→Ln ,需要将其重新排列顺序,变成: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不可以改变链表的节点的val,只能改变他们的连接关系。
Example 1:
Given 1->2->3->4, reorder it to 1->4->2->3.
Example 2:
Given 1->2->3->4->5, reorder it to 1->5->2->4->3.
2. 解题思路
这道题目真是一道好题,属于链表练习的综合题。
这道题涉及三种针对链表的操作:
- 如何从中间将链表一分为二?
- 如何反转一个链表?
- 如何拼接两个链表?
这三个问题如果单独拿出来考察,都可以出三个题目了,事实上LeetCode中还真有对应的题目。
- 如何从中间将链表一分为二?
------使用快慢双指针法,该方法在 141. Linked List Cycle 、 142. Linked List Cycle II 和 109. Convert Sorted List to Binary Search Tree 中都使用过。 - 如何反转一个链表?
------反转链表使用的是 206. Reverse Linked List 中反转链表的方法 - 如何拼接两个链表?
------使用了和 21. Merge Two Sorted Lists 和 2. Add Two Numbers 中类似的合并链表的方法
因此强烈建议先将上述题目做完后,再研究本题。
注: 接下来的内容参考了 https://www.cnblogs.com/love-yh/p/7017991.html ,该文讲解得非常好,我用的图片也是取自 https://www.cnblogs.com/love-yh/p/7017991.html 。有兴趣的读者可以直接去看原文。
反转以后的链表,从左往右是: L0→Ln→L1→Ln-1→L2→Ln-2→… ,首先想到是将链表 L 按照这个规律重新赋值一遍,但题中说不能改变结点的值;然后想到将链表反转然后重新连接起来,如1->2->3->4:
按虚线那样走,形成1->4->2->3->3->2->4->1。如果这样做,前面的1->4->2->3就是我们想得到的结果。也就是需要将得到的链表分成相等的两段,取前一段。这样做需要统计链表节点的总数,比较麻烦。我们不如在一开始就将链表一分为二,然后反转后半段,最后重新连接起来,依旧是1->2->3->4。
这样思路就出来了,分三步走:
第一步,将链表一分为二,用到快慢指针
第二步,反转第二部分
第三步,将两链表拼接起来
第1步,将链表一分为二
使用快慢指针将链表分成两段。假设有一个快指针fast,一个慢指针slow。快指针的速度是慢指针的两倍,它们都从head节点向后跑。当快指针到达链表尾部的时候,慢指针就会刚好在链表的中间,于是就可以从中间将链表一分为二了。
采用这种方式会导致在链表结点个数为奇数的情况下,后半段的个数比前半段多一个。
第2步,反转链表
在遍历链表时,将当前节点的 next 指针改为指向前一个元素。由于节点没有引用其上一个节点,因此必须事先存储其前一个节点。在更改 next 之前,还需要另一个指针来存储下一个节点。而且在反转以后,要在新的链表结尾加上next = NULL。
第3步,拼接两个链表
在链表结点个数为奇数的情况下,后半段的个数会比前半段多一个。所以在将两子链表连接起来的时候,要注意判断反转的后半段是否已经结束,没有,则连接上剩余部分即可。
实现代码
class Solution {
public void reorderList(ListNode head) {
if(head == null || head.next == null){
return;
}
//第一步:利用快慢指针法,将链表从中间一分为二
ListNode fast = head;
ListNode slow = head;
ListNode slowPre = null;
while(fast != null && fast.next != null){
fast = fast.next.next;
slowPre = slow;
slow = slow.next;
}
slowPre.next = null;
//一分为二后,链表的两段,前一段从head开始,到slowPre结束
//后一段从slow开始,到链表尾部结束
//第二步,反转后一段链表,也就是从slow开始的那段链表
//反转的方法是让每一个节点的next都指向它前面的节点
ListNode cur = slow;
ListNode before = null;
while(cur != null){
ListNode behind = cur.next;
cur.next = before;
before = cur;
cur = behind;
}
//反转后的链表,第一个节点是before所指的节点
//第三步,合并两个链表
ListNode l1 = head;
ListNode l2 = before;
ListNode assist = new ListNode(0);
while(l1 != null || l1 != null){
if(l1 != null){
assist.next = l1;
l1 = l1.next;
assist = assist.next;
}
if(l2 != null){
assist.next = l2;
l2 = l2.next;
assist = assist.next;
}
}
}
}