将链表按照左右分区重新排列
将链表按照左右分区重新排列
问题重述:
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
https://leetcode-cn.com/problems/reorder-list/
示例 1:
输入:head = [1,2,3,4]
输出:[1,4,2,3]
示例 2:
输入:head = [1,2,3,4,5]
输出:[1,5,2,4,3]
问题分析:
题目要求按照左右分区来重新排列链表,并且将右半区的结点倒序间隔的插入左半边链表中,因为我们链表只能从前往后,不能实现倒序,所以我们可以想到将右半边链表反转,然后插入到左边。那么如何将将链表分区呢,我们可以想到使用快慢指针,快慢指针很容易就可以找到中间结点
解法:
双指针
解题:
代码:
public ListNode reorderList(ListNode head) {
/**
* 主要分为三个步骤
* 首先找到中间节点
* 将右边链表反转
* 将反转后的链表和左半边的链表连起来
*/
// 特殊情况
if(head == null || head.next == null){
return head;
}
// 使用快慢指针找到中间节点
ListNode dummy = new ListNode(-1,head);
ListNode slow = dummy;
ListNode fast = dummy;
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
// 此时的slow指向的是中间结点的前一个结点,反转
fast = reverse(slow.next);
head = mergeLR(head,fast);
return head;
}
public ListNode reverse(ListNode head){
// 反转链表
ListNode pre = null;
ListNode cur = head;
ListNode next = null;
while(cur != null){
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
public ListNode mergeLR(ListNode left,ListNode right){
// 两个链表的合并
// 当我们合并的时候,右边结点的next需要被保存哟关于下一次使用
ListNode rNext = null;
// 创建一个结点用于遍历链表
ListNode cur = left;
while(cur != null && right != null){
// 保存当前右边结点的下一个结点,用于下一次使用
rNext = right.next;
// 将右边结点插入到左边去
right.next = cur.next;
cur.next = right;
// 因为此时cur的next插入了right,所以需要向右移动两格
cur = cur.next.next;
// 更新right结点
right = rNext;
}
// 这里非常需要注意,这里如果不对当前结点的next进行处理的话会造成结点成环
cur.next = rNext;
return left;
}
代码解析:通过问题分析我们可以知道这道题的基本思路,其中有几个细节需要注意:首先是我们要使用快慢指针操作链表的时候,最好创建一个假的dummy结点辅助操作,这样有助于对头节点的操作(也就是说快慢指针不能同时从头节点开始),否则容易造成其他后果。还有一个细节就是在将左右两段链表交叉连接起来的时候,需要注意最后一个结点,此时循环完成后,最后一个结点是中间节点,根据循环中的操作,此时该节点的next是它本身(因为right.next = cur.next而cur.next就是当前结点right),所以此时如果不对这个结点进行处理的话,这会成为一个环。对该结点的操作就是将原本右边可能有的结点连接到该节点的next(本题中右边已经没有了结点,但是做到其他题目的话,可能右边还会有结点,连起来即可)
总结:
当看到链表+中间,就要想到快慢指针,使用快慢指针的时候需要注意不能同时从head结点开始,最好是创建一个假节点放到头节点前面,然后从前面开始。
对于链表内结点的相互连接的时候,需要注意结点连接后是否会成环,主要是考虑最后一个结点。