题目描述
题目链接:重排链表_牛客题霸_牛客网 (nowcoder.com)
将给定的单链表\ L L: L_0→L_1→…→L_{n-1}→L_ nL0→L1→…→Ln−1→Ln
重新排序为:L_0→L_n →L_1→L_{n-1}→L_2→L_{n-2}→…L0→Ln→L1→Ln−1→L2→Ln−2→…
要求使用原地算法,不能只改变节点内部的值,需要对实际的节点进行交换。
数据范围:链表长度 0≤n≤20000 ,链表中每个节点的值满足 0≤ val ≤1000要求:空间复杂度 O(n)O(n) 并在链表上进行操作而不新建链表,时间复杂度 O(n)
进阶:空间复杂度O(1) , 时间复杂度: O(n)
示例1
输入:
{1,2,3,4}返回值:
{1,4,2,3}说明:
给定head链表1->2->3->4, 重新排列为 1->4->2->3,会取head链表里面的值打印输出
示例2
输入:
{1,2,3,4,5}返回值:
{1,5,2,4,3}说明:
给定head链表1->2->3->4->5, 重新排列为 1->5>2->4->3,会取head链表里面的值打印输出
示例3
输入:
{}返回值:
{}
题目解读:
题目给一个单链表,要求改变原有顺序,以0 -> n ->1 -> n-1 -> 2 -> n-2 ... 的形式连接,打印。这个题在牛客上是四星难度题。如果初次做,基本上没啥思路,我承认我第一次做也是抓耳挠腮,做了半天,摆烂了。。。
但没办法,从哪跌,从哪爬,算法题,考的是类似思维,如果有做过类似的,那也就觉得没那么难了。
解题思想:
这个题,要进行 首尾,次首次尾 形式的连接。这里有两种解决思路,一种是 切香肠式,每次就找到 没有被排序的链表的首部,第二个元素 和 最后一个元素。画图说明。
由图可知,总共需要四个指针,每次都先找到当前需要排序链表的 头节点,头节点的下一个节点,尾节点,尾节点的前一个节点,然后让尾节点的前一个节点置空,头节点指向尾节点,尾节点指向头节点的下一个节点。
做这个题,容易有个误区,就是 认为 两个头尾节点都要被改变逆置,比如 1->2->3->4,
经过一次重排序后为,1->4->2->3 就结束了,但是我总是觉得,2和3之间还要再处理一下,给形成思路带来困扰。
上面举的例子,只进行了一次重排序,可能没有代表性,那就再举例说明一个,
这个例子进行了两次重排序,那就有两个疑问了,一个是,我该如何控制排序的结束呢,
那就观察发现,由于是头尾相连,所以,至少得有三个节点,才有重排的必要,所以节点数少于三个的情况下,直接返回。 第二个疑问,我上图第二次排序,head都跑到其他位置了,那之后怎么遍历整个链表,这就要用到递归,不得不说,递归真是个好东西,我当时一直想着用循环去控制,但使用循环就要知道总长度,而且用了总长度控制,里面再有一个遍历节点的循环就O(n^2)时间复杂度了,不符合题意。 通过递归,改变头节点位置,最后递归完,头节点还在原来的位置,就可以获得完全重排后的链表。
第二种思路 ,就是把它当作数组来做,先用链表,依次放入当前链表的每一个节点,然后通过链表,让第一个节点连最后一个节点,再让最后一个节点连第二个节点... ,最后就连接完成了。 这种方式 有两个点需要注意,一个是 只要有两个及以上的节点,都需要连接;还有一个是 把节点放入链表,原有的连接并没有断开,所以最后要给它断开,避免形成循环链表。
第一种思路的代码注释:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public void reorderList(ListNode head) {
if(head == null)
return;
if(head.next == null)
return;
//如果只有两个节点,也直接返回
if(head.next.next == null)
return;
ListNode second = head.next;
ListNode pre = head;
ListNode tail = pre.next;
// 遍历找到尾节点和尾节点前面一个节点
while(tail.next != null){
pre =tail;
tail = tail.next;
}
pre.next = null;
head.next = tail;
tail.next = second;
// 把second 节点作为头节点传入,这里把head节点放到second节点,再传入head,结果也一
// 样,原理上面题解中有说
reorderList(second);
}
}
第二种思路的代码注释:
import java.util.*;
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public void reorderList(ListNode head) {
if(head == null)
return;
// 只有一个节点就不用重连
if(head.next == null)
return;
List<ListNode> list = new ArrayList<>();
ListNode cur = head;
while(cur != null){
list.add(cur);
cur = cur.next;
}
// 拿到链表的头和尾
int start = 0;
int end = list.size()-1;
while(start < end){
list.get(start++).next = list.get(end);
list.get(end--).next = list.get(start);
}
// 由于原来的连接还在,所以要把最后的尾节点断开,避免形成循环链表
list.get(start).next =null;
}
}