反轉(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
)。
隨后我們將cur
和pre
向后移動,因為之前我們用tmp
存儲了cur
的下一個結(jié)點,所以我們需要將cur
的值先賦值給pre
,然后再將tmp
(下個要反轉(zhuǎn)的結(jié)點)賦值給cur
(如果不先賦值pre
,cur
就被tmp
覆蓋了,你懂的~)
如圖所示
這樣我們
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。如下圖所示:
1.假設(shè)
我們按照階乘的思路假設(shè),假設(shè)除了頭結(jié)點head(也就是1),后面都反轉(zhuǎn)好了。也就是:
代碼就為:ListNode newHead = reverseList(head.next);
意思就是我們把1的下個 2傳進(jìn)去 期望返回上面圖中的樣子。
此時的1在干啥?它也在:
1(也就是head)是始終都指向2的。
但是通過我們的假設(shè)2345都叛變了,變成了下面的樣子:
此時要做什么?就是將1(head)節(jié)點和2節(jié)點之間弄好:
也就是
//把1的next節(jié)點的next指向自身
head.next.next = head;
然后我們發(fā)現(xiàn)1仍然指向2(嗯,1很忠貞鑒定不移),我們最終要反轉(zhuǎn)成圖1的樣子,也就需要增加:
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,記得備注哦~