反轉(zhuǎn)鏈表的思考和詳細(xì)理解(迭代&遞歸)

反轉(zhuǎn)鏈表算法很經(jīng)典了,我們面試也經(jīng)常會遇到。
大致的話會分為兩種解法:一種是迭代 一種是遞歸

一、迭代法

public ListNode reverseList(ListNode head) {
        ListNode pre = null, cur = head, tmp = null;
        while (cur != null) {
            //存儲下一個節(jié)點地址
            tmp = cur.next;
            //把當(dāng)前節(jié)點指向上個節(jié)點
            cur.next = pre;

            //兩個節(jié)點分別往前挪
            //上一個節(jié)點往前挪
            pre = cur;
            //當(dāng)前節(jié)點往前拖
            cur = tmp;
        }
        return pre;
    }

主要思路就是
先創(chuàng)建pre節(jié)點為null,cur節(jié)點指向鏈表的頭結(jié)點head,tmp為臨時變量用于存儲當(dāng)前節(jié)點的下個節(jié)點。
然后先tmp將當(dāng)前節(jié)點的next節(jié)點存儲后,將cur節(jié)點反轉(zhuǎn),指向pre節(jié)點。

while第一次循環(huán)時我們完成了第一個反轉(zhuǎn),就是將cur(也就是頭結(jié)點)指向了pre(上個節(jié)點為null)。
隨后我們將curpre向后移動,因為之前我們用tmp存儲了cur的下一個結(jié)點,所以我們需要將cur的值先賦值給pre,然后再將tmp(下個要反轉(zhuǎn)的結(jié)點)賦值給cur(如果不先賦值pre,cur就被tmp覆蓋了,你懂的~)

如圖所示


迭代法反轉(zhuǎn)鏈表示意圖(圖片來自leedcode-206)

這樣我們whlie循環(huán)介紹結(jié)束以后,以當(dāng)前節(jié)點cur為條件,當(dāng)cur移動到尾結(jié)點的后一個,也就是為null時,這時上個節(jié)點pre正好指向鏈表最后一個節(jié)點,我們也完成了鏈表的反轉(zhuǎn),此時的頭結(jié)點就是pre,直接返回。

我們也可以稍稍改造一下 省略掉tmp變量,以head(當(dāng)前節(jié)點)來替代
如下:

public ListNode reverseList(ListNode head) {
        ListNode pre = null, cur = head;
        while (cur != null) {
            //直接把下個節(jié)點指向頭結(jié)點完成反轉(zhuǎn)
            head = head.next;

            //把當(dāng)前節(jié)點(cur是之前的頭結(jié)點)指向上個節(jié)點 與之前一樣
            cur.next = pre;

            //兩個節(jié)點分別往前挪
            //上一個節(jié)點往前挪
            pre = cur;
            //當(dāng)前節(jié)點往前拖 由于頭結(jié)點已經(jīng)指向了下一個結(jié)點 直接挪動即可
            cur = head;

            //每次循環(huán)完畢之后 head就指向下個節(jié)點
        }
        return pre;
    }

二、遞歸法

一般來說,遞歸解決問題的代碼總是精簡的,但是思路卻很難想得通,我們要做的不僅僅是去演算別人寫出的算法,而是要總結(jié)出遞歸的思路,以便應(yīng)對每個有規(guī)律問題的遞歸解答方式。

先列舉下階乘的遞歸寫法:

int factorial(int n) {
    if (n == 1) return 1;
    return n * factorial(n - 1);
}

階乘想必大家見識的多了,求n!有很多種解法,最直接的莫過于循環(huán)乘積。
上面的是階乘的遞歸寫法,我們可以看到,遞歸總是有一個出口---防止代碼進(jìn)行無限循環(huán)
遞歸的思想首先是假設(shè),求n!的時候我們假設(shè)已經(jīng)算完了1*2*3...*(n-1)

我們甚至可以這么寫:

int factorial(int n) {
    if (n == 1) return 1;
    return n * (n - 1) * factorial(n - 2);
}

這個思路也就是我們假設(shè)已經(jīng)算完了1*2*3...*(n-2)

所以遞歸思想有先決的兩個條件 1.假設(shè) 2.出口
然后就是核心邏輯,調(diào)用自身。

我們來看反轉(zhuǎn)鏈表的問題:

首先我們有一個1->2->3->4->5->null的鏈表,想將它反轉(zhuǎn)成5->4->3->2->1->null。如下圖所示:

反轉(zhuǎn)鏈表示意圖

1.假設(shè)

我們按照階乘的思路假設(shè),假設(shè)除了頭結(jié)點head(也就是1),后面都反轉(zhuǎn)好了。也就是:

假設(shè)除頭結(jié)點 其他都反轉(zhuǎn)好了示意圖

代碼就為:ListNode newHead = reverseList(head.next);
意思就是我們把1的下個 2傳進(jìn)去 期望返回上面圖中的樣子。

此時的1在干啥?它也在:
1(也就是head)是始終都指向2的。
但是通過我們的假設(shè)2345都叛變了,變成了下面的樣子:

1(頭結(jié)點狀況示意圖)

此時要做什么?就是將1(head)節(jié)點和2節(jié)點之間弄好:
也就是

     //把1的next節(jié)點的next指向自身
     head.next.next = head;
head.next.next = head之后

然后我們發(fā)現(xiàn)1仍然指向2(嗯,1很忠貞鑒定不移),我們最終要反轉(zhuǎn)成圖1的樣子,也就需要增加:

head.next = null;

就變成如下的樣子:

head.next = null

我們發(fā)現(xiàn)已經(jīng)反轉(zhuǎn)成了想要的模樣。
此時需要一個返回值,返回什么,是head嗎?還是newHead?
我們肯定要返回反轉(zhuǎn)后鏈表的頭結(jié)點,也就是我們假設(shè)后的返回值,即newHead

2.出口

情況1:肯定就是我們的head是一個空 那么我們返回null就可以了。

if (head == null) return null;

情況2:反轉(zhuǎn)鏈表起碼兩個才有意義,如果當(dāng)前鏈表只有一個head呢?那我們返回head就可以了

if (head.next == null) return head;

總結(jié)以上情況,代碼如下:

public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;

        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
}

這就是反轉(zhuǎn)鏈表的遞歸解法。

予人玫瑰,手有余香!如果這篇文章幫助了你,并且讓你覺得想要放到自己的博客上,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處。

學(xué)習(xí)路上,與君共勉!
討論交流可加微信T_Yang_0727,記得備注哦~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容