前言 從業快4年,最近愈發感覺到算法的重要性.作為一名后端開發,在實際工作中,算法的應用其實是十分多的,比如我們熟悉的LinkedList、jdk的底層排序,算法的重要性大家都有目共睹,也成了入職大廠不可或缺的基本能力。最近就聽群里的伙伴說面試字節跳動的時候要求現場寫出以k個為一組反轉鏈表,如果不做准備,之前沒有一點了解,看到這種題目,很容易懵逼,那么肯定就必掛無疑了。
算法的種類很多,數組、鏈表、二叉樹、動態規划、貪心、回溯等(越來越感覺到自己的腦殼在逐漸變亮堂了,頭發在漸漸稀疏了- 。-).本篇博客就來探討一下在面試中,特別常見的關於鏈表幾道題:
目錄
一:從反轉一個鏈表說起
二:以K個為一組翻轉鏈表
三:翻轉m到n區間的鏈表
四:總結
一:從反轉一個鏈表說起

1.1:思路
鏈表作為一種以指針聯系的數據結構,無法像數組一樣進行某個元素的隨機訪問。在解決鏈表的問題上,我們唯一的法寶就是指針,通過定義不同的指針互相指引來改變鏈表的順序來解決棘手的問題。仔細分析這道題,發現鏈表的反轉本質上將下一個節點的指針指向前一個指針,按照從局部到整體的解決思路。我們可以將這個問題轉化為:下一個節點指向前一個節點的過程,只要依次循環這個過程,就可以得到最終的結果:

1.2:代碼實現
/** * 從頭開始翻轉鏈表 * * @param node * @return */ public ListNode reverse(ListNode node) { ListNode pre = null; ListNode curr = node; while (curr != null) { ListNode next = curr.next; //將指針指向前面的一個節點 curr.next = pre; pre = curr; curr = next; } return pre; }
1.3:測試
public static void main(String[] args) { ListNode node = new ListNode(1); node.next = new ListNode(2); node.next.next = new ListNode(3); node.next.next.next = new ListNode(4); node.next.next.next.next = new ListNode(5); final ListNode reverse = new ReverseNode().reverse(node); System.out.println(reverse); }

二:以K個為一組翻轉鏈表

2.1:思路
在這個問題中,其實可以大致的把鏈表分為三組:未翻轉區,待翻轉區,已翻轉區,然后通過定義4個指針來切斷我們需要翻轉的鏈表,為什么需要4個指針,往下看你就可以明白了。
這4個指針分別是:
prev:前置指針,作用是定位將要翻轉的前一個位置
next:后置指針,作用是定位要翻轉結束的后一個位置
start:待翻轉的開始指針,定位翻轉的鏈表的開始位置
end:翻轉結束的指針,定位翻轉鏈表的結束位置
2.2:畫圖表示

2.3:代碼實現
public ListNode reverseGroup(ListNode head, int k) { //創建一個預設節點 ListNode dump = new ListNode(0); dump.next = head; //pre節點 ListNode pre = dump; //end節點 ListNode end = dump; while (end != null) { //確定需要反轉區間的尾節點 for (int i = 0; i < k; i++) { //移動end指針 end = end.next; if (end.next == null) { break; } } //確定需要翻轉的子區間的頭結點 ListNode start = pre.next; //斷開子區間和后面的連接 ListNode next = end.next; end.next = null; //翻轉start到end區間 pre.next = reverse(start); start.next = next; pre = start; end = start; } return dump.next; } /** * 從頭開始翻轉鏈表 * * @param node * @return */ public ListNode reverse(ListNode node) { ListNode pre = null; ListNode curr = node; while (curr != null) { ListNode next = curr.next; //將指針指向前面的一個節點 curr.next = pre; pre = curr; curr = next; } return pre; }
2.4:測試類
public static void main(String[] args) { ListNode node = new ListNode(1); node.next = new ListNode(2); node.next.next = new ListNode(3); node.next.next.next = new ListNode(4); node.next.next.next.next = new ListNode(5); node.next.next.next.next.next = new ListNode(6); node.next.next.next.next.next.next = new ListNode(7); node.next.next.next.next.next.next.next = new ListNode(8);
ListNode resultNode = new ReverGroup2().reverseGroup(listNode,3); System.out.println(resultNode);
}

三:翻轉m到n區間的鏈表

3.1:思路
借鑒於第一題的思路,同樣需要定義4個指針,分別是prev,next,start,end和上面的含義基本相似。不同的是這次限定了翻轉的區間,所以本次需要按照以下步驟:
①定義pre指針,走m-1步,來到待翻轉鏈表的區間的前一個節點
②定義end指針,值和pre相同,然后走n-m+1個步驟來到待翻轉鏈表的結尾節點
③定義start節點,指向pre的next,這里的目的是為了我們需要將m-n區間范圍的鏈表進行截取,之后需要拼湊,因此需要記錄截取前的頭指針位置
④定義next節點,指向end節點的下一個節點,作用和③的功能類似,記錄截取后的尾結點的指針位置
3.2:畫圖
畫圖來表示一下:

3.3:代碼實現
/** * 使用 4 個指針變量的解法 * @param head * @param m * @param n * @return */ public ListNode reverseBetween(ListNode head, int m, int n) { // 因為有頭結點有可能發生變化,使用虛擬頭結點可以避免復雜的分類討論 ListNode dummyNode = new ListNode(-1); dummyNode.next = head; ListNode prev = dummyNode; // 第 1 步:從虛擬頭結點走 m - 1 步,來到 m 結點的前一個結點 // 建議寫在 for 循環里,語義清晰 for (int i = 0; i < m - 1; i++) { prev = prev.next; } // 第 2 步:從 prev 再走 n - m + 1 步,來到 n 結點 ListNode end = prev; for (int i = 0; i < n - m + 1; i++) { end = end.next; } // 第 3 步:切斷出一個子鏈表(截取鏈表) ListNode start = prev.next; ListNode next = end.next; prev.next = null; end.next = null; // 第 4 步:反轉子鏈表 reverse(start); // 第 5 步:接回到原來的鏈表中 prev.next = end; start.next = next; return dummyNode.next; } /** * 翻轉鏈表 * @param head */ private void reverse(ListNode head) { // 也可以使用遞歸反轉一個鏈表 ListNode pre = null; ListNode cur = head; // 在循環開始之前聲明,可以避免在循環中反復聲明新變量 ListNode next; while (cur != null) { next = cur.next; cur.next = pre; pre = cur; cur = next; } } }
3.4:測試用例
public static void main(String[] args) { ListNode node = new ListNode(1); node.next = new ListNode(2); node.next.next = new ListNode(3); node.next.next.next = new ListNode(4); node.next.next.next.next = new ListNode(5); node.next.next.next.next.next = new ListNode(6); node.next.next.next.next.next.next = new ListNode(7); ListNode resvers = new ReverseListNodeBetween().reverseBetween(node,3,5); System.out.println(resvers); }
輸出結果:

四:總結
鏈表作為經典的數據結構,本篇博客從反轉鏈表說起,然后分析了每k個一組翻轉和從m到n之間的翻轉,都是反轉的變形題目,難度也依次增大.其中可以看到解決鏈表的反轉問題本質上的最行之有效的方法就是通過定義不同作用的指針,來改變指針值從而改變鏈表的整個順序。對於這種思想的理解,也有助於我們理解鏈表這種數據結構。加油~
最后: 如果對學習java有興趣可以加入群:618626589,本群旨在打造無培訓廣告、無閑聊扯皮、無注水斗圖的純技術交流群,群里每天會分享有價值的問題和學習資料,歡迎各位隨時加入~

