面試10大算法彙總+常見題目解答(Java)

以下從Java的角度總結了面試常見的算法和數據結構:字符串,鏈表,樹,圖,排序,遞歸 vs. 迭代,動態規劃,位操作,概率問題,排列組合,以及一些需要尋找規律的題目。

1. 字符串、數組和矩陣

首先需要注意的是和C++不同,Java字符串不是char數組。沒有IDE代碼自動補全功能,應該記住下面這些常用的方法。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. toCharArray()  //獲得字符串對應的char數組  
  2. Arrays.sort()  //數組排序  
  3. Arrays.toString(char[] a) //數組轉成字符串  
  4. charAt(int x)  //獲得某個索引處的字符  
  5. length()       //字符串長度  
  6. length         //數組大小  
  7. substring(int beginIndex)   
  8. substring(int beginIndex, int endIndex)  
  9. Integer.valueOf() //string to integer  
  10. String.valueOf()  //integer to string  

字符串和數組本身很簡單,但是相關的題目需要更復雜的算法來解決。比如說動態規劃,搜索等。

經典題目:
1) Evaluate Reverse Polish Notation
2) Longest Palindromic Substring
3) Word Break
4) Word Ladder
5) Median of Two Sorted Arrays
6) Regular Expression Matching
7) Merge Intervals
8) Insert Interval
9) Two Sum
9) 3Sum
9) 4Sum
10) 3Sum Closest
11) String to Integer
12) Merge Sorted Array
13) Valid Parentheses
14) Implement strStr()
15) Set Matrix Zeroes
16) Search Insert Position
17) Longest Consecutive Sequence
18) Valid Palindrome
19) Spiral Matrix
20) Search a 2D Matrix
21) Rotate Image
22) Triangle
23) Distinct Subsequences Total
24) Maximum Subarray
25) Remove Duplicates from Sorted Array
26) Remove Duplicates from Sorted Array II
27) Longest Substring Without Repeating Characters
28) Longest Substring that contains 2 unique characters
29) Palindrome Partitioning

2. 鏈表

在Java中,鏈表的實現非常簡單,每個節點Node都有一個值val和指向下個節點的鏈接next。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class Node {  
  2.     int val;  
  3.     Node next;  
  4.   
  5.     Node(int x) {  
  6.         val = x;  
  7.         next = null;  
  8.     }  
  9. }  

鏈表兩個著名的應用是棧Stack和隊列Queue。在Java標準庫中都有實現,一個是Stack,另一個是LinkedList(Queue是它實現的接口)。

Stack

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class Stack{  
  2.     Node top;   
  3.   
  4.     public Node peek(){  
  5.         if(top != null){  
  6.             return top;  
  7.         }  
  8.   
  9.         return null;  
  10.     }  
  11.   
  12.     public Node pop(){  
  13.         if(top == null){  
  14.             return null;  
  15.         }else{  
  16.             Node temp = new Node(top.val);  
  17.             top = top.next;  
  18.             return temp;      
  19.         }  
  20.     }  
  21.   
  22.     public void push(Node n){  
  23.         if(n != null){  
  24.             n.next = top;  
  25.             top = n;  
  26.         }  
  27.     }  
  28. }  

Queue

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class Queue{  
  2.     Node first, last;  
  3.   
  4.     public void enqueue(Node n){  
  5.         if(first == null){  
  6.             first = n;  
  7.             last = first;  
  8.         }else{  
  9.             last.next = n;  
  10.             last = n;  
  11.         }  
  12.     }  
  13.   
  14.     public Node dequeue(){  
  15.         if(first == null){  
  16.             return null;  
  17.         }else{  
  18.             Node temp = new Node(first.val);  
  19.             first = first.next;  
  20.             return temp;  
  21.         }     
  22.     }  
  23. }  

經典題目:
1) Add Two Numbers
2) Reorder List
3) Linked List Cycle
4) Copy List with Random Pointer
5) Merge Two Sorted Lists
6) Merge k Sorted Lists *
7) Remove Duplicates from Sorted List
8) Partition List
9) LRU Cache

3. 樹和堆

這裏的樹通常是指二叉樹,每個節點都包含一個左孩子節點和右孩子節點,如下所示:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class TreeNode{  
  2.     int value;  
  3.     TreeNode left;  
  4.     TreeNode right;  
  5. }  

下面是與樹相關的一些概念:
二叉搜索樹:左結點 <= 中結點 <= 右結點
平衡 vs. 非平衡:平衡二叉樹中,每個節點的左右子樹的深度相差至多爲1(1或0)。
滿二叉樹(Full Binary Tree):除葉子節點以外的每個節點都有兩個孩子。
完美二叉樹(Perfect Binary Tree):是具有下列性質的滿二叉樹:所有的葉子節點都有相同的深度或處在同一層次,且每個父節點都必須有兩個孩子。
完全二叉樹(Complete Binary Tree):二叉樹中,可能除了最後一層,每一層都被完全填滿,且所有節點都必須儘可能向左靠。

堆是一種特殊的基於樹的數據結構,滿足堆屬性。其操作的時間複雜度是很重要的(例如查找最小,刪除最小,插入等)。在Java中,知道PriorityQueue很重要。

經典題目:
1) Binary Tree Preorder Traversal 
2) Binary Tree Inorder Traversal
3) Binary Tree Postorder Traversal
4) Word Ladder
5) Validate Binary Search Tree
6) Flatten Binary Tree to Linked List
7) Path Sum
8) Construct Binary Tree from Inorder and Postorder Traversal
9) Convert Sorted Array to Binary Search Tree
10) Convert Sorted List to Binary Search Tree
11) Minimum Depth of Binary Tree
12) Binary Tree Maximum Path Sum *
13) Balanced Binary Tree

4. 圖

圖相關的問題主要集中在深度優先搜索(depth first search)和廣度優先搜索(breath first search)。深度優先搜索很簡單,廣度優先要注意使用queue存儲節點。下面是一個簡單的用隊列Queue實現的廣度優先搜索。

1) 定義圖節點

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class GraphNode{   
  2.     int val;  
  3.     GraphNode next;  
  4.     GraphNode[] neighbors;  
  5.     boolean visited;  
  6.   
  7.     GraphNode(int x) {  
  8.         val = x;  
  9.     }  
  10.   
  11.     GraphNode(int x, GraphNode[] n){  
  12.         val = x;  
  13.         neighbors = n;  
  14.     }  
  15.   
  16.     public String toString(){  
  17.         return "value: "this.val;   
  18.     }  
  19. }  

2) 定義Queue

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class Queue{  
  2.     GraphNode first, last;  
  3.   
  4.     public void enqueue(GraphNode n){  
  5.         if(first == null){  
  6.             first = n;  
  7.             last = first;  
  8.         }else{  
  9.             last.next = n;  
  10.             last = n;  
  11.         }  
  12.     }  
  13.   
  14.     public GraphNode dequeue(){  
  15.         if(first == null){  
  16.             return null;  
  17.         }else{  
  18.             GraphNode temp = new GraphNode(first.val,   
  19.                                          first.neighbors);  
  20.             first = first.next;  
  21.             return temp;  
  22.         }     
  23.     }  
  24. }  

3) 使用Queue的BFS

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public class GraphTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         GraphNode n1 = new GraphNode(1);   
  5.         GraphNode n2 = new GraphNode(2);   
  6.         GraphNode n3 = new GraphNode(3);   
  7.         GraphNode n4 = new GraphNode(4);   
  8.         GraphNode n5 = new GraphNode(5);   
  9.   
  10.         n1.neighbors = new GraphNode[]{n2,n3,n5};  
  11.         n2.neighbors = new GraphNode[]{n1,n4};  
  12.         n3.neighbors = new GraphNode[]{n1,n4,n5};  
  13.         n4.neighbors = new GraphNode[]{n2,n3,n5};  
  14.         n5.neighbors = new GraphNode[]{n1,n3,n4};  
  15.   
  16.         breathFirstSearch(n1, 5);  
  17.     }  
  18.   
  19.     public static void breathFirstSearch(GraphNode root, int x){  
  20.         if(root.val == x)  
  21.             System.out.println("find in root");  
  22.   
  23.         Queue queue = new Queue();  
  24.         root.visited = true;  
  25.         queue.enqueue(root);  
  26.   
  27.         while(queue.first != null){  
  28.             GraphNode c = (GraphNode) queue.dequeue();  
  29.             for(GraphNode n: c.neighbors){  
  30.   
  31.                 if(!n.visited){  
  32.                     System.out.print(n + " ");  
  33.                     n.visited = true;  
  34.                     if(n.val == x)  
  35.                         System.out.println("Find "+n);  
  36.                     queue.enqueue(n);  
  37.                 }  
  38.             }  
  39.         }  
  40.     }  
  41. }  

輸出:

value: 2 value: 3 value: 5 Find value: 5
value: 4

經典題目:複製圖(Clone Graph)

5. 排序

下面是不同排序算法的時間複雜度,你可以去維基上看一下這些算法的基本思想。

算法 平均時間複雜度 最壞時間複雜度 輔助空間
冒泡排序(Bubble sort) n^2 n^2 1
選擇排序(Selection sort) n^2 n^2 1
插入排序(Insertion sort) n^2 n^2  
快速排序(Quick sort) n log(n) n^2  
歸併排序(Merge sort) n log(n) n log(n) depends

* 另外還有BinSort, RadixSort和CountSort 三種比較特殊的排序。

(此處可見我的整理:http://www.lilongdream.com/2014/04/10/83.html

你可能想看看 how developers sort in Java 。

經典題目:MergesortQuicksortInsertionSort.

6. 遞歸 vs. 迭代

對程序員來說,遞歸應該是一個與生俱來的思想(a built-in thought),可以通過一個簡單的例子來說明。

問題:

有n步臺階,一次只能上1步或2步,共有多少種走法。

步驟1:找到走完前n步臺階和前n-1步臺階之間的關係。

爲了走完n步臺階,只有兩種方法:從n-1步臺階爬1步走到或從n-2步臺階處爬2步走到。如果f(n)是爬到第n步臺階的方法數,那麼f(n) = f(n-1) + f(n-2)。

步驟2:確保開始條件是正確的。

f(0) = 0;
f(1) = 1;

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static int f(int n){  
  2.     if(n <= 2return n;  
  3.     int x = f(n-1) + f(n-2);  
  4.     return x;  
  5. }  

遞歸方法的時間複雜度是指數級的,因爲有很多冗餘的計算:

f(5)
f(4) + f(3)
f(3) + f(2) + f(2) + f(1)
f(2) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)
f(1) + f(0) + f(1) + f(1) + f(0) + f(1) + f(0) + f(1)

直接的想法是將遞歸轉換爲迭代:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static int f(int n) {  
  2.   
  3.     if (n <= 2){  
  4.         return n;  
  5.     }  
  6.   
  7.     int first = 1, second = 2;  
  8.     int third = 0;  
  9.   
  10.     for (int i = 3; i <= n; i++) {  
  11.         third = first + second;  
  12.         first = second;  
  13.         second = third;  
  14.     }  
  15.   
  16.     return third;  
  17. }  

這個例子迭代花費的時間更少,你可能想看看兩者的區別 Recursion vs Iteration

7. 動態規劃

動態規劃是解決具有下面這些性質問題的技術:

  1. 一個問題可以通過解決更小子問題來解決,或者說問題的最優解包含了其子問題的最優解
  2. 有些子問題的解可能需要計算多次
  3. 子問題的解存儲在一張表格裏,這樣每個子問題只需計算一次
  4. 需要額外的空間以節省時間

爬臺階問題完全符合上面的四條性質,因此可以用動態規劃法來解決。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static int[] A = new int[100];  
  2.   
  3. public static int f3(int n) {  
  4.     if (n <= 2)  
  5.         A[n]= n;  
  6.   
  7.     if(A[n] > 0)  
  8.         return A[n];  
  9.     else  
  10.         A[n] = f3(n-1) + f3(n-2); //存儲結果,只計算一次!  
  11.     return A[n];  
  12. }  

經典題目:

1) Edit Distance
2) Longest Palindromic Substring
3) Word Break
4) Maximum Subarray

8. 位操作

常用位操作符:

OR (|) AND (&) XOR (^) Left Shift (<<) Right Shift (>>) Not (~)
1|0=1 1&0=0 1^0=1 0010<<2=1000 1100>>2=0011 ~1=0

用一個題目來理解這些操作:獲得給定數字n的第i位:(i從0計數並從右邊開始)

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static boolean getBit(int num, int i){  
  2.     int result = num & (1<<i);  
  3.   
  4.     if(result == 0){  
  5.         return false;  
  6.     }else{  
  7.         return true;  
  8. }  

例如,獲得數字10的第2位:

i=1, n=10
1<<1= 10
1010&10=10
10 is not 0, so return true;

9. 概率問題

解決概率相關的問題通常需要先分析問題,下面是一個簡單的例子:

一個房間裏有50個人,那麼至少有兩個人生日相同的概率是多少?(忽略閏年的事實,也就是一年365天)

計算某些事情的概率很多時候都可以轉換成先計算其相對面。在這個例子裏,我們可以計算所有人生日都互不相同的概率,也就是:365/365 * 364/365 * 363/365 * … * (365-49)/365,這樣至少兩個人生日相同的概率就是1 – 這個值。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public static double caculateProbability(int n){  
  2.     double x = 1;   
  3.   
  4.     for(int i=0; i<n; i++){  
  5.         x *=  (365.0-i)/365.0;  
  6.     }  
  7.   
  8.     double pro = Math.round((1-x) * 100);  
  9.     return pro/100;  
  10. }  

calculateProbability(50) = 0.97

經典題目:桶中取球

10. 排列組合

組合和排列的區別在於次序是否關鍵。

例1:

1、2、3、4、5這5個數字,用java寫一個方法,打印出所有不同的排列, 如:51234、41235等。要求:"4″不能在第三位,"3″與”5″不能相連。

例2:

5個香蕉,4個梨子,3個蘋果。同一種水果都是一樣的,這些水果有多少種不同的組合情況。

經典問題:

1) Permutations
2) Permutations II 
3) Permutation Sequence

11. 其他類型的題目

主要是不能歸到上面10大類的。需要尋找規律,然後解決問題。

經典題目:

1) Reverse Integer
2) Palindrome Number
3) Pow(x,n)
4) Subsets
5) Subsets II

參考/推薦的資料:
1. Binary tree
2. Introduction to Dynamic Programming
3. UTSA Dynamic Programming slides
4. Birthday paradox
5. Cracking the Coding Interview: 150 Programming InterviewQuestions and Solutions, Gayle Laakmann McDowell
6. Counting sort
7. LeetCode Online Judge


發佈了52 篇原創文章 · 獲贊 18 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章