Reverse a singly linked list.

Example:

Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL

Follow up:

A linked list can be reversed either iteratively or recursively. Could you implement both?


 

反转链表,链表算法里必备技能点,基础的重中之重。很多有关链表反转、翻转的问题都是在这个基础上进行思维的延伸。既然要求用两个方法做,那就先说迭代再说递归。

 

解法一,迭代:

首先,肯定知道最后返回的结果是输入链表的链表尾节点。但先找到尾节点是很难继续实现的,因为链表没有办法高效获取前驱。往往这类问题很多时候都要想到建立一个新的节点,之后在遍历输入的时候重新组织节点顺序,将节点挂在新节点上。所以高效的做法是在遍历链表的过程中,一个一个的把输入链表的节点放到一个新的链表头部。所以思路就是建立一个新的链表头,每次遍历输入链表的节点都把他放到新链表的头部,这样遍历完成后就获得了反转的链表。详细代码注释见下。

 

解法二,递归:

每次想着用递归解法我习惯于用数学归纳法的思维去思考。先想输入规模最小的情况,再想比较general的情况。就本题来说,如果输入的是null或者单节点链表,必然是返回其本身。如果至少有两个节点,那么才开始递归。想一下,递归后的结果一定是一个规模更小问题的结果。即如果输入有k个节点,那么递归调用程序,输入原链表第二个节点所返回的结果,是一个反转后的,拥有k-1个节点的链表的首节点 —— 规模更小的问题的结果。那么如果把这个递归调用后返回的头节点所指向链表的尾节点的next域,指向被调用的节点的前驱,就相当反转了k个节点的链表。即利用k-1的结果去完成了k的问题。所以想到这里,在递归函数里要做的就是三件事:第一,记录即将被递归调用节点的前驱(或者换句话说,建立个新的节点指向输入的下一个节点,之后递归调用那个新节点);第二,递归调用输入的下一个节点;第三,将返回结果的末尾指向记录好的前驱节点,完成反转。

这里需要注意的只有第三步,如何找到返回的结果链表的末尾。还是要回归到递归的本质,即返回的结果是一个已经反转完成的链表的首节点。反转完成的意思就是我们输入一个以节点S为头结点,节点E为尾结点的链表,那么调用后返回的节点是E,而S经过调用后变成了尾节点。即,递归调用时的输入本身,即是调用完成后我们需要的尾节点!所以我们并不需要每一次都去寻找递归调用后结果的尾节点,只需要直接利用递归调用的输入即可,因为这个输入就是调用完成的尾节点。详细代码注释见下。

 


解法一(Java)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode first = head;
        ListNode reverseHead = null; //建立一个新的节点用来存放结果
        while (first != null) { //遍历输入链表,开始处理每一个节点
            ListNode second = first.next; //先处理第一个节点first,所以需要一个指针来存储first的后继
            first.next = reverseHead; //将first放到新链表头节点的头部
            reverseHead = first; //移动新链表的头指针,让它始终指向新链表头部
            first = second; //继续处理原链表的节点,即之前指针存放的后继,循环往复
        }
        return reverseHead;
    }
}

 

解法二(Java)

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head; //处理最小输入的情况,即空链表和单节点链表
        ListNode second = head.next; //将即将被调用的下一个节点分离,即将下一个调用的输入存在second里
        ListNode reverseHead = reverseList(second); //将调用后的结果存储,这个结果就是最终结果。之后利用递归,调用刚才存好的输入
        second.next = head; //上面一步的调用已经完成以second为首的链表的反转,所以现在second变成了反转完成后的尾节点
                 //把这个尾节点的next指向一开始输入的前驱,即head,完成整个链表反转
head.next = null; //最开始的头节点要变成尾节点,即在后面补null使链表终结 return reverseHead; } }

 

posted on 2018-07-08 10:49  小T在学习  阅读(3853)  评论(0编辑  收藏  举报