题目描述
将给定的单链表L: L_0→L_1→…→L_{n-1}→L_n
重新排序为:L_0→L_n →L_1→L_{n-1}→L_2→L_{n-2}→…
要求使用原地算法,不能只改变节点内部的值,需要对实际的节点进行交换。
示例1
输入:{1,2,3,4}
返回值:{1,4,2,3}
说明:给定head链表1->2->3->4, 重新排列为 1->4->2->3,会取head链表里面的值打印输出
题目分析
题目排序的意思可以理解为:将链表的尾部结点插入到链表中的头部结点之间,直到头部结点与尾部结点重合(奇数结点链表)或者之间没有其他结点(偶数结点链表)。
将链表分为(0~ n/2)头部结点部分和(n/2 ~ n-1)尾部结点部分,可以进行如图的交换。
解题思路
方法1:使用列表数组存储结点,按下标访问交换
通过列表数组存储结点,可以使用O(1)的时间访问任意结点,避免链表每次需要从头结点遍历访问。
可以使用双指针的方式,一个指针left指向链表头部,一个指针right指向链表尾部,修改这两个结点的next指向,left和right指针分别向中间移动。
方法2:使用栈存储结点,按弹出顺序交换
利用栈的“先进后出”的特点,用栈存储结点,可以直接从栈顶倒序访问链表的结点,从而根头部结点进行交换。
代码实现
方法1:使用列表数组存储结点,按下标访问交换
public void reorderList(ListNode head) { if(head == null) return; ListNode tmp = head; // 存储链表所有结点数据 ArrayList<ListNode> list = new ArrayList<ListNode>(); int n = 0; while(tmp != null){ list.add(tmp); tmp = tmp.next; n++; } int left = 0; int right = n-1; while(left<right){ // 获取头部结点 ListNode pre = list.get(left); // 获取尾部结点 ListNode end = list.get(right); // 指向尾部结点 pre.next = end; left++; // 若到了中间,则交换完成退出 if(left == right) break; // 指向头部下一个结点 end.next = list.get(left); right--; } // 最后的尾部结点指向null list.get(right).next = null; }
时间复杂度:O(n),n为链表长度,需要遍历链表1次时间为n,循环交换时间n/2,所以时间复杂度为O(n);
空间复杂度:O(n),借助了长度为n的列表来存储结点。
方法2:使用栈存储结点,按弹出顺序交换
public void reorderList(ListNode head) { ListNode tmp = head; // 存储链表所有结点数据,使用栈先处理末尾的结点 Stack<ListNode> stack = new Stack<ListNode>(); int n = 0; while(tmp != null){ stack.push(tmp); tmp = tmp.next; n++; } tmp = head; // 计算需要进行交换的结点数 int num = (n-1)/2; while(num>0 && tmp != null && !stack.isEmpty()){ // 获取尾部结点 ListNode end = stack.pop(); ListNode next = tmp.next; // 将尾部节点插入到当前结点的后一个 end.next = next; tmp.next = end; tmp = next; num--; // 将倒数前一个结点尾部置空 stack.peek().next = null; } }
时间复杂度:O(n),n为链表长度,需要遍历链表1次时间为n,循环交换时间n/2,所以时间复杂度为O(n);
空间复杂度:O(n),借助了长度为n的栈来存储结点。