面試 7 快慢指標法玩轉鍊錶演算法面試(一)

2021-09-08 05:18:31 字數 3220 閱讀 7152

鍊錶是我們資料結構面試中比較容易出錯的問題,所以很多面試官總喜歡在這上面下功夫,為了避免出錯,我們最好先進行全面的分析。在實際軟體開發周期中,設計的時間通常不會比編碼的時間短,在面試的時候我們不要著急於寫**,而是一開始仔細分析和設計,這將給面試官留下乙個很好的印象。

與其很快寫出一段千瘡百孔的**,不容仔細分析後再寫出健壯性無敵的程式。

面試題:輸入乙個單鏈表的頭結點,返回它的中間元素。為了方便,元素值用整型表示。

當應聘者看到這道題的時候,內心一陣狂喜,怎麼給自己遇到了這麼簡單的題。拿起筆就開始寫,先遍歷整個鍊錶,拿到鍊錶的長度 len,再次遍歷鍊錶,位於 len/2 的元素就是鍊錶的中間元素。

所以這個題最重要的點就是拿到鍊錶的長度 len。而拿到這個 len 也比較簡單,只需要遍歷前設定乙個 count 值,遍歷的時候 count++ ,第一次遍歷結束,就拿到單鏈表的長度 len 了。

於是我們很快寫出了這樣的**:

public class test15  } private static int getthemid(linknode head)  for (int i = 0; i < count / 2; i++)  return node.data; } public static void main(string args)  } 

面試官看到這個**的時候,他告訴我們上面**迴圈了兩次,但是他期待的只有一次。

於是我們絞盡腦汁,突然想到了網上介紹過的乙個概念:快慢指標法。

假設我們設定兩個變數 slow、fast 起始都指向單鏈表的頭結點當中,然後依次向後面移動,fast 的移動速度是 slow 的 2 倍。這樣當 fast 指向末尾節點的時候,slow 就正好在正中間了。

想清楚這個思路後,我們很快就能寫出如下**:

public class test15  } private static int getthemid(linknode head)  return slow.data; } public static void main(string args)  } 

快慢指標法舉一反三

快慢指標法 確實在鍊錶類面試題中特別好用,我們不妨在這裡舉一反三,對原題稍微修改一下,其實也可以實現。

面試題:給定乙個單鏈表的頭結點,判斷這個鍊錶是否是迴圈鍊錶。

和前面的問題一樣,我們只需要定義兩個變數 slow,fast,同時從鍊錶的頭結點出發,fast 每次走鍊錶,而 slow 每次只走一步。如果走得快的指標追上了走得慢的指標,那麼鍊錶就是環形(迴圈)鍊錶。如果走得快的指標走到了鍊錶的末尾(fast.next 指向 null)都沒有追上走得慢的指標,那麼鍊錶就不是環形鍊錶。

有了這樣的思路,實現**那還不是分分鐘的事兒。

public class test15  } private static boolean isringlink(linknode head)  fast = fast.next.next; slow = slow.next; } return false; } public static void main(string args)  } 

確實有意思,快慢指標法 再一次利用它的優勢巧妙解決了我們的問題。

快慢指標法的延展

我們上面講解的「快慢指標法」均是乙個變數走 1 步,乙個變數走 n 步。我們其實還可以拓展它。這個「快慢」並不是說一定要同時遍歷。

比如《劍指offer》中的第 15 道面試題,就運用到了「快慢指標法」的延展。

面試題:輸入乙個單鏈表的頭結點,輸出該鍊錶中倒數第 k 個節點的值。

初一看這個似乎並不像我們前面學習到的「快慢指標法」的考察。所以大多數人就迷糊了,進入到常規化思考。依然還是設定乙個整型變數 count,然後每次迴圈的時候 count++,拿到鍊錶的長度 n。那麼倒數第 k 個節點也就是順數第 n-k+1 個結點。所以我們只需要在拿到長度 n 後再進行一次 n-k+1 次迴圈就可以拿到這個倒數第 k 個節點的值了。

但面試官顯然不會太滿意這個臃腫的解法,他依然希望我們一次迴圈就能搞定這個事。

為了實現只遍歷一次鍊錶就能找到倒數第 k 個結點,我們依然可以定義兩個遍歷 slow 和 fast。我們讓 fast 變數先往前遍歷 k-1 步,slow 保持不動。從第 k 步開始,slow 變數也跟著 fast 變數從鍊錶的頭結點開始遍歷。由於兩個變數指向的結點距離始終保持在 k-1,那麼當 fast 變數到達鍊錶的尾結點的時候,slow 變數指向的結點正好是我們所需要的倒數第 k 個結點。

我們依然可以在心中預設一遍**:

假設輸入的鍊錶是:1->2->3->4->5;

現在我們要求倒數第三個結點的值,即順數第 3 個結點,它的值為 3;

定義兩個變數 slow、fast,它們均指向結點 1;

先讓 fast 向前走 k-1 即 2 步,這時候 fast 指向了第 3 個結點,它的值是 3;

現在 fast 和 slow 同步向右移動;

fast 再經過了 2 步到達了鍊錶尾結點;fast 正好指向了第 3 個結點,這顯然是符合我們的猜想的。

在心中默走了一遍**後,我們顯然很容易寫出下面的**。

public class test15  } private static int getspecifiednodereverse(linknode head, int k)  // 先讓 fast 先走 k-1 步 for (int i = 0; i < k - 1; i++)  fast = fast.next; } while (fast.next != null)  return slow.data; } public static void main(string args)  } 

總結

鍊錶類面試題,真是可以玩出五花八門,當我們用乙個變數遍歷鍊錶不能解決問題的時候,我們可以嘗試用兩個變數來遍歷鍊錶,可以讓其中乙個變數遍歷的速度快一些,比如一次走兩步,或者是走若干步。我們在遇到這類面試的時候,千萬不要自亂陣腳,學會理性分析問題。

原本是想給我的小夥伴說再見了,但唯恐大家還沒學到真本事,所以在這裡再留乙個拓展題。

面試題:給定乙個單鏈表的頭結點,刪除倒數第 k 個結點。

哈哈,和上面的題目僅僅只是把獲得它的值變成了刪除,不少小夥伴肯定都偷著樂了,但南塵還是先提醒大家,不要太得意忘形喲~

好啦,咱們明天再見啦~

環形鍊錶(快慢指標法)

給定乙個鍊錶,判斷鍊錶中是否有環。如果鍊錶中有某個節點,可以通過連續跟蹤 next 指標再次到達,則鍊錶中存在環。為了表示給定鍊錶中的環,我們使用整數 pos 來表示鍊錶尾連線到鍊錶中的位置 索引從 0 開始 如果 pos 是 1,則在該鍊錶中沒有環。注意 pos 不作為引數進行傳遞,僅僅是為了標識...

鍊錶 尋找中間結點(快慢指標法)

一般方法 鍊錶這種無法隨機訪問的資料結構,要定位到中間結點的方法通常是先把整個鍊錶遍歷一遍,得到整個鍊錶的長度,再除以2,得到中間結點的位置,接著根據這個位置從頭結點開始找到這個結點,時間複雜度為o 1.5n 具體 如下 definition for singly linked list.struc...

演算法練習 回文鍊錶 快慢指標 鍊錶翻轉

題目 編寫乙個函式,檢查輸入的鍊錶是否是回文的。示例 輸入 1 2 輸出 false個人思路 回文鍊錶即字元左右對稱,但是鍊錶是單向的,這時我們就要讓鍊錶後半部分進行翻轉,首先遍歷鍊錶獲得長度,然後讓快指標走到鍊錶的後半部分開頭,然後開始進行後半部分的鍊錶翻轉,翻轉結束後快指標走到鍊錶的結尾,這時讓...