剑指Offer[24]:反转链表

题目

  定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。我们将链表的节点定义如下:

class ListNode{
	private int val;       // 节点值
	private ListNode next;     // 下一个节点
}

  LeetCode中也有相应的题目

解法

迭代解法

  迭代的方法很好理解:为了反转链表我们从前向后遍历链表,改变每一个节点的指针指向,让每个节点都指向前一个节点,最后返回尾节点。

  迭代方法主要注意两个问题:第一个是头节点反转之后是尾节点,因此头节点应当指向null;第二个要注意的点是,每次让当前节点指向前一个结点后,会将链表截断为两部分,因此每次需要保存下一个节点。

迭代解法的代码如下:

// 迭代法
public static ListNode reverse(ListNode head){
    ListNode curr = head;    // 当前节点
    ListNode prev = null;    // 当前节点的前一个节点

    while(curr != null){
        ListNode temp = curr.next;   // 临时指针指向当前节点的下一个节点
        curr.next = prev;   // 当前节点指向前一个节点
        prev = curr;      // 指针后移
        curr = temp;   
    }
    return prev;
}

迭代解法的时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

递归解法

  递归解法理解起来稍微复杂一点。假设链表从第 k + 1 k+1 k+1 个元素开始的后半部分已经被反转,当前节点为 n k n_k nk,也就是当前链表如下:

n 1 → … → n k − 1 → n k → n k + 1 ← n k + 2 ← … ← n m n_{1} \rightarrow \ldots \rightarrow n_{k-1} \rightarrow n_{k} \rightarrow n_{k+1} \leftarrow n_{k+2} \leftarrow \ldots \leftarrow n_{m} n1nk1nknk+1nk+2nm

那么接下来要做的就是让 n k + 1 n_{k+1} nk+1 的 下一个节点为 n k n_k nk,另外就是每次的中间节点(上面的 n k + 1 n_{k+1} nk+1)是不指向其余节点的。

递归解法的代码如下:

// 递归解法
public static ListNode reverse(ListNode head){
    if(head == null || head.next == null)    return head;
    ListNode p = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return p;
} 

相比起迭代解法,递归法的代码短小精悍,其时间复杂度为 O ( n ) O(n) O(n),由于递归使用了额外栈空间,因此递归法的空间复杂度为 O ( n ) O(n) O(n)

扩展——反转部分链表

  LeetCode的第92题是反转链表的进阶版——反转链表的一部分,题目如下:

反转从位置 mn 的链表。请使用一趟扫描完成反转。1 ≤ mn ≤ 链表长度。

以链表 1 -> 2 -> 3 -> 4 -> 5 -> 6 为例,假如要反转的部分是 2 到 5,反转之后的链表应当为 1 ->5 -> 4 -> 3 -> 2 -> 6 。反转过程通过下图来说明:

  首先通过计数找到反转的起始节点标记为 curr,并记录其前面节点 prev和后一个节点 temp,如图中第1行所示;接下来分三步:

  1. curr.next指向 temp.next
  2. temp.next指向 prev.next
  3. prev.next指向temp

执行完上面三步后会得到图中第2行的结构,接下来重复上面的三步直到 curr到达反转部分的末尾,反转完成。代码如下:

// 反转链表的一部分
public static ListNode reverseBetween(ListNode head, int start, int end){
    if(start == end) return head;
    ListNode curr = head, newHead = new ListNode(-1);
    ListNode prev = newHead;
    newHead.next = head;

    int count = 1;     
    while(count < start){
        prev = prev.next;
        count++;
    }
    curr = prev.next; // 找到反转的起始节点

    while(count++ < end){
        ListNode temp = curr.next;   // 保存当前节点的下一个节点
        curr.next = curr.next.next;  
        temp.next = prev.next;
        prev.next = temp;
    }
    return newHead.next;
}

如果提前已知链表的长度,那么让起始位置为1,结束位置为链表长度就可以实现整个列表的反转,因此上面的程序能作为一个更一般化的反转链表的程序。其时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值