1.反轉鏈表
經典考題,針對鏈表的反轉,第一時間需要聯想到將鏈表的指針進行反轉,而這種一系列的變化,可以使用遞歸,也可以使用while
迭代
假設鏈表為 1 \rightarrow 2 \rightarrow 3 \rightarrow \varnothing1→2→3→∅,我們想要把它改成 \varnothing \leftarrow 1 \leftarrow 2 \leftarrow 3∅←1←2←3。
在遍歷鏈表時,將當前節點的 \textit{next}next 指針改為指向前一個節點。由於節點沒有引用其前一個節點,因此必須事先存儲其前一個節點。在更改引用之前,還需要存儲后一個節點。最后返回新的頭引用。
class Solution { public ListNode reverseList(ListNode head) { //使用指針的方法對全部的值進行反轉 //首先定義空的節點 ListNode pre = null; ListNode cur = head; while(cur!=null){ ListNode next = cur.next; //當前節點的子節點將指向前節點 cur.next = pre; //當前節點變為前節點 pre = cur; //下一個節點變為當前節點 cur = next; } return pre; } }
遞歸
遞歸版本稍微復雜一些,其關鍵在於反向工作。假設鏈表的其余部分已經被反轉,現在應該如何反轉它前面的部分?
假設鏈表為:
n1→…→nk−1→nk→nk+1→…→nm→∅
若從節點 n_{k+1}nk+1 到 n_mnm 已經被反轉,而我們正處於 n_knk。
n1→…→nk−1→nk→nk+1←…←nm
我們希望 n_{k+1}nk+1 的下一個節點指向 n_knk。
所以,n_k.\textit{next}.\textit{next} = n_knk.next.next=nk。
需要注意的是 n_1n1 的下一個節點必須指向 \varnothing∅。如果忽略了這一點,鏈表中可能會產生環
class Solution { 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; } }
2.鏈表取中間節點
給定一個頭結點為 head 的非空單鏈表,返回鏈表的中間結點。
如果有兩個中間結點,則返回第二個中間結點。
示例 1:
輸入:[1,2,3,4,5]
輸出:此列表中的結點 3 (序列化形式:[3,4,5])
返回的結點值為 3 。 (測評系統對該結點序列化表述是 [3,4,5])。
注意,我們返回了一個 ListNode 類型的對象 ans,這樣:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:
輸入:[1,2,3,4,5,6]
輸出:此列表中的結點 4 (序列化形式:[4,5,6])
由於該列表有兩個中間結點,值分別為 3 和 4,我們返回第二個結點。
需要聯想到的方法:
1.雙指針(快慢指針)法,既然是一個數組,我分別定義一個走兩步的head和一個走一步的head,每當head1走兩步,我的head2走1步,head1走完,head2剛好走到中間。
public ListNode middleNode(ListNode head) { ListNode slow = head, fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
2.單指針法:我每走一步,i++,在第二次遍歷是,我走到i/2步,結束
public ListNode middleNode(ListNode head) { int n = 0; ListNode cur = head; while (cur != null) { ++n; cur = cur.next; } int k = 0; cur = head; while (k < n / 2) { ++k; cur = cur.next; } return cur; }
3.數組法:將節點變為數組,並記錄一個i++,變成節點數組之后直接獲取最大i/2的數組內容,也就是中間的結點。
public ListNode middleNode(ListNode head) { ListNode[] A = new ListNode[100]; int t = 0; while (head != null) { A[t++] = head; head = head.next; } return A[t / 2]; }
3.合並兩個有序鏈表
將兩個升序鏈表合並為一個新的 升序 鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。
輸入:l1 = [1,2,4], l2 = [1,3,4]
輸出:[1,1,2,3,4,4]
示例 2:
輸入:l1 = [], l2 = []
輸出:[]
示例 3:
輸入:l1 = [], l2 = [0]
輸出:[0]
遞歸,思想是這樣的,我先將l1和l2看作一個單獨的結點,進行判斷,如果L1小於L2,我就講L1.next和L2的剩余內容又看作一個整體,再進行遞歸。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { if(l1 == null){ return l2; }else if(l2 == null){ return l1; }else if(l1.val<l2.val){ l1.next = mergeTwoLists(l1.next,l2); return l1; }else{ l2.next = mergeTwoLists(l1,l2.next); return l2; } }
4.將鏈表兩兩進行反轉
給定一個鏈表,兩兩交換其中相鄰的節點,並返回交換后的鏈表。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。
輸入:head = [1,2,3,4]
輸出:[2,1,4,3]
遞歸,將前兩個看作一個單位進行遞歸,返回
public ListNode swapPairs(ListNode head) { if(head==null||head.next==null){return head;} ListNode pre = head; ListNode cur = pre.next; ListNode next = cur.next; pre.next = swapPairs(next); cur.next = pre; return cur; }
5.將鏈表回文
變成數組和遞歸方法
1.變成數組
class Solution { public boolean isPalindrome(ListNode head) { List<Integer> vals = new ArrayList<Integer>(); // 將鏈表的值復制到數組中 ListNode currentNode = head; while (currentNode != null) { vals.add(currentNode.val); currentNode = currentNode.next; } // 使用雙指針判斷是否回文 int front = 0; int back = vals.size() - 1; while (front < back) { if (!vals.get(front).equals(vals.get(back))) { return false; } front++; back--; } return true; } }
2.遞歸
class Solution { private ListNode frontPointer; private boolean recursivelyCheck(ListNode currentNode) { if (currentNode != null) { if (!recursivelyCheck(currentNode.next)) { return false; } if (currentNode.val != frontPointer.val) { return false; } frontPointer = frontPointer.next; } return true; } public boolean isPalindrome(ListNode head) { frontPointer = head; return recursivelyCheck(head); } }