劍指offer(一)

最近在看《劍指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的個數

請實現一個函數,輸入一個整數,輸出該數二進制表示中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;
}

解法3(大神解法)

把一個整數減去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。

解題思路

咱們注意到數組中的數字都在0n-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;   
}
相關文章
相關標籤/搜索