終於又回到了咱們的算法習題講解了。南塵發現最近文章閱讀量明顯比之前少了很多,就上門請教小夥伴緣由。他們都說做爲一名 Android 應用開發工程師,實在是在工做中沒有接觸到算法。作技術這個東西,學習了仍是得練,不練過幾天必定會忘掉。java
不過想必你們讀南塵的文章也是深有所感,基本都是站在一個極其普通的程序員角度思考的,層次感也不會突如其來。因此,你們還望多多思考呀,算法這個東西是練出來的,不是看出來的。程序員
不過呢,不喜歡算法系列推文的小夥伴也大可沒必要擔憂,在算法以後的板塊就是 Java 基礎啦。面試
有句話說的好,面試造航母,入職擰螺絲。實際上也是這樣,面試官極難經過簡單的面試瞭解到你這我的的能力,而手寫算法倒是最適合看出一我的寫代碼的習慣和程序思惟的,這也是大公司以及很多小公司慢慢轉向算法面試的一個緣由吧~算法
好了,話很少說,咱們直接來看今天的面試題。學習
面試題:輸入兩個遞增排序的單鏈表,data 域爲 int 型值。合併這兩個鏈表,並使新鏈表中的結點也是按照遞增排序的。例如鏈表 A:1->3->5,鏈表 B:2->4,那它們合併後就是 1->2->3->4->5測試
看到這樣的題,咱們必定要學會先在心中想好測試用例,再思考程序邏輯。寫完程序邏輯後,再把本身事先想好的測試用例測試經過後再交給面試官。事實上,面試官也是事先準備了測試用例的。this
而對於測試用例,就必定要注意好以前南塵給你們講的邊界值。只有能經過邊界值、錯誤值的程序,才擁有足夠的健壯性。spa
咱們回到題幹,輸入的是兩個遞增排序的單鏈表,咱們須要合併它,獲得的新鏈表也是遞增排序的。在心中不難擁有這樣的思路。指針
咱們既然是重複的步驟,那咱們必定首先能想到遞歸的思路。咱們極容易獲得下面的代碼。code
public class Test14 { public static class Node { int data; Node next; Node(int data) { this.data = data; } } private static Node merge(Node head1, Node head2) { // 若是鏈表 1 爲 null ,新鏈表直接爲 2 鏈表; if (head1 == null) return head2; // 若是鏈表 2 爲 null,則新鏈表直接爲 1 鏈表 if (head2 == null) return head1; Node head = null; // 假設鏈表 1 的頭結點值小於等於鏈表 2;則直接把鏈表 1 的頭結點賦值爲新鏈表,並遞歸新的 1 鏈表 if (head1.data <= head2.data) { head = head1; head.next = merge(head1.next, head2); } else { // 不然,對鏈表 2 執行一樣的操做,並把脫離的值賦值上去 head = head2; head.next = merge(head1, head2.next); } return head; } public static void main(String[] args) { Node head1 = new Node(1); head1.next = new Node(3); head1.next.next = new Node(5); head1.next.next.next = new Node((7)); Node head2 = new Node(2); head2.next = new Node(4); head2.next.next = new Node(6); head2.next.next.next = new Node(8); Node head = merge(head1, head2); while (head != null) { System.out.print(head.data + "->"); head = head.next; } System.out.print("null"); } }
寫出了代碼後,天然不能忘了用事先想好的測試用例去測試一遍流程,這裏測試是沒有問題的。
事實上,這也是《劍指 Offer》的標準解法。遞歸解法有個好處,就是比較容易想到,但這應該是創建在建模能力比較強的基礎之上。但每每很多人會以爲遞歸特別繞,容易把人繞暈,並且在空間利用率上一直都表現很差,有的面試官就是不能接受遞歸的代碼,因此本題咱們更加推薦的是迭代的方式處理。
回到咱們前面的思路中,上面咱們用的方式是不斷「脫落」,而後把暴露出來的結點當作一個新的頭結點來處理,相信很多小夥伴不怎麼能接受這個思路。那咱們換個更好理解的方式,咱們就直接用咱們鏈表經常使用的指針移動的方式來處理,咱們看行不行。
因此咱們開始直接編寫。
private static Node merge(Node head1, Node head2) { if (head1 == null) return head2; if (head2 == null) return head1; // 上面的不用說,就是處理傳入值爲 null 的狀況 Node head = null; // 當兩個鏈表都不爲空就能夠比較大小來肯定接哪一個 while (head1 != null && head2 != null) { if (head1.data <= head2.data) { head = head1; head1 = head1.next; } else { head = head2; head2 = head2.next; } } // 若是第一個鏈表的元素未處理完畢,則把剩餘的鏈表接到最後一個鏈表後 if (head1 != null) head.next = head1; // 同理,若是第二個鏈表的元素未處理完畢,就把剩餘的鏈表接到新鏈表的尾結點 if (head2 != null) head.next = head2; return head; }
一樣寫完後,拿出咱們的測試用例開始測試,首先確定是功能測試。
功能測試就出了問題,咱們固然得思考如何修改,正常來講,咱們但願新鏈表的 1.next = 2,因此咱們確定不能直接用 head = head2 這樣的表達式來進行賦值。
咱們必定的有個相似 head.next = head2,而後用相似 head = head.next 這樣的方式向後遍歷纔是正確的。因此咱們修改一下代碼:
private static Node merge(Node head1, Node head2) { if (head1 == null) return head2; if (head2 == null) return head1; // 上面的不用說,就是處理傳入值爲 null 的狀況 // 爲了下面的 head.next,因此咱們首先確定得初始化一個 Node Node head = new Node(); // 當兩個鏈表都不爲空就能夠比較大小來肯定接哪一個 while (head1 != null && head2 != null) { if (head1.data <= head2.data) { head.next = head1; head1 = head1.next; } else { head.next = head2; head2 = head2.next; } head = head.next; } // 若是第一個鏈表的元素未處理完畢,則把剩餘的鏈表接到最後一個鏈表後 if (head1 != null) head.next = head1; // 同理,若是第二個鏈表的元素未處理完畢,就把剩餘的鏈表接到新鏈表的尾結點 if (head2 != null) head.next = head2; return head; }
再次進行功能測試:
因此咱們必須在 while 循環前面放頭結點的值。須要注意的是,因爲咱們要用到 head.next,因此咱們最後真正的頭結點其實是 head.next。
完整代碼以下:
public class Test14 { public static class Node { int data; Node next; Node(int data) { this.data = data; } Node() { } } private static Node merge(Node head1, Node head2) { if (head1 == null) return head2; if (head2 == null) return head1; // 上面的不用說,就是處理傳入值爲 null 的狀況 // 爲了下面的 head.next,因此咱們首先確定得初始化一個 Node Node head = new Node(); Node temp = head; // 當兩個鏈表都不爲空就能夠比較大小來肯定接哪一個 while (head1 != null && head2 != null) { if (head1.data <= head2.data) { head.next = head1; head1 = head1.next; } else { head.next = head2; head2 = head2.next; } head = head.next; } // 若是第一個鏈表的元素未處理完畢,則把剩餘的鏈表接到最後一個鏈表後 if (head1 != null) head.next = head1; // 同理,若是第二個鏈表的元素未處理完畢,就把剩餘的鏈表接到新鏈表的尾結點 if (head2 != null) head.next = head2; return temp.next; } public static void main(String[] args) { Node head1 = new Node(1); head1.next = new Node(3); head1.next.next = new Node(5); head1.next.next.next = new Node((7)); Node head2 = new Node(2); head2.next = new Node(4); head2.next.next = new Node(6); head2.next.next.next = new Node(8); Node head = merge(head1, head2); while (head != null) { System.out.print(head.data + "->"); head = head.next; } System.out.print("null"); } }
寫完後,仍是得過一遍測試用例。
基本仍是沒毛病,這時候咱們就能夠交上本身的答卷啦~
基本步驟很清晰:思考測試用例 => 思考程序邏輯 => 拿測試用例進去驗證 => 發現問題直接更改 => 測試用例驗證,注意邊界值 => 測試經過。
若是保持上面這樣的步驟,實在想不明白,在紙上畫畫思路,相信即便你沒有作到 100% 正確,你給面試官的感受也是足夠靠譜的。
好啦,今天的面試題就先到這裏,不知道你們有沒有被南塵繞暈呀,繞暈了沒事兒,多體驗幾回就行了。
仍是要提醒你們:千萬要本身去練習,看懂不思考,這樣的學習效率是極低的!
好啦,明天的習題咱們先放一下,別忘了先思考思考,並動手練練~
題目:輸入一個矩陣,按照從外向裏以順時針的順序依次打印每個數字。例如輸入:{{1,2,3},{4,5,6},{7,8,9}}則依次打印數字爲 一、二、三、六、九、八、七、四、5