第二章 面試須要的基礎知識

2.1 面試官談基礎知識
  • 基礎很重要
  • 算法、複雜的
  • 編程能力
  • 數據結構
2.2 編程語言
  • 程序員寫代碼老是基於某一種編程語言,所以技術面試的時候直接或者間接都會涉及至少一種編程語言。在面試的過程當中,面試官要麼直接問語言的語法,要麼讓應聘者用-~種編程語言寫代碼解決一個問題,經過寫出的代碼來判斷應聘者對他使用的語言的掌握程度。如今流行的編程語言不少,不一樣公司開發用的語言也不盡相同。作底層開發好比常常寫驅動的人更習慣用C, Linux下有不少程序員用C++開發應用程序,基於Windows的C#項目已經愈來愈多,跨平臺開發的程序員則可能更喜歡Java,隨着蘋果iPad、iPhone 的熱銷已經有不少程序員投向了Objective C的陣營,同時還有不少人喜歡用腳本語言如Perl、Python 開發短小精緻的小應用軟件。所以,不一樣公司面試的時候對編程語言的要求也有所不一樣。每一種編程語言均可以寫出一本大部頭的書籍,本書限於篇幅不可能面面俱到。本書中全部代碼都用C/C++/C#實現,下 面簡要介紹一些C++/C#常見的面試題。
 
面試題1:賦值運算符函數
  • 題目:以下爲類型CMyString的聲明,請爲該類型添加賦值運算符函數。
  • 思路
    • 將返回值類型聲明爲該類型的引用
    • 把傳入的參數類型聲明爲常量引用
    • 釋放實例自身已有的內存
    • 判斷傳入的參數和當前的實例是否是同一個實例
  • 不適用java
 
面試題2:實現Singleton模式
  • 題目:設計一個類,咱們只能生成該類的一個實例。
    • http://www.javashuo.com/article/p-fpcfqeab-gh.html
    • 1. 懶漢模式
    • public class SingletonDemo {
    •     private static SingletonDemo instance;
    •     private SingletonDemo(){
    •     }
    •     public static SingletonDemo getInstance(){
    •         if(instance==null){
    •             instance=new SingletonDemo();
    •         }
    •         return instance;
    •     }
    • }
    • 如上,經過提供一個靜態的對象instance,利用private權限的構造方法和getInstance()方法來給予訪問者一個單例。缺點是,沒有考慮到線程安全,可能存在多個訪問者同時訪問,並同時構造了多個對象的問題。之因此叫作懶漢模式,主要是由於此種方法能夠很是明顯的lazy loading。針對懶漢模式線程不安全的問題,咱們天然想到了,在getInstance()方法前加鎖,因而就有了第二種實現。
  • 2. 線程安全的懶漢模式
    • public class SingletonDemo {
    •     private static SingletonDemo instance;
    •     private SingletonDemo(){
    •     }
    •     public static synchronized SingletonDemo getInstance(){
    •         if(instance==null){
    •             instance=new SingletonDemo();
    •         }
    •         return instance;
    •     }
    • }
    • 然而併發實際上是一種特殊狀況,大多時候這個鎖佔用的額外資源都浪費了,這種打補丁方式寫出來的結構效率很低。
  • 3. 餓漢模式
    • public class SingletonDemo {
    • private static SingletonDemo instance=new SingletonDemo();
    •     private SingletonDemo(){
    •     }
    •     public static SingletonDemo getInstance(){
    •         return instance;
    •     }
    • }
    • 直接在運行這個類的時候進行一次loading,以後直接訪問。顯然,這種方法沒有起到lazy loading的效果,考慮到前面提到的和靜態類的對比,這種方法只比靜態類多了一個內存常駐而已。
  • 4. 靜態類內部加載
    • public class SingletonDemo {
    •     private static class SingletonHolder{
    •         private static SingletonDemo instance=new SingletonDemo();
    •     }
    •     private SingletonDemo(){
    •         System.out.println("Singleton has loaded");
    •     }
    •     public static SingletonDemo getInstance(){
    •         return SingletonHolder.instance;
    •     }
    • }
    • 使用內部類的好處是,靜態內部類不會在單例加載時就加載,而是在調用getInstance()方法時才進行加載,達到了相似懶漢模式的效果,而這種方法又是線程安全的。
  • 本題考點:
    • 考查對單例(Singleton) 模式的理解。
    • 考查對Java的基礎語法的理解,如靜態構造函數等。
    • 考查對多線程編程的理解。
 
2.3 數據結構
    數據結構一直是技術面試的重點,大多數面試題都是圍繞着數組、字符串、鏈表、樹、棧及隊列這幾種常見的數據結構展開的,所以每個應聘者都要熟練掌握這幾種數據結構。
    數組和字符串是兩種最基本的數據結構,它們用連續內存分別存儲數字和字符。鏈表和樹是面試中出現頻率最高的數據結構。因爲操做鏈表和樹須要操做大量的指針,應聘者在解決相關問題的時候-要留意代碼的魯棒性,不然容易出現程序崩潰的問題。是一個與遞歸緊密相關的數據結構,一樣隊列也與廣度優先遍歷算法緊密相關。深入理解這兩種數據結構能幫助咱們解決不少算法問題。
 
2.3.1 數組
面試題3:二維數組中的查找
  • 題目:在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
  • 思路:從右上角或左下角開始找,逐行刪除,或者用二分法查找
  • 代碼實現 右上角開始實現
    •  publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • int[][] arr= { { 1, 2, 8, 9 }, { 2, 4, 9, 12 }, { 4, 7, 10, 13 }, { 6, 8, 11, 15 } };
    • System.out.println(find(arr, 7));
    • }
    • publicstaticbooleanfind(int[][] array, inttarget) {
    • if(array== null) {
    • returnfalse;
    • }
    • introw= 0;
    • intcolumn= array[0].length- 1;
    • while(row< array.length&& column>= 0) {
    • if(array[row][column] == target) {
    • returntrue;
    • }
    • if(array[row][column] > target) {
    • column--;
    • } else{
    • row++;
    • }
    • }
    • returnfalse;
    • }
    • }
  • 測試用例:
    • 二維數組中包含查找的數字(查找的數字是數組中的最大值和最小值,查找的數字介於數組中的最大值和最小值之間)。
    • 二維數組中沒有查找的數字(查找的數字大於數組中的最大值,查找的數字小於數組中的最小值,查找的數字在數組的最大值和最小值之間但數組中沒有這個數字)。
    • 特殊輸入測試(輸入空指針)。
  • 本題考點:
    • 考查應聘者對二維數組的理解及編程能力。二維數組在內存中佔據連續的空間。在內存中從上到下存儲各行元素,在同一-行中按照從左到右的順序存儲。所以咱們能夠根據行號和列號計算出相對於數組首地址的偏移量,從而找到對應的元素。
    • 考查應聘者分析問題的能力。當應聘者發現問題比較複雜時,能不能經過具體的例子找出其中的規律,是可否解決這個問題的關鍵所在。這個題目只要從一個具體的二維數組的右上角開始分析,就能找到查找的規律,從而找到解決問題的突破口。
 
2.3.2 字符串
面試題4:替換空格
  • 題目:請實現一個函數,把字符串中的每一個空格替換成"%20"。例如輸入「We are happy.",則輸出「We%20are%20happy.」。
  • 思路:從後往前複製,數組長度會增長,或使用StringBuilder、StringBuffer類 , 先遍歷空格個數直接擴容
  • 代碼實現
    •  publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(replaceSpace(newStringBuffer("We Are Happy.")));
    • }
    • publicstaticString replaceSpace(StringBuffer str) {
    • if(str== null)
    • returnnull;
    • StringBuilder sb= newStringBuilder();
    • for(inti= 0; i< str.length(); i++) {
    • if(String.valueOf(str.charAt(i)).equals(" ")) {
    • sb.append("%20");
    • } else{
    • sb.append(str.charAt(i));
    • }
    • }
    • returnString.valueOf(sb);
    • }
    • }
  • 方法二
    • "We Are Happy.".replace(" ", "%20")

  • 測試用例:
    • 輸入的字符串中包含空格(空格位於字符串的最前面,空格位於字符串的最後面,空格位於字符串的中間,字符串中有連續多個空格)。
    • 輸入的字符串中沒有空格。
    • 特殊輸入測試(字符串是個NULL指針、字符串是個空字符串、字符串只有一個空格字符、字符串中只有連續多個空格)。
  • 本題考點:
    • 考查對字符串的編程能力。
    • 考查分析時間效率的能力。咱們要能清晰地分析出兩種不一樣方法的時間效率各是多少。
    • 考查對內存覆蓋是否有高度的警戒。在分析得知字符串會變長之後,咱們可以意識到潛在的問題,並主動和麪試官溝通以尋找問題的解決方案。
    • 考查思惟能力。在從前到後替換的思路被面試官否認以後,咱們能迅速想到從後往前替換的方法,這是解決此題的關鍵。
  • 相關題目:
    • 有兩個排序的數組A1和A2,內存在A1的末尾有足夠多的空餘空間容納A2。請實現一個函數,把A2中的全部數字插入到A1中而且全部的數字是排序的。和前面的例題同樣,不少人首先想到的辦法是在A1中從頭至尾複製數字,但這樣就會出現屢次複製一個數字的狀況。更好的辦法是從尾到頭比較A1和A2中的數字,並把較大的數字複製到A1的合適位置。

 

  • 觸類旁通:
  • 合併兩個數組(包括字符串)時,若是從前日後複製每一個數字(或字符)須要重複移動數字(或字符)屢次,那麼咱們能夠考慮從後往前複製,這樣就能減小移動的次數,從而提升效率。
2.3.3 鏈表
    咱們說鏈表是一種動態數據結構,是由於在建立鏈表時,無須知道鏈
表的長度。當插入一一個結點時,咱們只須要爲新結點分配內存,而後調整指針的指向來確保新結點被連接到鏈表當中。內存分配不是在建立鏈表時.次性完成,而是每添加一個結點分配一 次內存。因爲沒有閒置的內存,鏈表的空間效率比數組高。
面試題5:從尾到頭打印鏈表
  • 題目:輸入一個鏈表的頭結點,從尾到頭反過來打印出每一個結點的值。
  • 面試小提示:在面試中若是咱們打算修改輸入的數據,最好先問面試官是否是容許作修改。
  • 思路:藉助棧實現,或使用遞歸的方法。
  • 代碼實現
    • publicArrayList<Integer> printListFromTailToHead(ListNodelistNode) {
    • ArrayList<Integer> list= newArrayList<>();
    • if(listNode== null)
    • returnlist;
    • Stack<ListNode> stack= newStack<>();
    • while(listNode!= null) {
    • stack.push(listNode);
    • listNode= listNode.next;
    • }
    • while(!stack.isEmpty()) {
    • list.add(stack.pop().val);
    • }
    • returnlist;
    • }
  • 簡單演示 棧
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • ArrayList<Integer> arr= newArrayList<Integer>();
    • arr.add(1);
    • arr.add(2);
    • arr.add(3);
    • arr.add(4);
    • arr.add(5);
    • printListFromTailToHead(arr);
    • }
    • publicstaticvoidprintListFromTailToHead(ArrayList<Integer> list) {
    • Stack<Integer> stack= newStack<Integer>();
    • for(Integer integer: list) {
    • stack.push(integer);
    • }
    • while(!stack.isEmpty()) {
    • System.out.println(stack.pop());
    • }
    • }
    • }
  • 簡單演示 遞歸
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • int[] arr= {1,2,3,4,5};
    • printListFromTailToHead(arr,0);
    • }
    • publicstaticvoidprintListFromTailToHead(int[] arr,intnum) {
    • if(arr.length> num){
    • printListFromTailToHead(arr,num+1);
    • System.out.println(arr[num]);
    • }
    • }
    • }
  • 測試用例:
    • 功能測試(輸入的鏈表有多個結點,輸入的鏈表只有一個結點)。
    • 特殊輸入測試(輸入的鏈表頭結點指針爲NULL)。
  • 本題考點:
    • 考查對單項鍊表的理解和編程能力。
    • 考查對循環、遞歸和棧3個相互關聯的概念的理解。
2.3.4 樹
    樹是一種在實際編程中常常遇到的數據結構。它的邏輯很簡單:除了根結點以外每一個結點只有一個父結點,根結點沒有父結點;除了葉結點以外全部結點都有一個或多個子結點,葉結點沒有子結點。父結點和子結點之間用指針連接。因爲樹的操做會涉及大量的指針,所以與樹有關的面試題都不太容易。當面試官想考查應聘者在有複雜指針操做的狀況下寫代碼的能力,他每每會想到用與樹有關的面試題。
    面試的時候提到的樹,大部分都是二叉樹。所謂二叉樹是樹的一種特殊結構,在二叉樹中每一個結點最多隻能有兩個子結點。在二叉樹中最重要的操做莫過於遍歷,即按照某一-順序訪問樹中的全部結點。一般樹有以下幾種遍歷方式:
  • 前序遍歷:先訪問根結點,再訪問左子結點,最後訪問右子結點。圖2.5中的二叉樹的前序遍歷的順序是十、六、四、八、1四、十二、16。
  • 中序遍歷:先訪問左子結點,再訪問根結點,最後訪問右子結點。圖2.5中的二叉樹的中序遍歷的順序是四、六、八、十、十二、1四、16。
  • 後序遍歷:先訪問左子結點,再訪問右子結點,最後訪問根結點。圖2.5中的二叉樹的後序遍歷的順序是四、八、六、十二、1六、1四、10。
 
面試題6:重建二叉樹
  • 題目:輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
  • 思路:先找出根節點,而後利用遞歸方法構造二叉樹
  • 代碼實現
    • classTreeNode {
    • intval;
    • TreeNode left;
    • TreeNode right;
    • TreeNode(intx) {
    • val= x;
    • }
    • }
    • publicclassTestc {
    • publicTreeNode reConstructBinaryTree(int[] pre, int[] in) {
    • if(pre== null|| in== null) {
    • returnnull;
    • }
    • if(pre.length== 0 || in.length== 0) {
    • returnnull;
    • }
    • if(pre.length!= in.length) {
    • returnnull;
    • }
    • TreeNode root= newTreeNode(pre[0]);
    • for(inti= 0; i< pre.length; i++) {
    • if(pre[0] == in[i]) {
    • root.left= reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+ 1), Arrays.copyOfRange(in, 0, i));
    • root.right= reConstructBinaryTree(Arrays.copyOfRange(pre, i+ 1, pre.length),
    • Arrays.copyOfRange(in, i+ 1, in.length));
    • }
    • }
    • returnroot;
    • }
    • }
  • 測試用例:
    • 普通二叉樹(徹底二叉樹,不徹底二叉樹)。
    • 特殊二叉樹(全部結點都沒有右子結點的二叉樹,全部結點都沒有左子結點的二叉樹,只有一一個結點的二叉樹)。
    • 特殊輸入測試(二叉樹的根結點指針爲NULL、輸入的前序遍歷序列和中序遍歷序列不匹配)。
  • 本題考點:
    • 考查應聘者對二叉樹的前序遍歷、中序遍歷的理解程度。只有對二叉樹的不一樣遍歷算法有了深入的理解,應聘者纔有可能在遍歷序列中劃分出左、右子樹對應的子序列。考查應聘者分析複雜問題的能力。咱們把構建二叉樹的大問題分解成構建左、右子樹的兩個小問題。咱們發現小問題和大問題在本質上是一致的,所以能夠用遞歸的方式解決。
 
2.3.5 棧和隊列
    棧是一個很是常見的數據結構,它在計算機領域中被普遍應用,
好比操做系統會給每一個線程建立-一個棧用來存儲函數調用時各個函數的參數、返回地址及臨時變量等。棧的特色是後進先出,即最後被壓入(push)棧的元素會第一個被彈出(pop)。 在面試題22「棧的壓入、彈出序列」中,咱們再詳細分析進棧和出棧序列的特色。
    一般棧是一「個不考慮排序的數據結構,咱們須要O(n)時間才能找到棧中最大或者最小的元素。若是想要在O(1)時間內獲得棧的最大或者最小值,咱們須要對棧作特殊的設計,詳見面試題21「包含min函數的棧」。         
    隊列是另一種很重要的數據結構。和棧不一樣的是,隊列的特色是先進先出,即第一個進入隊列的元素將會第一個出來。 在2.3.4節介紹的樹的寬度優先遍歷算法中,咱們在遍歷某一層樹的結點時,把結點的子結點放到一個隊列裏,以備下一層結點的遍歷。詳細的代碼參見面試題23「從上到下遍歷二叉樹」。
    棧和隊列雖然是特色針鋒相對的兩個數據結構,但有意思的是它們卻相互聯繫。請看面試題7「用兩個棧實現隊列」,同時讀者也能夠考患如何用兩個隊列實現棧。
面試題7:用兩個棧實現隊列
  • 題目:用兩個棧來實現一個隊列,完成隊列的Push和Pop操做。 隊列中的元素爲int類型。
  • 思路:一個棧壓入元素,而另外一個棧做爲緩衝,將棧1的元素出棧後壓入棧2中。也能夠將棧1中的最後一個元素直接出棧,而不用壓入棧2中再出棧。
  • 代碼
    • publicclassTestc {
    • Stack<Integer> stack1= newStack<Integer>();
    • Stack<Integer> stack2= newStack<Integer>();
    • publicstaticvoidmain(String[] args) throwsException {
    • Testc t= newTestc();
    • t.push(1);
    • t.push(2);
    • System.out.println(t.pop());
    • t.push(3);
    • System.out.println(t.pop());
    • System.out.println(t.pop());
    • System.out.println(t.pop());
    • }
    • publicvoidpush(intnode) {
    • stack1.push(node);
    • }
    • publicintpop() throwsException {
    • if(stack1.isEmpty() && stack2.isEmpty()) {
    • thrownewRuntimeException("棧爲空!");
    • }
    • if(stack2.isEmpty()) {
    • while(!stack1.isEmpty()) {
    • stack2.push(stack1.pop());
    • }
    • }
    • returnstack2.pop();
    • }
    • }
  • 測試用例:
    • 往空的隊列裏添加、刪除元素。
    • 往非空的隊列裏添加、
    • 刪除元素。
    • 連續刪除元素直至隊列爲空。
  • 本題考點:
    • 考查對棧和隊列的理解。
    • 考查寫與模板相關的代碼的能力。
    • 考查分析複雜問題的能力。本題解法的代碼雖然只有只有20幾行
    • 代碼,但造成正確的思路卻不容易。應聘者可否經過具體的例子分
    • 析問題,經過畫圖的手段把抽象的問題形象化,從而解決這個相對
    • 比較複雜的問題,是可否順利經過面試的關鍵。
 
2.4 算法和數據操做
    和數據結構同樣,考查算法的面試題也備受面試官的青睞,其中排序和查找是面試時考查算法的重點。在準備面試的時候,咱們應該重點掌握二分查找、歸併排序和快速排序,作到能隨時正確、完整地寫出它們的代碼。
    有不少算法均可以用遞歸和循環兩種不一樣的方式實現。一般基於遞歸的實現方法代碼會比較簡潔,但性能不如基於循環的實現方法。在面
試的時候,咱們能夠根據題目的特色,甚至能夠和麪試官討論選擇合適.的方法編程。
    位運算能夠當作是一- 類特殊的算法,它是把數字表示成二進制以後對0和1的操做。因爲位運算的對象爲二進制數字,因此不是很直觀,但掌握它也不難,由於總共只有與、或、異或、左移和右移5種位運算。
 
2.4.1 查找和排序
    查找和排序都是在程序設計中常常用到的算法。查找相對而言較爲簡
單,不外乎順序查找、二分查找、哈希表查找和二叉排序樹查找。在試
的時候,無論是用循環仍是用遞歸,面試官都期待應聘者可以信手拈來寫出完整正確的二分查找代碼,不然可能連繼續面試的興趣都沒有。
  • 面試小提示:若是面試題是要求在排序的數組( 或者部分排序的數組)中查找一個數字或者統計某個數字出現的次數,咱們均可以嘗試用二分查找算法。
面試題8:旋轉數組的最小數字
  • 題目:把一個數組最開始的若干個元素搬到數組的末尾,咱們稱之爲數組的旋轉。 輸入一個非遞減排序的數組的一 個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的全部元素都大於0,若數組大小爲0,請返回0
  • 思路:
  • 代碼實現
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(minNumberInRotateArray(newint[] { 1, 0, 1, 1, 1 }));
    • }
    • publicstaticintminNumberInRotateArray(int[] array) {
    • if(array== null|| array.length== 0)
    • return0;
    • intleft= 0;
    • intright= array.length- 1;
    • intmid= 0;
    • while(array[left] >= array[right]) {
    • if(right- left<= 1) {
    • mid= right;
    • break;
    • }
    • mid= (left+ right) / 2;
    • if(array[left] == array[mid] && array[mid] == array[right]) {
    • if(array[left+ 1] != array[right- 1]) {
    • intreslut= array[left];
    • for(inti= left+ 1; i<= right; i++) {
    • if(reslut>array[i]) {
    • reslut= array[i];
    • }
    • }
    • returnreslut;
    • }
    • } else{
    • if(array[left] <= array[mid]) {
    • left= mid;
    • } else{
    • right= mid;
    • }
    • }
    • }
    • returnarray[mid];
    • }
    • }
  • 測試用例:
    • 功能測試(輸入的數組是升序排序數組的一個旋轉,數組中有重複數字或者沒有重複數字)。
    • 邊界值測試(輸入的數組是一一個升序排序的數組、只包含一一個數字的數組)。
    • 特殊輸入測試(輸入NULL指針)。
  • 本題考點:
    • 考查對二分查找的理解。本題變換了二分查找的條件,輸入的數組不是排序的,而是排序數組的一個旋轉。這要求咱們對二分查找的過程有深入的理解。
    • 考查溝通學習能力。本題面試官提出了一個新的概念:數組的旋轉。咱們要在很短期內學習理解這個新概念。在面試過程當中若是面試官提出新的概念,咱們能夠主動和麪試官溝通,多問幾個問題把概念弄清楚。
    • 考查思惟的全面性。排序數組自己是數組旋轉的一一個特例。另外,咱們要考慮到數組中有相同數字的特例。若是不能很好地處理這些特例,就很難寫出讓面試官滿意的完美代碼。
2.4.2 遞歸和循環
    若是咱們須要重複地屢次計算相同的問題,一般能夠選擇用遞歸或者循環兩種不一樣的方法。遞歸是在一一個函數的內部調用這個函數自身。而循環則是經過設置計算的初始值及終止條件,在一個範圍內重複運算。
    面試小提示:一般基於遞歸實現的代碼比基於循環實現的代碼要簡潔不少,更加容易實現。若是面試官沒有特殊要求,應聘者能夠優先採用遞歸的方法編程。
  • 遞歸雖然有簡潔的優勢,但它同時也有顯著的缺點。遞歸因爲是函數調用自身,而函數調用是有時間和空間的消耗的:每一次函數調用,都須要在內存棧中分配空間以保存參數、返回地址及臨時變量,並且往棧裏壓入數據和彈出數據都須要時間。這就不難理解.上述的例子中遞歸實現的效率不如循環。
  • 另外,遞歸中有可能不少計算都是重複的,從而對性能帶來很大的負面影響。遞歸的本質是把一一個問題分解成兩個或者多個小問題。若是多個小問題存在相互重疊的部分,那麼就存在重複的計算。在面試題9「斐波那契數列」及面試題43「n個骰子的點數」中咱們將詳細地分析遞歸和循環的性能區別。
  • 除了效率以外,遞歸還有可能引發更嚴重的問題:調用棧溢出。前面分析中提到須要爲每一次函數調用在內存棧中分配空間,而每一個進程的棧的容量是有限的。當遞歸調用的層級太多時,就會超出棧的容量,從而致使調用棧溢出。在上述例子中,若是輸入的參數比較小,如10,它們都能返回結果55。但若是輸入的參數很大,如5000,那麼遞歸代碼在運行的時候就會出錯,但運行循環的代碼能獲得正確的結果12502500。
 
面試題9:斐波那契數列
  • 題目一:寫一個函數,輸入n,求斐波那契( Fibonacci )數列的第n項斐波那契數列的定義以下:
        
  • 思路:遞歸實現效率低,循環實現。
  • 循環代碼實現
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(fibonacci(9));
    • }
    • publicstaticlongfibonacci(intn) {
    • longresult= 0;
    • longpreOne= 1;
    • longpreTwo= 0;
    • if(n== 0) {
    • returnpreTwo;
    • }
    • if(n== 1) {
    • returnpreOne;
    • }
    • for(inti= 2; i<= n; i++) {
    • result= preOne+ preTwo;
    • preTwo= preOne;
    • preOne= result;
    • }
    • returnresult;
    • }
    • }
  • 遞歸代碼實現
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(fibonacci(9));
    • }
    • publicstaticlongfibonacci(intn) {
    • if(n< 0) {
    • return0;
    • }
    • if(n== 1) {
    • return1;
    • }
    • returnfibonacci(n- 1) + fibonacci(n- 2);
    • }
    • }
  • 題目二:一隻青蛙一次能夠跳上1級臺階,也能夠跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法
    • 把問題一 0 1 改爲 1 2
    • 假設,一級臺階,有f(1)種方法,二級有f(2)種,以此類推,n級有f(n)種方法。
    • 能夠看出,f(1)=1;f(2)=2。
    • 那麼,假設n級臺階,那麼第一步就有兩種狀況,跳一步,跟跳兩步。
    • 狀況一:跳一步,那麼接下去的就是f(n-1);
    • 狀況二:跳兩步,那麼接下去的就是f(n-2)。
  • 測試用例:
    • 功能測試(如輸入三、五、10 等)。
    • 邊界值測試(如輸入0、一、2)。
    • 性能測試(輸入較大的數字,如40、50、100 等)。
  • 本題考點:
    • 考查對遞歸、循環的理解及編碼能力。
    • 考查對時間複雜度的分析能力。
    • 若是面試官採用的是青蛙跳臺階的問題,那同時還在考查應聘者的數學建模能力。
2.4.3 位運算
五種運算:與、或、異或、左移、右移
面試題10:二進制中的1的個數
  • 題目:請實現一個函數,輸入一個整數,輸出該數二進制表示中1的個數。例如把9表示成二進制是1001,有2位是1。所以若是輸入9,該函數輸出2。
  • 思路:
    • 1.可能引發死循環的解法 判斷尾數 右移。面試官接下來可能要問的第二個問題就是:上面的函數若是輸入一個負數,好比0x80000000,運行的時候會發生什麼狀況?把負數0x80000000右移一位的時候 並非簡單地把最高位的1移到第二位變成0x4000000而是0xC000000。這是由於移位前是個負數,仍然要保證移位後是個負數,所以移位後的最高位會設爲1。若是一直作右移運算,最終這個數字就會變成0xFFFFFFFF而陷入死循環。
    • 2.爲了不死循環,咱們能夠不右移輸入的數字i。首先把i和1作與運算,判斷i的最低位是否是爲1。接着把1左移一-位獲得2,再和i作與運算,就能判斷i的次低位是否是.....這樣反覆左移,每次都能判斷i的其中一位是否是1。
    • 代碼實現
      • publicclassTestc {
      • publicstaticvoidmain(String[] args) {
      • System.out.println(NumberOf(5));
      • }
      • publicstaticintNumberOf(intn) {
      • intcount= 0;
      • intflag= 1;
      • while(flag!= 0){
      • if((n& flag) != 0){
      • count++;
      • }
      • flag= flag<< 1;
      • }
      • returncount;
      • }
      • }
  • 思路二:把一個整數減去1,再和原整數作與運算,會把該整數最右邊一個1變成0。那麼一一個整數的二進制表示中有多少個1,就能夠進行多少次這樣的操做。
  • 代碼實現
    • publicclassTestc {
    • publicstaticvoidmain(String[] args) {
    • System.out.println(NumberOf(7));
    • }
    • publicstaticintNumberOf(intn) {
    • intcount= 0;
    • while(n> 0){
    • if(n!= 0)
    • n= n&(n-1);
    • count++;
    • }
    • returncount;
    • }
    • }
  • 測試用例:
    • 正數(包括邊界值一、0x7FFFFFFF)。
    • 負數(包括邊界值0x80000000、0xFFFFFFFF)。
  • 本題考點:
    • 考查對二進制及位運算的理解。
    • 考查分析、調試代碼的能力。若是應聘者在面試過程當中採用的是第
    • 一種思路,當面試官提示他輸入負數將會出現問題時,面試官會期待他能在心中運行代碼,本身找出運行出現死循環的緣由。這要求應聘者有必定的調試功底。
  • 相關題目:
    • 用一條語句判斷一「個整數是否是2的整數次方。一個整數若是是2的整數次方,那麼它的二進制表示中有且只有一位是1,而其餘全部位都是0。根據前面的分析,把這個整數減去1以後再和它本身作與運算,這個整數中惟一的1就會變成0。
    • 輸入兩個整數m和n,計算須要改變m的二進制表示中的多少位才能獲得n。好比10的二進制表示爲1010, 13 的二進制表示爲1101,須要改變1010中的3位才能獲得1101。咱們能夠分爲兩步解決這個問題:第一步求這兩個 數的異或,第二步統計異或結果中1的位數。
  • 觸類旁通:
    • 把一個整數減去1以後再和原來的整數作位與運算,獲得的結果至關因而把整數的二進制表示中的最右邊一個1變成0.不少二進制的問題均可以用這個思路解決。
 
2.5 本章小結
    本章着重介紹應聘者在面試以前應該認真準備的基礎知識。爲了應對編程面試,應聘者須要從編程語言、數據結構和算法3方面作好準備。
    面試官一般採用概念題、代碼分析題及編程題這3種常見題型來考查應聘者對某一編程語言 的掌握程度。本章的2.2節討論了C++/C#語言這3種題型的常見面試題。
    數據結構題目一直是面試官考查的重點。數組和字符串是兩種最基本的數據結構。鏈表應該是面試題中使用頻率最高的一種數據結構。若是面試官想加大面試的難度,他頗有可能會選用與樹(尤爲是二叉樹)相關的第2章面試須要的基礎知識
面試題。因爲棧與遞歸調用密切相關,隊列在圖(包括樹)的寬度優先遍歷中須要用到,所以應聘者也須要掌握這兩種數據結構。
    算法是面試官喜歡考查的另一個重點。查找(特別是二分查找)和排序(特別是快速排序和歸併排序)是面試中最常常考查的算法,應聘者必定要熟練掌握。另外,應聘者還要掌握分析時間複雜度的方法,理解即便是同一思路,基於循環和遞歸的不一樣實現它們的時間複雜度可能大不相同。
    位運算是針對二進制數字的運算規律。只要應聘者熟練掌握了二進制的與、或、異或運算及左移、右移操做,就能解決與位運算相關的面試題。
相關文章
相關標籤/搜索