最近在看《劍指offer》,寫幾篇文章來記錄一下,今天有如下幾道算法題:算法
斐波那契數列
二進制中1的個數
數組中重複的數字
二維數組中的查找
替換空格
數值的整數次方
寫一個函數,輸入n,求斐波那契數列的第n項。
遞歸解法重複計算太多,效率過低,這裏咱們使用時間複雜度爲O(n)的方法。數組
解題思路
從下往上計算,首先根據f(0)和f(1)算出f(2),再根據f(1)和f(2)算出f(3).......以此類推就能夠算出第n項。app
public class Fibonacci { public static long fibonacci(long n){ if (n<0) throw new RuntimeException("無效的輸入"); if (n==0) return 0; if (n==1) return 1; long fibOne=0; long fibTwo=1; long fibSum=0; for (int i = 2; i <= n; i++) { fibSum=fibOne+fibTwo; fibOne=fibTwo; fibTwo=fibSum; } return fibSum; } }
請實現一個函數,輸入一個整數,輸出該數二進制表示中1的個數。
例如,把9表示成二進制就是1001,則輸出2.
若是一個整數與1作 &(與)運算的結果是1,則表示該整數最右邊一位是1,不然是0函數
public static int Solution1(int n) { int count = 0; //JAVA語言規範中,int整形佔四個字節,總計32位 //對每個位置與1進行求與操做,再累加就能夠求出當前數字的表示是多少位1 for (int i = 0; i < 32; i++) { if ((n & 1) == 1) { count++; } n = n >> 1; } return count; }
首先把n和1作與運算,判斷n的最低位是否是1。接着把1左移一位獲得2,再和n作&運算,就能判斷n的次低位是否是1,這樣反覆左移,每次都能判斷n的其中一位是否是1優化
public static int Solution2(int n) { int count = 0; int k = 1; for (int i = 0; i < 32; i++) { if ((n & k) == k) { count++; } k = k << 1; } return count; }
把一個整數減去1,再和原整數作與運算,會把該整數最右邊的1變爲0
該方法整數中有幾個1就只須要循環幾回指針
public static int Solution3(int n) { int count = 0; //數字的二進制表示中有多少個1就進行多少次操做 while (n != 0) { count++; //從最右邊的1開始,每一次操做都使n的最右的一個1變成了0 n = n & (n - 1); } return count; }
在一個長度爲n的數組裏的全部數字都在0到n-1的範圍。數組中某些數字是重複的,但不知道有幾個數字重複,也不知道每一個數字重複的次數。 請找出數組中任意一個重複的數字。例如若是輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是重複的數字2或者3。
解題思路
咱們注意到數組中的數字都在0
到n-1
的範圍內。若是這個數組中沒有重複的數字,那麼當數組排序後數字i
將出如今下標爲i
的位置。code
從頭至尾掃描這個數組中的每一個數字。當掃描到下標爲i
的數字的時候,首先比較這個數字(用m
表示)是否是i。 若是是,接着掃描下一個數字。若是不是,再拿它和第m
個數字進行比較。 若是它和第m個數字相等,就找到一個重複的數字(該數字在下標爲i和m的位置都出現了)。 若是它和第m個數字不想等,就把第i個數字和第m個數字交換,把m放到屬於它的位置。 接下來再重複這個比較,交換的過程,直到發現一個重複的數字。排序
以數組{2,3,1,0,2,5,3}爲例來分析找到重複數字的步驟。數組的第0個數字(從0開始計數,和數組的下標保持一致)是2, 與它的下標不相等,因而把它和下標爲2的數字1交換,交換後的數組是{1,3,2,0,2,5,3}。 此時第0 個數字是1,仍然與它的下標不相等,繼續把它和下標爲1的數字3交換,獲得數組{0,1,2,3,2,5,3}。 此時第0 個數字爲0,接着掃描下一個數字,在接下來的幾個數字中,下標爲1,2,3的三個數字分別爲1,2,3, 他們的下標和數值都分別相等,所以不須要作任何操做。 接下來掃描下標爲4的數字2.因爲它的值與它的下標不相等,再比較它和下標爲2的數字。 注意到此時數組中下標爲2的數字也是2,也就是數字2和下標爲2和下標4的兩個位置都出現了,所以找到一個重複的數字。遞歸
public class Test { public static int duplication;//用來保存重複數字 public static boolean duplicate(int[] arr) { //判斷輸入是否合法 if (arr == null || arr.length == 0) { return false; } for (int i = 0; i < arr.length; i++) { if (arr[i] < 0 || arr[i] >= arr.length) { return false; } } //核心代碼 for (int i = 0; i < arr.length; i++) { while (arr[i] != i) { //若是兩個數相等則返回true if (arr[i] == arr[arr[i]]) { duplication = arr[i]; System.out.println(arr[i]); return true; } //不然交換數字 int temp = arr[i]; arr[i] = arr[temp]; arr[temp] = temp; } } return false; } }
在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。 請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
解題思路
(1)首先選取數組中右上角的數字。若是該數字等於要查找的數字,則結束查找過程。
(2)若是該數字大於要查找的數字,則剔除這個數字所在的列。
(3)若是該數字小於要查找的數字,則剔除這個數字所在的行。ci
public class Test { public static boolean find(int[][] matrix, int number) { if (matrix == null || matrix.length < 1 || matrix[0].length < 1) { return false; } int rows = matrix.length; // 數組的行數 int cols = matrix[1].length; // 數組行的列數 int row = 0; // 起始開始的行號 int col = cols - 1; // 起始開始的列號 while (row >= 0 && row < rows && col >= 0 && col < cols) { if (matrix[row][col] == number) { // 若是找到了就直接退出 return true; } else if (matrix[row][col] > number) { col--; // 列數減一,表明向左移動 } else { // row++; // 行數加一,表明向下移動 } } return false; } }
請實現一個函數,把字符串中的每一個空格替換成"%20",例如「We are happy」,則輸出「We%20are%20happy」
很容易想到能夠直接遍歷字符串替換空格,可是這樣的話須要重複移動多個元素,時間複雜度爲O(n^2),咱們來看下只須要移動一次的方法。
解題思路
首先遍歷字符串,統計出空格數,計算出替換後的字符串的總長度。 每替換一個空格,長度增長2,所以替換後的字符串長度等於原來的長度加上2*空格數
。 咱們從字符串後面開始複製和替換。準備兩個指針P1和P2,P1指向原始字符串末尾,P2指向替換以後的字符串的末尾。 接下來咱們向前移動指針P1,逐個把它指向的字符複製到P2指向的位置,當碰到空格後,把P1向前移動1格, 同時把P2向前移動3格。當P1和P2指向同一位置時,代表全部空格已替換完畢。
public class Test { public static void replaceBlank(char[] c) { // 判斷輸入是否合法 if (c == null || c.length <= 0) { return; } // 統計字符數組中的空白字符數 int numberOfBlank = 0; //統計c實際的字符數量 int numberOfPractical=0; int i=0; //'\u0000'是char默認值 while(c[i]!='\u0000'){ numberOfPractical++; if(c[i]==' ') numberOfBlank++; i++; } // 計算轉換後的字符長度 int newLength = numberOfBlank * 2 + numberOfPractical; if (newLength > c.length) { // 若是轉換後的長度大於數組的最大長度,直接返回失敗 return; } // 若是沒有空白字符就不用處理 if (numberOfBlank == 0) { return; } // indexOfOriginal 爲指向原始字符串末尾的指針 int indexOfOriginal=numberOfPractical; // indexOfNew 爲指向替換以後的字符串末尾的指針 int indexOfNew=newLength; // 當 indexOfOriginal = indexOfNew 時代表空格已所有替換完畢 while (indexOfOriginal >= 0 && indexOfOriginal < indexOfNew) { // 如是當前字符是空白字符,進行"%20"替換 if (c[indexOfOriginal] == ' ') { c[indexOfNew--] = '0'; c[indexOfNew--] = '2'; c[indexOfNew--] = '%'; } else { // 不然移動字符 c[indexOfNew--] = c[indexOfOriginal]; } indexOfOriginal--; } } }
實現函數double power(double base,int exponent),求base的exponent次方,不須要考慮大數問題。
很容易能夠想到下面這種解法:
public static double power(double base, int exponent) throws Exception { if (base == 0.0 && exponent < 0) throw new Exception("無效輸入"); //指數絕對值 int absExponent = exponent < 0 ? Math.abs(exponent) : exponent; double result = absPower(base, absExponent); //若是指數爲負數,則轉換成結果的倒數 if (exponent < 0) result = 1.0 / result; return result; } //求base的exponent次方 private static double absPower(double base, int exponent) { double result = 1.0; while (exponent-- > 0) { result *= base; } return result; }
這種解法全面但不夠高效,假如算2^32則須要作31次乘法。
若是求2的32次方,咱們能夠先求2^1,而後2^2,2^4,2^8.....直到2^32,這樣就不用作31次乘法了,大大優化效率。
private static double absPower(double base, int exponent) { if (exponent == 0) return 1; if (exponent == 1) return base; double result = absPower(base, exponent >> 1); result *= result; //判斷是否爲奇數 if ((exponent & 1) == 1){ result *= base; } return result; }