不难注意到目标链表即为将原链表分成前后两段然后将后半段反转再依照某种规则链接得到的。这样我们的任务即可划分为三步:找到原链表的中点,将原链表的右半端反转,将原链表的两端合并。可以通过快慢指针找到中点不过要注意链表长度分别为奇偶情况的处理,反转可以通过迭代或者递归的方法。
public void reorderList(ListNode head) {// 创建一个虚拟节点,使得链表可以从头开始ListNode dummy = new ListNode(0);dummy.next = head;// 使用快慢指针找到链表的中点,将链表分为两部分ListNode fast = dummy;ListNode slow = dummy;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next;// 链表长度是奇数这样处理使前半段比后半段多一个节点if (fast.next != null) {fast = fast.next;}}// 将链表分为两部分后,对后半部分进行反转ListNode temp = slow.next;slow.next = null;// 调用 link 方法连接前半部分和反转后的后半部分链表link(head, reverseList(temp), dummy);}/*** 作用:连接两个链表* @param node1 前半段头节点* @param node2 后半段头节点* @param head 新链表的头节点,即dummy节点*/private void link(ListNode node1, ListNode node2, ListNode head) {ListNode prev = head;while (node1 != null && node2 != null) {ListNode temp = node1.next;// 将前半部分链表的当前节点指向后半部分链表的当前节点prev.next = node1;node1.next = node2;// 更新 prev 指向后半部分链表的当前节点prev = node2;// 同时移动两个链表的指针node1 = temp;node2 = node2.next;}// 如果前半部分链表还有剩余节点(链表长度是奇数),连接到后半部分链表的末尾if (node1 != null) {prev.next = node1;}}// 反转链表public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode cur = head;while (cur != null) {ListNode next = cur.next;cur.next = prev;prev = cur;cur = next;}return prev;}
另一种相较于这个方法没那么优雅的方法是我们可以使用线性表。因为链表不支持下标访问,所以我们无法随机访问链表中任意位置的元素。因此我们可以利用线性表存储该链表,然后利用线性表可以下标访问的特点,直接按顺序访问指定元素,重建该链表即可。
public void reorderList(ListNode head) {// 如果链表为空,则直接返回,不需要重新排列if (head == null) {return;}// 创建一个 ArrayList 用于存储链表的所有节点List<ListNode> list = new ArrayList<ListNode>();ListNode node = head;// 将链表中的每个节点依次添加到 ArrayList 中while (node != null) {list.add(node);node = node.next;}// 使用双指针 i 和 j 分别指向数组的开头和结尾int i = 0, j = list.size() - 1;// 在循环中,将指针 i 对应的节点的 next 指向指针 j 对应的节点,// 同时将指针 j 对应的节点的 next 指向指针 i 对应的节点。while (i < j) {list.get(i).next = list.get(j);i++;// 如果 i 和 j 相遇,说明链表已经重新排列完成,跳出循环if (i == j) {break;}list.get(j).next = list.get(i);j--;}// 将最后一个节点的 next 设置为 null,以确保链表的结束list.get(i).next = null;}