【编程题 】NC2 重排链表(详细注释 易懂)

本文详细解析了一道关于重排链表的算法题,介绍了两种实现方式:一是通过递归进行首尾节点连接,二是利用数组模拟进行节点重排。文章提供了完整的代码示例,并对每一步操作进行了详细解释。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

  题目链接:重排链表_牛客题霸_牛客网 (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;
        
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值