143. 重排链表
思路:给定一个单链表 L:L0→L1→…→Ln-1→Ln ,
将其重新排列后变为: L0→Ln→L1→Ln-1→L2→Ln-2→…
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
给定链表 1->2->3->4->5, 重新排列为 1->5->2->4->3.
这一题的规律实际上是把链表分成两半,然后将后一半从后往前插到前一半的中间,所以先来个简单粗暴的想法,把链表存储到线性表中,然后用双指针依次从头尾取元素即可。
public void reorderList(ListNode head) {
if (head == null) {
return;
}
//存到 list 中去
List<ListNode> list = new ArrayList<>();
while (head != null) {
list.add(head);
head = head.next;
}
//头尾指针依次取元素
int i = 0, j = list.size() - 1;
while (i < j) {
list.get(i).next = list.get(j);//0指向n-1
i++;
//偶数个节点的情况,会提前相遇
if (i == j) {//此时连上的节点是双数
break;
}
list.get(j).next = list.get(i);//n-1指向1(此时连上的节点是单数)
j--;
}
list.get(i).next = null;
}
接下来看第二种方法,直接在原始链表上操作不消耗额外的空间,整个过程分为这几个步骤:
public void reorderList(ListNode head) {
if(head == null){
return ;
}
//1. 使用快慢指针,找出链表的中心节点。
// 1->2->3->4->5,中心节点为3
ListNode middle = middleNode(head);
//2. 将原始链表按照中心链表分割为两个链表,并将右链表反转
//2.1 原始链表:1->2->3->4->5 左链表:1->2->3 右链表:4->5
ListNode left = head;
ListNode right = middle.next;//如果链表长度是偶数,则不是均匀的分成两个链表(第一个比第二个长2个节点)
middle.next = null;//注意要切断链表
//2.2 反转右链表
//原始右链表:4->5 反转后:5->4
right = reverse2(right);
//3. 合并两个链表,将右链表插入到左链表
//左链表:1->2->3 右链表:4->5 合并后:1->5->2->4->3
merge(left,right);
}
如果链表长度是偶数,则不是均匀的分成两个链表(第一个比第二个长2个节点,但是不影响结果!!因为第二个链表最后一个节点本身就放在第一个链表最后一个节点后面)
注意 middle.next = null;不能少!!一定要截断链表!!接下来开始写这几个子函数:
//1. 使用快慢指针,找出链表的中心节点
public ListNode middleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
//3. 合并两个链表,将右链表插入到左链表
public void merge(ListNode left, ListNode right){
ListNode leftTemp;
ListNode rightTemp;
while (right!= null) {
leftTemp = left.next;
rightTemp = right.next;
left.next = right;
right.next = leftTemp;
left = leftTemp;
right = rightTemp;
}
}
上面两个函数都比较简单,接下来是反转链表,首先采用最常规的方法,不断的改变表头的指向即可:
public ListNode reverse2(ListNode head){
if(head == null || head.next == null) return head;
ListNode pre = null;
while(head != null){
ListNode temp = head.next;
head.next = pre;
pre = head;
head = temp;
}
return pre;
}
另外也可以用到递归:
//2. 通过递归反转链表
public ListNode reverse(ListNode head) {//即先反转最后两个节点
if (head == null || head.next == null) {//只有到达最后的节点才会return
return head;
}
ListNode last = reverse(head.next);//1>2>3>4>5>6-->1>2>3>4>5 6>5-->1>2>3>4 6>5>4-->....
head.next.next = head;
head.next = null;
return last;
}
我觉得递归可能比较难理解,不妨这样想,因为递归只有到达出口才会有返回值,那么也就是最先反转的应该是链表最后两个节点,拿最后两个节点最为例子再草稿纸上比划比划就能理解了o( ̄▽ ̄)o
但是轮到我自己第二次再写一遍的时候却遇到了问题,找链表中间节点以及截断链表我是这样写的:
public ListNode pingfen(ListNode head){
ListNode fast = head;
ListNode slow = head;
ListNode pre = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
pre = slow;
slow = slow.next;
}
pre.next = null;
return slow;
}
我这样错就错在,slow永远只等于链表的中间节点,而在链表长度为偶数的情况,slow为另一条链表的头节点,当链表为奇数时slow为链表的中间节点,那么在这种奇数情况下,另一条链表的头节点应该为slow.next!!!所以应该返回的时slow.next再把slow.next置空才对!!这时就不用pre了。
这里可能就有人有疑问,如果是1234,那么就分成了123,4。这样对吗?当然对!这和分成12,34再合并的结果是一样的!所以平分链表部分应该改为:
public ListNode pingfen(ListNode head){
ListNode fast = head;
ListNode slow = head;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
}
ListNode head2 = slow.next;
slow.next = null;
return head2;
}