鏈表是咱們數據結構面試中比較容易出錯的問題,因此不少面試官總喜歡在這上面下功夫,爲了不出錯,咱們最好先進行全面的分析。在實際軟件開發週期中,設計的時間一般不會比編碼的時間短,在面試的時候咱們不要着急於寫代碼,而是一開始仔細分析和設計,這將給面試官留下一個很好的印象。java
與其很快寫出一段千瘡百孔的代碼,不容仔細分析後再寫出健壯性無敵的程序。node
面試題:輸入一個單鏈表的頭結點,返回它的中間元素。爲了方便,元素值用整型表示。面試
當應聘者看到這道題的時候,心裏一陣狂喜,怎麼給本身遇到了這麼簡單的題。拿起筆就開始寫,先遍歷整個鏈表,拿到鏈表的長度 len,再次遍歷鏈表,位於 len/2 的元素就是鏈表的中間元素。算法
因此這個題最重要的點就是拿到鏈表的長度 len。而拿到這個 len 也比較簡單,只須要遍歷前設定一個 count 值,遍歷的時候 count++ ,第一次遍歷結束,就拿到單鏈表的長度 len 了。微信
因而咱們很快寫出了這樣的代碼:數據結構
public class Test15 {
public static class LinkNode {
int data;
LinkNode next;
public LinkNode(int data) {
this.data = data;
}
}
private static int getTheMid(LinkNode head) {
int count = 0;
LinkNode node = head;
while (head != null) {
head = head.next;
count++;
}
for (int i = 0; i < count / 2; i++) {
node = node.next;
}
return node.data;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
System.out.println(getTheMid(head));
}
}
複製代碼
面試官看到這個代碼的時候,他告訴咱們上面代碼循環了兩次,可是他期待的只有一次。學習
因而咱們絞盡腦汁,忽然想到了網上介紹過的一個概念:快慢指針法。this
假設咱們設置兩個變量 slow、fast 起始都指向單鏈表的頭結點當中,而後依次向後面移動,fast 的移動速度是 slow 的 2 倍。這樣當 fast 指向末尾節點的時候,slow 就正好在正中間了。編碼
想清楚這個思路後,咱們很快就能寫出以下代碼:spa
public class Test15 {
public static class LinkNode {
int data;
LinkNode next;
public LinkNode(int data) {
this.data = data;
}
}
private static int getTheMid(LinkNode head) {
LinkNode slow = head;
LinkNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow.data;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
System.out.println(getTheMid(head));
}
}
複製代碼
快慢指針法 確實在鏈表類面試題中特別好用,咱們不妨在這裏觸類旁通,對原題稍微修改一下,其實也能夠實現。
面試題:給定一個單鏈表的頭結點,判斷這個鏈表是不是循環鏈表。
和前面的問題同樣,咱們只須要定義兩個變量 slow,fast,同時從鏈表的頭結點出發,fast 每次走鏈表,而 slow 每次只走一步。若是走得快的指針追上了走得慢的指針,那麼鏈表就是環形(循環)鏈表。若是走得快的指針走到了鏈表的末尾(fast.next 指向 null)都沒有追上走得慢的指針,那麼鏈表就不是環形鏈表。
有了這樣的思路,實現代碼那還不是分分鐘的事兒。
public class Test15 {
public static class LinkNode {
int data;
LinkNode next;
public LinkNode(int data) {
this.data = data;
}
}
private static boolean isRingLink(LinkNode head) {
LinkNode slow = head;
LinkNode fast = head;
while (slow != null && fast != null && fast.next != null) {
if (slow == fast || fast.next = slow) {
return true;
}
fast = fast.next.next;
slow = slow.next;
}
return false;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
System.out.println(isRingLink(head));
head.next.next.next.next.next = head;
System.out.println(isRingLink(head));
}
}
複製代碼
確實有意思,快慢指針法 再一次利用它的優點巧妙解決了咱們的問題。
咱們上面講解的「快慢指針法」均是一個變量走 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 個結點。
咱們依然能夠在心中默認一遍代碼:
在心中默走了一遍代碼後,咱們顯然很容易寫出下面的代碼。
public class Test15 {
public static class LinkNode {
int data;
LinkNode next;
public LinkNode(int data) {
this.data = data;
}
}
private static int getSpecifiedNodeReverse(LinkNode head, int k) {
LinkNode slow = head;
LinkNode fast = head;
if (fast == null) {
throw new RuntimeException("your linkNode is null");
}
// 先讓 fast 先走 k-1 步
for (int i = 0; i < k - 1; i++) {
if (fast.next == null) {
// 說明輸入的 k 已經超過了鏈表長度,直接報錯
throw new RuntimeException("the value k is too large.");
}
fast = fast.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
return slow.data;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
System.out.println(getSpecifiedNodeReverse(head, 3));
System.out.println(getSpecifiedNodeReverse(null, 1));
}
}
複製代碼
鏈表類面試題,真是能夠玩出五花八門,當咱們用一個變量遍歷鏈表不能解決問題的時候,咱們能夠嘗試用兩個變量來遍歷鏈表,可讓其中一個變量遍歷的速度快一些,好比一次走兩步,或者是走若干步。咱們在遇到這類面試的時候,千萬不要自亂陣腳,學會理性分析問題。
本來是想給個人小夥伴說再見了,但惟恐你們還沒學到真本事,因此在這裏再留一個拓展題。
面試題:給定一個單鏈表的頭結點,刪除倒數第 k 個結點。
哈哈,和上面的題目僅僅只是把得到它的值變成了刪除,很多小夥伴確定都偷着樂了,但南塵仍是先提醒你們,不要太忘乎所以喲~
好啦,我們明天再見啦~
我是南塵,只作比心的公衆號,歡迎關注我。
作不完的開源,寫不完的矯情。歡迎掃描下方二維碼或者公衆號搜索「nanchen」關注個人微信公衆號,目前多運營 Android ,盡本身所能爲你提高。若是你喜歡,爲我點贊分享吧~