1. 程式人生 > >第六章 面試中的各項能力

第六章 面試中的各項能力

數列 觀察 左移 暗示 本質 bst 判斷語句 遇到 if else

6.1 面試官談能力
  • 禮貌平和、思路清晰
  • 提供解答時觀察面試者的溝通能力及求知欲
  • 溝通能力、學習能力
  • 表達能力
  • 是否得到暗示之後迅速做出反應糾正錯誤
  • 是否會在自己得到的信息不夠的情況下主動發問澄清
6.2 溝通能力和學習能力
  • 溝通能力
  • 學習能力
  • 善於學習、溝通的人也善於提問
  • 面試小提示:
    • 面試是一個雙向交流的過程,面試官可以問應聘者問題,同樣應聘者也可以向面試官提問。如果應聘者能夠針對面試題主動地提出幾個高質量的問題,面試官就會覺得他有很強的溝通能力和學習能力。
6.3 知識遷移能力
  • 所謂學習能力,很重要的一點就是根據已經掌握的知識、技術,能夠迅速學習、理解新的技術並能運用到實際工作中去。大部分新的技術都不是憑空產生的,而是在已有技術的基礎上發展起來的。這就要求我們能夠把對已有技術的理解遷移到學習新技術的過程中去,也就是要具備很強的知識遷移能力。
面試題38:數字在排序數組中出現的次數
  • 題目:統計一個數字在排序數組中出現的次數。例如輸入排序數組{1,2,3,3,3,3,4,5}和數字3,由於3在這個數組中出現了4次,因此輸出4。
  • 思路:利用二分查找+遞歸思想,進行尋找。當目標值與中間值相等時進行判斷
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.GetNumberOfK(new int[]{1,2,2,3,3,3,3,4,4,5},3));
    • }
    • public int GetNumberOfK(int[] array, int k) {
    • int result = 0;
    • int mid = array.length / 2;
    • if (array == null || array.length == 0)
    • return 0;
    • if (array.length == 1) {
    • if (array[0] == k)
    • return 1;
    • else
    • return 0;
    • }
    • if (k < array[mid])
    • result += GetNumberOfK(Arrays.copyOfRange(array, 0, mid), k);
    • else if (k > array[mid])
    • result += GetNumberOfK(Arrays.copyOfRange(array, mid, array.length), k);
    • else {
    • for (int i = mid; i < array.length; i++) {
    • if (array[i] == k)
    • result++;
    • else
    • break;
    • }
    • for (int i = mid - 1; i >= 0; i--) {
    • if (array[i] == k)
    • result++;
    • else
    • break;
    • }
    • }
    • return result;
    • }
    • }
  • 測試用例:
    • 功能測試(數組中包含查找的數字,數組中沒有查找的數字,查找的數字在數組中出現一次/多 次)。
    • 邊界值測試(查找數組中的最大值、最小值,數組中只有一個數字)
    • 特殊輸入測試(表示數組的指針為NULL指針)。
  • 本題考點:
    • 考查應聘者的知識遷移能力。我們都知道二分查找算法可以用來在排序數組中查找一一個數字。應聘者如果能夠運用知識遷移能力,把問題轉換成用二分查找算法查找重復數字的第-一個和最後一個,那麽這個問題也就解決了一大半。
    • 考查應聘者對二分查找算法的理解程度。這道題實際上是二分查找算法的加強版。只有對二分查找算法有著深刻的理解,應聘者才有可能解決這個問題。
面試題39:二叉樹的深度
  • 題目一:輸入一棵二叉樹的根結點,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度為樹的深度。
  • 思路:利用遞歸遍歷分別返回左右子樹深度
  • 代碼實現
    • class TreeNode {
    • public int val;
    • public TreeNode left;
    • public TreeNode right;
    • public TreeNode(int val) {
    • this.val = val;
    • }
    • public TreeNode() {
    • }
    • }
    • public class TestTree {
    • public static int n;
    • ArrayList<ArrayList<Integer>> resultList = new ArrayList<ArrayList<Integer>>();
    • ArrayList<Integer> list = new ArrayList<Integer>();
    • public static void main(String[] args) {
    • int[] array = new int[] { 10, 5, 4, 0, 0, 7, 1,0,0,0, 0, 12, 0, 0 };
    • TestTree t = new TestTree();
    • TreeNode root = t.CreateTreeBinary(new TreeNode(), array);
    • System.out.println(t.TreeDepth(root));
    • }
    • public TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • public int TreeDepth(TreeNode root) {
    • if (root == null)
    • return 0;
    • int left = TreeDepth(root.left);
    • int right = TreeDepth(root.right);
    • return left > right ? left + 1 : right + 1;
    • }
    • }
  • 測試用例:
    • 功能測試(輸入普通的二叉樹,二叉樹中所有結點都沒有左/右子樹)。
    • 特殊輸入測試(二叉樹只有一一個結點,二叉樹的頭結點為NULL指針)。
  • 題目二:輸入一棵二叉樹的根結點,判斷該樹是不是平衡二叉樹。如果某二叉樹中任意結點的左右子樹的深度相差不超過1,那麽它就是一棵平衡二叉樹。例如,圖6.1中的二叉樹就是一棵平衡二叉樹。
  • 思路:平衡因子的絕對值<= 1.
  • 代碼實現
    • class TreeNode {
    • public int val;
    • public TreeNode left;
    • public TreeNode right;
    • public TreeNode(int val) {
    • this.val = val;
    • }
    • public TreeNode() {
    • }
    • }
    • public class TestTree {
    • public static int n;
    • ArrayList<ArrayList<Integer>> resultList = new ArrayList<ArrayList<Integer>>();
    • ArrayList<Integer> list = new ArrayList<Integer>();
    • public static void main(String[] args) {
    • int[] array = new int[] { 7, 4, 5, 6, 0, 0, 0, 0, 8, 0, 9, 0, 10, 0, 0 };
    • TestTree t = new TestTree();
    • TreeNode root = t.CreateTreeBinary(new TreeNode(), array);
    • System.out.println(t.IsBalanced_Solution(root));
    • }
    • public TreeNode CreateTreeBinary(TreeNode treeNode, int[] array) {
    • if (array[n] == 0) {
    • n++;
    • return null;
    • } else {
    • treeNode.val = array[n++];
    • treeNode.left = CreateTreeBinary(new TreeNode(0), array);
    • treeNode.right = CreateTreeBinary(new TreeNode(0), array);
    • return treeNode;
    • }
    • }
    • private boolean isBalanced = true;
    • public boolean IsBalanced_Solution(TreeNode root) {
    • getDepth(root);
    • return isBalanced;
    • }
    • public int getDepth(TreeNode root) {
    • if (root == null)
    • return 0;
    • int left = getDepth(root.left);
    • int right = getDepth(root.right);
    • System.out.print(" root: " + root.val);
    • System.out.print(" Math.abs(left - right): " + Math.abs(left - right));
    • System.out.print(" left: " + left);
    • System.out.println(" right: " + right);
    • if (Math.abs(left - right) > 1) {
    • isBalanced = false;
    • }
    • return right > left ? right + 1 : left + 1;
    • }
    • }
  • 測試用例:
    • 功能測試(平衡的二叉樹,不是平衡的二叉樹,二叉樹中所有結點都沒有左/右子樹)。
    • 特殊輸入測試(二叉樹中只有一一個結點,二叉樹的頭結點為NULL指針)。
  • 本題考點:
    • 考查對二叉樹的理解及編程能力。這兩個題的解法實際都只是樹的遍歷算法的應用。
    • 考查對新概念的學習能力。面試官提出一個新的概念即樹的深度,這就要求我們在較短的時間內理解這個概念並解決相關的問題。這是一種常見的面試題型。能在較短時間內掌握、理解新概念的能力,就是一種學習能力。
    • 考查知識遷移的能力。如果面試官先問如何求二叉樹的深度,再問如何判斷一棵=二叉樹是不是平衡的,應聘者應該從求二叉樹深度的分析過程中得到啟發,找到判斷平衡二叉樹的突破口。
面試題40:數組中只出現一次的數字
  • 題目:一個整型數組裏除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。要求時間復雜度是O(n), 空間復雜度是O(1)。
  • 思路:我們還是從頭到尾依次異或數組中的每-一個數字,那麽最終得到的結果就是兩個只出現一次的數字的異或結果。因為其他數字都出現了兩次,在異或中全部抵消了。由於這兩個數字肯定不一-樣,那麽異或的結果肯定不為0,也就是說在這個結果數字的二進制表示中至少就有一位為1。我們在結果數字中找到第-一個為1的位的位置,記為第n位。現在我們以第n位是不是1為標準把原數組中的數字分成兩個子數組,第-一個子數組中每個數字的第n位都是1,而第二個子數組中每個數字的第n位都是0。由於我們分組的標準是數字中的某一位是1還是0,那麽出現了兩次的數字肯定被分配到同一個子數組。因為兩個相同的數字的任意-一位都是相同的,我們不可能把兩個相同的數字分配到兩個子數組中去,於是我們已經把原數組分成了兩個子數組,每個子數組都包含-一個只出現一次的數字, 而其他數字都出現了兩次。我們已經知道如何在數組中找出唯一一個只出現一次數字,因此到此為止所有的問題都已經解決了。
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • t.FindNumsAppearOnce(new int[] {2,4,3,6,2,3,5,5 });
    • System.out.println(t.num1);
    • System.out.println(t.num2);
    • }
    • private int num1;
    • private int num2;
    • public void FindNumsAppearOnce(int[] array) {
    • if (array == null)
    • return;
    • num1 = 0;
    • num2 = 0;
    • int number = array[0];
    • for (int i = 1; i < array.length; i++)
    • number ^= array[i];
    • // 異或後的數1出現在第幾位
    • int index = 0;
    • while ((number & 1) == 0) {
    • number = number >> 1;
    • index++;
    • }
    • for (int i = 0; i < array.length; i++) {
    • // 判斷第index位是不是0
    • boolean isBit = ((array[i] >> index) & 1) == 0;
    • if (isBit) {
    • num1 ^= array[i];
    • } else {
    • num2 ^= array[i];
    • }
    • }
    • }
    • }
  • 測試用例:
    • 功能測試(數組中多對重復的數字,數組中沒有重復的數字)
  • 本題考點:
    • 考查知識遷移能力。只有一個數字出現一-次這個簡單的問題,很多應聘者都能想到解決辦法。能不能把解決簡單問題的思路遷移到復雜問題上,是應聘者能否通過這輪面試的關鍵。
    • 考查對二進制和位運算的理解。
面試題41: 和為 s 的兩個數字 VS 和為 s 的連續正數序列
  • 題目一:輸入一個遞增排序的數組和一個數字s,在數組中查找兩個數,使得它們的和正好是s。如果有多對數字的和等於s,輸出任意一對即可。
  • 思路:定義兩個指針,分別從前面和後面進行遍歷。間隔越遠乘積越小,所以是最先出現的兩個數乘積最小
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • int[] array = new int[] { 2, 4, 5, 7, 8 };
    • System.out.println(t.FindNumbersWithSum(array, 11));
    • }
    • public ArrayList<Integer> FindNumbersWithSum(int[] array, int sum) {
    • ArrayList<Integer> list = new ArrayList<>();
    • if (array == null)
    • return list;
    • int left = 0;
    • int right = array.length - 1;
    • while (left < right) {
    • int s = array[left] + array[right];
    • if (s == sum) {
    • list.add(array[left]);
    • list.add(array[right]);
    • return list;
    • } else {
    • if (s > sum) {
    • right--;
    • } else {
    • left++;
    • }
    • }
    • }
    • return list;
    • }
    • }
  • 測試用例:
    • 功能測試(數組中存在和為s的兩個數,數組中不存在和為s的兩個數)
    • 特殊輸入測試(表示數組的指針為NULL指針)
  • 題目二:輸入一個正數s,打印出所有和為s的連續正數序列(至少含有兩個數)。例如輸入15,由於1+2+3+4+5-4+5+6=7+8=15, 所以結果打印出3個連續序列1~5、4~6和7~8。
  • 思路:定義兩個指針,分別遞增,尋找和為s的序列。
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • int[] array = new int[] { 2, 4, 5, 7, 8 };
    • System.out.println(t.FindContinuousSequence(9));
    • }
    • public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
    • ArrayList<ArrayList<Integer>> arrayList = new ArrayList<>();
    • ArrayList<Integer> list = new ArrayList<>();
    • if (sum < 3)
    • return arrayList;
    • int small = 1;
    • int big = 2;
    • while (small < (sum + 1) / 2) {//10的時候small到5以後就沒有意義了
    • int s = 0;
    • for (int i = small; i <= big; i++) {
    • s += i;
    • }
    • if (s == sum) {
    • for (int i = small; i <= big; i++) {
    • list.add(i);
    • }
    • arrayList.add(new ArrayList<>(list));
    • list.clear();
    • small++;
    • } else {
    • if (s > sum) {
    • small++;
    • } else {
    • big++;
    • }
    • }
    • }
    • return arrayList;
    • }
    • }
  • 測試用例:
    • 功能測試(存在和為s的連續序列,如9、100等;不存在和為s的連續序列,如4、0)。
    • 邊界值測試(連續序列的最小和3)
  • 本題考點:
    • 考查思考復雜問題的思維能力。應聘者如果能夠通過一兩個具體的例子找到規律,解決這個問題就容易多了。
    • 考查知識遷移的能力。應聘者面對第二個問題的時候,能不能把解決第一個問題的思路應用到新的題目上,是面試官考查知識遷移能力的重要指標。
面試題42:翻轉單詞順序 VS 左旋轉字符串
  • 題目一:輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字符的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字符串"I am a student.”,則輸 出"student. a am I”。
  • 思路:先將整個字符串翻轉,然後將每個單詞翻轉。
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • int[] array = new int[] { 2, 4, 5, 7, 8 };
    • System.out.println(t.ReverseSentence("I am a student"));
    • }
    • public String ReverseSentence(String str) {
    • if (str == null || str.length() == 0)
    • return str;
    • if (str.trim().length() == 0)
    • return str;
    • StringBuilder sb = new StringBuilder();
    • String re = reverse(str);
    • String[] s = re.split(" ");
    • for (int i = 0; i < s.length; i++) {
    • sb.append(reverse(s[i]) + " ");
    • }
    • return String.valueOf(sb);
    • }
    • public String reverse(String str) {
    • StringBuilder sb = new StringBuilder();
    • for (int i = str.length() - 1; i >= 0; i--) {
    • sb.append(str.charAt(i));
    • }
    • return String.valueOf(sb);
    • }
    • }
  • 測試用例:
    • 功能測試(句子中有多個單詞,句子中只有-一個單詞)。
    • 特殊輸入測試(字符串指針為NULL指針、字符串的內容為空、字符串中只有空格)。
  • 題目二:字符串的左旋轉操作是把字符串前面的若千個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如輸入字符串"abcdefg"和數字2,該函數將返回左旋轉2位得到的結果"cdefgab"。
  • 思路:拼接或反轉三次字符串
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • int[] array = new int[] { 2, 4, 5, 7, 8 };
    • System.out.println(t.LeftRotateString("Iamastudent",5));
    • }
    • public String LeftRotateString(String str,int n) {
    • if (str == null || str.length() == 0 || n >= str.length())
    • return str;
    • String s1 = reverse(str.substring(0,n));
    • String s2 = reverse(str.substring(n,str.length()));
    • return reverse(s2)+reverse(s1);
    • }
    • public String reverse(String str) {
    • StringBuilder sb = new StringBuilder();
    • for (int i = str.length() - 1; i >= 0; i--) {
    • sb.append(str.charAt(i));
    • }
    • return String.valueOf(sb);
    • }
    • }
  • 測試用例:
    • 功能測試(把長度為n的字符串左旋轉0個字符、1 個字符、2個字符、n-1個字符、n個字符、n+1個字符)。
    • 特殊輸入測試(字符串的指針為NULL指針)。
  • 本題考點:
    • 考查知識遷移的能力。當面試的時候遇到第二個問題,而之前我們做過“翻轉句子中單詞的順序”這個題目,那如果能夠把多次翻轉字符串的思路遷移過來,就能很輕易地解決字符串左旋轉的問題。
    • 考查對字符串的編程能力。
6.4 抽象建模能力
  • 計算機只是一種工具,它的作用是用來解決實際生產生活中的問題。程序員的工作就是把各種現實問題抽象成數學模型並用計算機的編程語言表達出來,因此有些面試官喜歡從日常生活中抽取提煉出問題考查應聘者是否能建立數學模型並解決問題。要想順利解決這種類型的問題,應聘者除了需要具備紮實的數學基礎和編程能力之外,還需要具有敏銳的洞察力和豐富的想象力。
  • 建模的第一步是選擇合理的數據結構來表述問題。實際生產生活中的問題千變萬化,而常用的數據結構卻只有有限的幾種。我們在根據問題的特點綜合考慮性能、編程難度等因素之後,選擇最合適的數據結構來表達問題,也就是建立模型。比如在面試題44“撲克牌的順子”中,我們用一個數組表示-副牌,用11、12和13分別表示J、Q、K並且用0表示大小王。在面試題45“圓圈中最後剩下的數字”中,我們可以用一個環形鏈表模擬一個圓圈。
  • 建模的第二步是分析模型中的內在規律,並用編程語言表述這種規律。我們只有對現實問題進行深入細微的觀察分析之後,才能找到模型中的規律,才有可能編程解決問題。例如在本書2.4.2節提到的“青蛙跳臺階”問題中,它內在的規律是斐波那契數列。再比如面試題43“n個骰子的點數”問題,其本質是求數列f(n)=f(n- 1)+f(n- 2)+f(n-3)+f(n-4)+f{n -5)+{(n-6)。找到這個規律之後,我們就可以分別用遞歸和循環兩種不同的方法去寫代碼。然而,並不是所有問題的內在規律都是顯而易見的。在面試題45“圓圈中最後剩下的數字”中,我們經過嚴密的數學分析之後才能找到每次從圓圈中刪除的數字的規律,從而找到一一種不需要輔助環形鏈表的快速方法來解決問題。
面試題43: n個骰子的點數
  • 題目:把n個骰子扔在地上,所有骰子朝上一面的點數之和為s。輸入n,打印出s的所有可能的值出現的概率。
  • 思路:動態規劃法求解過程可以使用遞歸來實現,也可以使用叠代來實現。遞歸的優勢就是代碼簡潔明了,但是遞歸有時會對不同階段的子問題重復求解,所以效率低於叠代。
  • 解題思路:
    • 第一步,確定問題解的表達式。可將f(n, s) 表示n個骰子點數的和為s的排列情況總數。
    • 第二步,確定狀態轉移方程。n個骰子點數和為s的種類數只與n-1個骰子的和有關。因為一個骰子有六個點數,那麽第n個骰子可能出現1到6的點數。所以第n個骰子點數為1的話,f(n,s)=f(n-1,s-1),當第n個骰子點數為2的話,f(n,s)=f(n-1,s-2),…,依次類推。在n-1個骰子的基礎上,再增加一個骰子出現點數和為s的結果只有這6種情況!那麽有:
    • f(n,s)=f(n-1,s-1)+f(n-1,s-2)+f(n-1,s-3)+f(n-1,s-4)+f(n-1,s-5)+f(n-1,s-6) ,0< n<=6n
    • f(n,s)=0, s< n or s>6n
    • 上面就是狀態轉移方程,已知初始階段的解為:
    • 當n=1時, f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1。
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • int n = 3;
    • for(int i=n;i<=6*n;++i){
    • System.out.println(i+" "+t.getNSumCount(n,i));
    • }
    • }
    • public int getNSumCount(int n, int sum) {
    • if (n < 1 || sum < n || sum > 6 * n) {
    • return 0;
    • }
    • if (n == 1) {
    • return 1;
    • }
    • int resCount = 0;
    • resCount = getNSumCount(n - 1, sum - 1) + getNSumCount(n - 1, sum - 2) + getNSumCount(n - 1, sum - 3)
    • + getNSumCount(n - 1, sum - 4) + getNSumCount(n - 1, sum - 5) + getNSumCount(n - 1, sum - 6);
    • return resCount;
    • }
    • }
  • 測試用例:
    • 功能測試(1、2、3、4個骰子的各點數的概率)。
    • 特殊輸入測試(輸入0)。
    • 性能測試(輸入較大的數字,比如11)。
  • 本題考點:
    • 數學建模的能力。不管采用哪種思路解決問題,我們都要先想到用數組來存放n個骰子的每一個點數出現的次數,並通過分析點數的規律建立模型並最終找到解決方案。
    • 考查對遞歸和循環的性能的理解。
面試題44:撲克牌的順子
  • 題目:從撲克牌中隨機抽5張牌,判斷是不是一個順子,即這5張牌是不是連續的。2~10為數字本身,A為1,J為11,Q為12,K為13,而大、小王可以看成任意數字。 0為大小王。
  • 思路:用數組記錄五張撲克牌,將數組調整為有序的,若0出現的次數>=順子的差值,即為順子。如果有對子直接返回false。
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.isContinuous(new int[]{1,0,3,4,3,0}));
    • }
    • public boolean isContinuous(int[] numbers) {
    • if (numbers == null || numbers.length == 0)
    • return false;
    • int count = 0;
    • int diff = 0;
    • Arrays.sort(numbers);
    • for (int i = 0; i < numbers.length - 1; i++) {
    • if (numbers[i] == 0) {
    • count++;
    • continue;
    • }
    • if (numbers[i] != numbers[i + 1]) {
    • diff += numbers[i + 1] - numbers[i] - 1;
    • } else {
    • return false;
    • }
    • }
    • if (diff <= count)
    • return true;
    • return false;
    • }
    • }
  • 測試用例:
    • 功能測試(抽出的牌中有一個或者多個大、小王,抽出的牌中沒有大、小王,抽出的牌中有對子)。
    • 特殊輸入測試(輸入NULL指針)。
  • 本題考點:
    • 考查抽象建模能力。這個題目要求我們把熟悉的撲克牌轉換為數組,把找順子的過程通過排序、計數等步驟實現。這些都是把生活中的模型用程序語言來表達的例子。
面試題45:圓圈中最後剩下的數字
  • 題目: 0,1...,n-1這n個數字排成一個圓圈,從數字0開始每次從這個圓圈裏刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。
  • 技術分享圖片

  • 思路一:利用循環鏈表實現
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.LastRemaining_Solution(5, 3));
    • }
    • public int LastRemaining_Solution(int n, int m) {
    • LinkedList<Integer> list = new LinkedList<Integer>();
    • int bt = 0;
    • for (int i = 0; i < n; i++) {
    • list.add(i);
    • }
    • while (list.size() > 1) {
    • bt = (bt + m - 1) % list.size();
    • list.remove(bt);
    • }
    • return list.get(0);
    • }
    • }
  • 思路二:求出公式,遞歸計算
    • 技術分享圖片

  • 代碼實現
    • public int LastRemaining(int n, int m) {
    • if (n < 1 || m < 1)
    • return -1;
    • int last = 0;
    • for (int i = 2; i <= n; i++)
    • last = (last + m) % i;
    • return last;
    • }
  • 測試用例:
    • 功能測試(輸入的m小於n,比如從最初有5個數字的圓圈刪除每次第2、3個數字;輸入的m大於或者等於n,比如從最初有6個數字的圓圈刪除每次第6、7個數字)。
    • 特殊輸入測試(圓圈中有0個數字)。
    • 性能測試(從最初有4000個數字的圓圈中每次刪除第997個數字)。
  • 本題考點:
    • 考查抽象建模的能力。不管應聘者是用環形鏈表來模擬圓圈,還是分析被刪除數字的規律,都要深刻理解這個問題的特點並編程實現自己的解決方案。
    • 考查對環形鏈表的理解及應用能力。大部分面試官只要求應聘者基於環形鏈表的方法解決這個問題。
    • 考查數學功底及邏輯思維能力。少數對算法和數學基礎要求很高的公司,面試官會要求應聘者不能使用O(n)的輔助內存,這個時候應聘者就只能靜下心來一步步推導出每次刪除的數字有哪些規律。
6.5 發散思維能力 發散思維的特點是思維活動的多向性和變通性,也就是我們在思考問題時註重運用多思路、多方案、多途徑地解決問題。對於同一個問題,我們可以從不同的方向、側面和層次,采用探索、轉換、遷移、組合和分解等方法,提出多種創新的解法。 面試題46:求1+2+.*.+n
  • 題目:求1+2+**.+n.要求不能使用乘除法、for. while. if else. switch、case等關鍵字及條件判斷語句(A?B:C)。
  • 思路:巧用遞歸(返回值類型為Boolean)
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.Sum_Solution(5));
    • }
    • public int Sum_Solution(int n) {
    • int sum = n;
    • boolean result = (n > 0) && ((sum += Sum_Solution(n - 1)) > 0);
    • return sum;
    • }
    • }
  • 測試用例:
    • 功能測試(輸入5、10 求1+2+**+5和1+2+**+10).
    • 邊界值測試(輸入0和1)。
  • 本題考點:
    • 考查發散思維能力。當習以為常的方法被限制使用的時候,應聘者是否能發揮創造力,打開思路想出新的辦法,是能否通過面試的關鍵所在。
    • 考查知識面的廣度和深度。上面提供的幾種解法,涉及構造函數、靜態變量、虛擬函數、函數指針、模板類型的實例化等知識點。只有深刻理解了相關的概念,才能在需要的時候信手拈來。這就是厚積薄發的過程。
面試題47:不用加減乘除做加法
  • 題目:寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/ 四則運算符號。
  • 思路:5的二進制是101,17 的二進制是10001。還是試著把計算分成三步:
    • 第一步各位相加但不計進位,得到的結果是10100 (最後一位兩個數都是1,相加的結果是二進制的10。這一步不計進位,因此結果仍然是0);第二步記下進位。在這個例子中只在最後一位相加時產生一個進位,結果是二進制的10;第三步把前兩步的結果相加,得到的結果是10110, 轉換成十進制正好是22。由此可見三步走的策略對二進制也是適用的。接下來我們試著把二進制的加法用位運算來替代。
    • 第一步不考慮進位對每一位相加。0加0、1加1的結果都0,0加1、1加0的結果都是1。我們註意到,這和異或的結果是一樣的。對異或而言,0和0、1和1異或的結果是0,而0和1、1和0的異或結果是1。接著考慮第一步進位,對0加0、0加1、1加0而言,都不會產生進位,只有1加1時,會向前產生一個進位。此時我們可以想象成是兩個數先做位與運算,然後再向左移動一位。只有兩個數都是1的時候,位與得到的結果是1,其余都是0。第三步把前兩個步驟的結果相加。第三步相加的過程依然是重復前面兩步,直到不產生進位為止。
  • 代碼實現
    • public class TestMain {
    • public static void main(String[] args) {
    • TestMain t = new TestMain();
    • System.out.println(t.Add(5, 22));
    • }
    • public int Add(int num1, int num2) {
    • //num2 = ~(num2-1); 減法
    • while (num2 != 0) {
    • // 計算相加結果 不考慮進位
    • int temp = num1 ^ num2;
    • // 計算進位(1+1)
    • num2 = (num1 & num2) << 1;
    • num1 = temp;
    • }
    • return num1;
    • }
    • }
  • 測試用例:
    • 輸入正數、負數和0。
  • 本題考點:
    • 考查發散思維能力。當+、-、*、/運算符都不能使用時,應聘者能不能打開思路想到用位運算做加法,是能否順利解決這個問題的關鍵。
    • 考查對二進制和位運算的理解。
面試題48: 不能被繼承的類
  • 實現一個不能被繼承的類
  • 思路:
    • 1、如果類被final修飾,那麽此類不可以被繼承。
    • 2、如果類中只有private的構造方法,那麽此類不可以被繼承。
  • 本題考點:
    • 考查發散思維能力。當要求設計一個不能被繼承的類時,應聘者要馬上從把構造函數定義為私有函數出發去尋找解題方法。
6.6 本章小結 面試是我們展示自己綜合素質的時候。除了紮實的編程能力,我們還需要表現自己的溝通能力和學習能力,以及知識遷移能力、抽象建模能力和發散思維能力等方面的綜合實力(如圖6.3所示)。 技術分享圖片

面試官對溝通能力、學習能力的考查貫穿著面試的始終。面試官不僅會留意我們回答問題時的言語談吐,還會關註我們是否能抓住問題的本質從而提出有針對性的問題。通常面試官認為善於提問的人有較好的溝通和學習能力。 知識遷移能力能幫助我們輕松地解決很多問題。有些面試官在提問一道難題之前,會問一道相關但比較簡單的題目,他希望我們能夠從解決簡單問題的過程中受到啟發,最終解決較為復雜的問題。另外,我們在面試之前可以做-些練習。如果面試的時候碰到類似的題目,就可以應用之前的方法。這要求我們平時要有一-定的積累,並且每做完-道題之,後都要總結解題方法。 有一類很有意思的面試題是從日常生活中提煉出來的,面試官用這種類型的問題來考查我們抽象建模的能力。為了解決這種類型的問題,我們先用適當的數據結構表述模型,並分析模型中的內在規律確定計算方法。有些面試官喜歡在面試的時候限制使用常規的思路。這個時候就需要我們充分發揮發散思維的能力,跳出常規思路的束縛,從不同的角度去嘗試新的辦法。

第六章 面試中的各項能力