leetcode-反轉連結串列(方法總結)
題目描述:給定一個連結串列,將連結串列進行反轉。
示例1:
輸入:1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
題目理解:首先先接收一個連結串列,然後根據連結串列的效能進行反轉。
解法1:藉助棧的後進先出的功能,先掃描一遍連結串列,在棧中儲存每個節點的值,然後再從頭到尾遍歷,將棧中的元素按次序彈出賦給連結串列的節點,時空複雜度都是O(n).
package TEST; import java.util.Stack; /** * @Author: * @Description:反轉連結串列; * @Date: Created in 2018/9/3 23:14 * @Pram: */ public class Node { private Node next; private String val; public Node(String val){ this.val = val;//節點的值; } public Node() { } public static void ReverseList(Node node){ Stack<Node> stack = new Stack<Node>();//定義一個棧; while(node!=null){//連結串列不為空就開始遍歷; stack.push(node);//入棧; node = node.next; } while(!stack.empty()){ Node temp = stack.pop(); System.out.println(temp.val); } } public static void main(String[] args) { Node node1 = new Node("A"); Node node2 = new Node("B"); Node node3 = new Node("C"); node1.next = node2; node2.next = node3; System.out.println("連結串列反轉後是:"); ReverseList(node1); node1.next = null; } }
解法2:可以做到in-place的反轉。連結串列反轉後,實際上只是中間節點的指標反轉,並且反轉後原來的連結串列的頭節點的下一個節點應該為null,而反轉後連結串列的頭節點為原來連結串列的結尾處的尾節點。從頭節點開始處理每兩個節點之間的指標將其反轉過來,然後處理接校來的指標,直到尾節點結束。並設定新的連結串列的頭節點即可。
public class ListNode{ int val; ListNode next; ListNode(int x) { val = x; } } public class Solution{ public ListNode reserseList(ListNode head){ if(head == null || head.next == null){ return head; } ListNode next = head.next; head.next = null; while(next.next != null){ ListNode node = next.next; next.next = head; head = next; next = node; } next.next = head; return next; } }
解法3--迭代:首先,肯定知道最後返回的結果是輸入連結串列的連結串列尾節點。但先找到尾節點是很難繼續實現的,因為連結串列沒有辦法高效獲取前驅。往往這類問題很多時候都要想到建立一個新的節點,之後在遍歷輸入的時候重新組織節點順序,將節點掛在新節點上。所以高效的做法是在遍歷連結串列的過程中,一個一個的把輸入連結串列的節點放到一個新的連結串列頭部。所以思路就是建立一個新的連結串列頭,每次遍歷輸入連結串列的節點都把他放到新連結串列的頭部,這樣遍歷完成後就獲得了反轉的連結串列。詳細程式碼註釋見下。
/** * 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; } }
解法4--遞迴:
遞迴:
每次想著用遞迴解法我習慣於用數學歸納法的思維去思考。先想輸入規模最小的情況,再想比較general的情況。就本題來說,如果輸入的是null或者單節點連結串列,必然是返回其本身。如果至少有兩個節點,那麼才開始遞迴。想一下,遞迴後的結果一定是一個規模更小問題的結果。即如果輸入有k個節點,那麼遞迴呼叫程式,輸入原連結串列第二個節點所返回的結果,是一個反轉後的,擁有k-1個節點的連結串列的首節點 —— 規模更小的問題的結果。那麼如果把這個遞迴呼叫後返回的頭節點所指向連結串列的尾節點的next域,指向被呼叫的節點的前驅,就相當反轉了k個節點的連結串列。即利用k-1的結果去完成了k的問題。所以想到這裡,在遞迴函式裡要做的就是三件事:第一,記錄即將被遞迴呼叫節點的前驅(或者換句話說,建立個新的節點指向輸入的下一個節點,之後遞迴呼叫那個新節點);第二,遞迴呼叫輸入的下一個節點;第三,將返回結果的末尾指向記錄好的前驅節點,完成反轉。
這裡需要注意的只有第三步,如何找到返回的結果連結串列的末尾。還是要回歸到遞迴的本質,即返回的結果是一個已經反轉完成的連結串列的首節點。反轉完成的意思就是我們輸入一個以節點S為頭結點,節點E為尾結點的連結串列,那麼呼叫後返回的節點是E,而S經過呼叫後變成了尾節點。即,遞迴呼叫時的輸入本身,即是呼叫完成後我們需要的尾節點!所以我們並不需要每一次都去尋找遞迴呼叫後結果的尾節點,只需要直接利用遞迴呼叫的輸入即可,因為這個輸入就是呼叫完成的尾節點。詳細程式碼註釋見下。
/**
* 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;
}
}