劍指 offer(1) -- 陣列篇

TimberLiu發表於2019-04-16

演算法難,難如上青天,但是難也得靜下心來慢慢學習,並總結歸納。所以將劍指 offer 中的題目按照類別進行了歸納,這是第一篇--陣列篇。當然,如果各位大佬發現程式有什麼 bug 或其他更巧妙的思路,歡迎交流學習。

3. 陣列中重複的數字

題目一描述

在一個長度為 n 的陣列裡的所有數字都在 0~n-1 的範圍內。陣列中存在有重複的數字,但不知道有幾個數字重複,也不知道重複了幾次。請找出陣列中任意一個重複的數字。

解題思路

由於陣列中所有數字都在 0 ~ n-1 範圍內,那麼如果陣列中沒有重複的數字,則排序後的陣列中,數字 i 就一定出現在下標為 i 的位置。

所以,可以在遍歷陣列的時候,判斷:

  1. 如果當前位置元素 arr[i] 等於 i,則繼續遍歷;
  2. 否則,將 arr[i]arr[arr[i]] 進行比較:
    • 如果相等,則表示找到了重複的數字;
    • 否則,將它們兩個進行交換,也就是將 arr[i] 放到下標為 i 的位置。然後繼續重複步驟 2 進行比較。

程式碼實現

public boolean duplicate(int[] arr, int[] duplication) {
    if (arr == null || arr.length <= 0) {
        return false;
    }

    for (int i = 0; i < arr.length; i++) {
        while (arr[i] != i) {
            if (arr[i] == arr[arr[i]]) {
                duplication[0] = arr[i];
                return true;
            }

            int temp = arr[i];
            arr[i] = arr[temp];
            arr[temp] = temp;
        }
    }

    return false;
}
複製程式碼

這種方法,每一個數字最多隻需要交換兩次就可以歸位:

  • 第一次和當前正在遍歷的元素進行交換;
  • 第二次就可以將它歸位。

因此時間複雜度是 O(n)。由於不需要額外空間,空間複雜度是 O(1)

題目二描述

在一個長度為 n+1 的陣列中所有數字都在 1~n 範圍內,所以陣列中至少有一個數字重複。請找出任意一個重複的數字,但是不能修改原有的陣列。

解題思路

由於陣列中所有數字都在 1 ~ n 範圍內,所以可以將 1 ~ n 的陣列從中間值 m 分為 1 ~ mm+1 ~ n 兩部分。如果 1 ~ m 之間的數字超過了 m 個,表示重複數字在 1 ~ m 之間,否則在 m+1 ~ n 之間。

然後繼續將包含重複數字的區間分為兩部分,繼續判斷直到找到一個重複的數字。

程式碼實現

public int duplicateNumber(int[] arr) {
    if (arr == null || arr.length <= 0) {
        return -1;
    }
    int start = 1;
    int end = arr.length - 1;
    while (start <= end) {
        int mid = ((end - start) >> 1) + start;
        int count = countRange(arr, start, mid);
        if (start == mid) {
            if (count > 1) {
                return start;
            } else {
                break;
            }
        }

        if (count > (mid - start + 1)) {
            end = mid;
        } else {
            start = mid + 1;
        }
    }

    return -1;
}

public int countRange(int[] arr, int start, int end) {
    int count = 0;
    for (int i = 0; i < arr.length; i++) {
        if (arr[i] >= start && arr[i] <= end) {
            count++;
        }
    }
    return count;
}
複製程式碼

按照二分查詢的思路,函式 countRange 會被呼叫 log(n) 次,每次需要 O(n) 的時間,所以總的時間複雜度是 O(nlogn)

4. 二位陣列中的查詢

題目描述

在一個二維陣列中,每一行按照從左到右遞增的順序排序,每一列按照從上到下遞增的順序排序。要求實現一個函式,輸入一個二位陣列和一個整數,判斷該整數是否在陣列中。

解題思路

這裡可以選取左下角或右上角的元素進行比較。這裡,以右上角為例:

對於右上角的元素,如果該元素大於要查詢的數字,則要查詢的數字一定在它的左邊,將 col--,如果該元素小於要查詢的數字,則要查詢的數字一定在它的下邊,將 row++,否則,找到了該元素,查詢結束。

public boolean find(int target, int [][] array) {
    if(array == null || array.length <= 0 || array[0].length <= 0) {
        return false;
    }
    
    int row = 0;
    int col = array[0].length - 1;
    while(row < array.length && col >= 0){
        if(array[row][col] > target) {
            col--;
        } else if(array[row][col] < target) {
            row++;
        } else {
            return true;
        }
    }
    
    return false;
}
複製程式碼

11. 旋轉陣列的最小數字

題目描述

將一個陣列最開始的幾個元素移動陣列的末尾,稱為旋轉陣列。輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。

解題思路

由於陣列在一定程度上是有序的,所以可以採用類似二分查詢的方法來解決。可以使用兩個指標,start 指向陣列的第一個元素,end 指向最後一個元素,接著讓 mid 指向陣列的中間元素。

這裡需要考慮一類特殊情況,就是陣列中存在重複元素,例如 1 1 1 0 1 或者 1 0 1 1 1 的情況,這時利用二分法已經不能解決,只能進行順序遍歷。

一般情況下,判斷陣列中間元素(mid)與陣列最後一個元素(end)的大小,如果陣列中間元素大於最後一個元素,則中間元素屬於前半部分的非遞減子陣列,例如 3 4 5 1 2。此時最小的元素一定位於中間元素的後面,則將 start 變為 mid + 1

否則的話,也就是陣列中間元素(mid)小於等於最後一個元素(end),則中間元素屬於後半部分的非遞減子陣列中,例如 2 0 1 1 1,或者 4 5 1 2 3。此時最小的元素可能就是中間元素,可能在中間元素的前面,所以將 end 變為 mid

如此,直到 start 大於等於 end 退出迴圈時,start 指向的就是最小的元素。

程式碼實現

public int minNumberInRotateArray(int [] array) {
    if(array == null || array.length <= 0) {
        return 0;
    }
    int start = 0;
    int end = array.length - 1;
    
    // 陣列長度為 1 時,該元素必然是最小的元素,也就不需要再判斷 start == end 的情況
    while(start < end) { 
        int mid = start + ((end - start) >> 1);
        if (array[start] == array[end] && array[start] == array[mid]) {
            return min(array, start, end);
        }
        
        if(array[mid] > array[end]) {
            start = mid + 1;
        } else {
            end = mid;
        } 
    }
    
    return array[start];
}

public int min(int[] array, int start, int end) {
    int min = array[start];
    for(int i = start + 1; i <= end; i++) {
        if(array[i] < min) {
            min = array[i];
        }
    }
    return min;
}
複製程式碼

21. 調整陣列順序使奇數位於偶數前面

題目描述

輸入一個整數陣列,實現一個函式來調整該陣列中數字的順序,使得所有的奇數位於陣列的前半部分,所有的偶數位於陣列的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

解題思路

這裡有兩種解題思路:第一種是利用插入排序的思路(其實只要能保證穩定性的排序演算法都可以),遍歷陣列,如果該元素是奇數,則對前面的元素進行,如果前面的元素是偶數則進行交換,直到找到一個奇數為止。

第二種是藉助輔助陣列,首先遍歷一遍陣列,將所有奇數元素儲存到輔助陣列中,並計算出奇數元素的個數;然後再遍歷一遍輔助陣列,將其中所有奇數元素放到原陣列的前半部分,將所有偶數元素放到從 count 開始的後半部分。

程式碼實現

// 時間複雜度 O(n^2)
public static void reorderOddEven1(int[] data) {
    for (int i = 1; i < data.length; i++) {
        if ((data[i] & 1) == 1) {
            int temp = data[i];
            int j = i - 1;
            for (; j >= 0 && (data[j] & 1) == 0; j--) {
                data[j + 1] = data[j];
            }
            data[j + 1] = temp;
        }
    }
}

// 時間複雜度 O(n) 空間複雜度 O(n)
public static void reorderOddEven2(int[] data) {
    if (data == null || data.length <= 0) {
        return;
    }
    int count = 0;
    int[] tempArr = new int[data.length];
    for (int i = 0; i < data.length; i++) {
        if ((data[i] & 1) == 1) {
            count++;
        }
        tempArr[i] = data[i];
    }

    int j = 0, k = count;
    for (int i = 0; i < data.length; i++) {
        if ((tempArr[i] & 1) == 1) {
            data[j++] = tempArr[i];
        } else {
            data[k++] = tempArr[i];
        }
    }
}
複製程式碼

這裡第一種做法,和插入排序的時間複雜度一致,平均情況下時間複雜度為 O(n^2),在最好情況下時間複雜度是 O(n)

而第二種做法,由於只需要遍歷兩次陣列,所以時間複雜度為 O(n)。但是需要藉助輔助陣列,所以空間複雜度是 O(n)

29. 順時針列印矩陣

題目描述

輸入一個矩陣,按照從外向裡以順時針的順序依次列印出每一個數字。如果輸入如下 4 X 4 矩陣:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
則依次列印出數字 1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10。

解題思路

在列印矩陣時,可以按照從外到內一圈一圈來列印,於是可以使用迴圈來列印矩陣,每次迴圈列印一圈。對於一個 5 * 5 的矩陣,迴圈結束條件是 2 * 2 < 5,而對於一個 6 * 6 的矩陣,迴圈結束條件是 2 * 3 < 6。所以可以得出迴圈結束的條件是 2 * start < rows && 2 * start < cols

在列印一圈時,可以分為從左到右列印第一行、從上到下列印最後一列、從右到左列印最後一行、從下到上列印第一列。但是這裡需要考慮最後一圈退化為一行、一列的情況。

程式碼實現

public ArrayList<Integer> printMatrix(int [][] matrix) {
    ArrayList<Integer> res = new ArrayList<>();
    int rows, cols;
    if(matrix == null || (rows = matrix.length) <= 0 || (cols = matrix[0].length) <= 0){
        return res;
    }
    int i = 0;
    while(2 * i < rows && 2 * i < cols) {
        printMatrixCore(matrix, i++, res);    
    }
    return res;
}

public void printMatrixCore(int[][] matrix, int start, ArrayList<Integer> res) {
    int endX = matrix.length - start - 1;
    int endY = matrix[0].length - start - 1;
    
    // 第一行總是存在的
    for(int i = start; i <= endY; i++) {
        res.add(matrix[start][i]);
    }
    
    // 至少要有兩行
    if(endX > start) {
        for(int j = start + 1; j <= endX; j++) {
            res.add(matrix[j][endY]);
        }
    }
    
    // 至少要有兩行兩列
    if(endX > start && endY > start) {
        for(int i = endY - 1; i >= start; i--) {
            res.add(matrix[endX][i]);
        }
    }
    
    // 至少要有三行兩列
    if(endX > start + 1 && endY > start) {
        for(int j = endX - 1; j > start; j--) {
            res.add(matrix[j][start]);
        }
    }
}
複製程式碼

39. 陣列中出現次數超過一半的數字

題目描述

陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為 9 的陣列 {1,2,3,2,2,2,5,4,2}。由於數字 2 在陣列中出現了 5 次,超過陣列長度的一半,因此輸出 2。如果不存在則輸出 0。

解題思路1

由於有一個數字出現次數超過了陣列長度的一半,所以如果陣列有序的話,那麼陣列的中位數必然是出現次數超過一半的數。

但是這裡沒有必要完全對陣列排好序。可以利用快速排序的思想,使用 partition 函式,對陣列進行切分,使得切分元素之前的元素都小於等於它,之後的元素都大於等於它。

一次切分之後可以將切分元素的下標 index 與陣列中間的 mid 比較,如果 index 大於 mid,表示中間值在左半部分,將 end = mid - 1,繼續進行切分;而如果 index 小於 mid,表示中間值在右半部分,將 start = mid + 1,繼續進行切分;否則表示找到了出現次數超過一半的元素。

程式碼實現1

public int MoreThanHalfNum_Solution(int [] array) {
    if(array == null || array.length <= 0) {
        return 0;
    }
    int start = 0;
    int end = array.length - 1;
    int mid = (end - start) >> 1;
    
    int index = partition(array, start, end);
    while(index != mid) {
        if(index > mid) {
            end = index - 1;
            index = partition(array, start, end);
        } else {
            start = index + 1;
            index = partition(array, start, end);
        }
    }
    
    if(checkMoreThanHalf(array, array[index])) {
        return array[index];
    }
    return 0;
}

public int partition(int[] array, int left, int right) {
    int pivot = array[left];
    int i = left, j = right + 1;
    while(true) {
        while(i < right && array[++i] < pivot) {
            if(i == right) { break; }
        }
        while(j > left && array[--j] > pivot) { }
        if(i >= j) {
            break;
        }
        int temp = array[i];
        array[i] = array[j];
        array[j] = temp;
    }
    
    array[left] = array[j];
    array[j] = pivot;
    
    return j;
}

public boolean checkMoreThanHalf(int[] array, int res) {
    int count = 0;
    for(int i = 0; i < array.length; i++) {
        if(array[i] == res) {
            count++;
        }
    }
    return count * 2 > array.length;
}
複製程式碼

解題思路2

還有一種解題思路,它是利用陣列的特點,使用一個 times 來記錄某個數的出現的次數,然後遍歷陣列,如果 times0,將當前元素賦給 result,並將 times 置為 1;否則如果當前元素等於 result,則將 times1,否則將 times1

如此在遍歷完陣列,出現次數 times 大於等於 1 對應的那個數一定就是出現次數超過陣列一半長度的數。

程式碼實現2

public static int moreThanHalfNum(int[] number) {
    if (number == null || number.length <= 0) {
        return -1;
    }

    int result = 0;
    int times = 0;
    for (int i = 0; i < number.length; i++) {
        if (times == 0) {
            result = number[i];
            times = 1;
        } else if (result == number[i]) {
            times++;
        } else {
            times--;
        }
    }

    if (checkMoreThanHalf(number, result)) {
        return result;
    }
    return -1;
}

private static boolean checkMoreThanHalf(int[] number, int result) {
    int count = 0;
    for (int a : number) {
        if (a == result) {
            count++;
        }
    }

    return count * 2 > number.length;
}
複製程式碼

這種方法只需要遍歷一遍陣列,就可以找到找到陣列中出現次數超過一半的數,所以時間複雜度是 O(n)。雖然與前一種方法的時間複雜度一致,但無疑簡潔了不少。

42. 連續子陣列的最大和

題目描述

輸入一個整型陣列。陣列裡有正數和負數。陣列中一個或多個連續的整陣列成一個子陣列,求所有子陣列和的最大值。

解題思路

可以從頭到尾遍歷陣列,如果前面數個元素之和 lastSum 小於 0,就將其捨棄,將 curSum 賦值為 array[i]。否則將前面數個元素之和 lastSum 加上當前元素 array[i],得到新的和 curSum。然後判斷這個和 curSum 與儲存的最大和 maxSum,如果 curSum 大於 maxSum,則將其替換。然後更新 lastSum,繼續遍歷陣列進行比較。

程式碼實現

public int findGreatestSumOfSubArray(int[] array) {
    if(array == null || array.length <= 0) {
        return -1;
    }
    int lastSum = 0;
    int curSum = 0;
    int maxSum = Integer.MIN_VALUE;
    
    for(int i = 0; i < array.length; i++) {
        if(lastSum <= 0) {
            curSum = array[i];
        } else {
            curSum = lastSum + array[i];
        }
        if(curSum > maxSum) {
            maxSum = curSum;
        }
        lastSum = curSum;
    }
    
    return maxSum;
}
複製程式碼

51. 陣列中的逆序對

題目描述

在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個陣列,求出這個陣列中的逆序對的總數 P,並將 P 對 1000000007 取模的結果輸出,即輸出 P%1000000007。

解題思路

首先把陣列分成兩個子陣列,然後遞迴地對子陣列求逆序對,統計出子陣列內部的逆序對的數目。

由於已經統計了子陣列內部的逆序對的數目,所以需要這兩個子陣列進行排序,避免在後面重復統計。在排序的時候,還要統計兩個子陣列之間的逆序對的數目。

注意,這裡如果 aux[i] > aux[j],應該是 count += mid + 1 - i;,也就是從下標為 i ~ mid 的元素與下標為 j 的元素都構成了逆序對。而如果是 count += j - mid; 的話,則成了下標為 i 的元素與下標為 mid + 1 ~ j 的元素構成了逆序對,後面會出現重複統計的情況。

最後對兩個子陣列內部的逆序對和兩個子陣列之間的逆序對相加,返回即可。

程式碼實現

public int inversePairs(int [] array) {
    if(array == null || array.length <= 0) {
        return 0;
    }
    int[] aux = new int[array.length];
    return inversePairs(array, aux, 0, array.length - 1);
}

public int inversePairs(int[] data, int[] aux, int start, int end) {
    if(start >= end) {
        return 0;
    }
    int mid = start + ((end - start) >> 1);
    int left = inversePairs(data, aux, start, mid);
    int right = inversePairs(data, aux, mid + 1, end);
    
    for(int i = start; i <= end; i++) {
        aux[i] = data[i];
    }
    int i = start;
    int j = mid + 1;
    int count = 0;
    for(int k = start; k <= end; k++) {
        if(i > mid) {
            data[k] = aux[j++];
        } else if(j > end) {
            data[k] = aux[i++];
        } else if(aux[i] > aux[j]) {
            data[k] = aux[j++];
            count += mid + 1 - i;
            count %= 1000000007;
        } else {
            data[k] = aux[i++];
        }
    }
    
    return (left + right + count) % 1000000007;
}
複製程式碼

這種方法與歸併排序的時間、空間複雜度一致,每次排序的時間為 O(n),總共需要 O(logn) 次,所以總的時間複雜度是 O(nlogn)。在歸併時需要輔助陣列,所以其空間複雜度為 O(n)

53. 在排序陣列中查詢數字

題目一描述

統計一個數字在排序陣列中出現的次數。

解題思路

對於排序陣列,可以使用兩次二分查詢分別找到要查詢的數字第一次和最後一次出現的陣列下標。然後就可以計算出該數字出現的次數。

查詢第一次出現的陣列下標時,如果陣列中間元素大於該數字 k,則在陣列左半部分去查詢,否則陣列中間元素小於該數字 k,則在陣列右半部分去查詢。

當中間元素等於 k 時,則需要判斷 mid,如果 mid 前面沒有數字,或者前面的數字不等於 k,則找到了第一次出現的陣列下標;否則繼續在陣列左半部分去查詢。

查詢最後一次出現的陣列下標與查詢第一次出現的思想類似,這裡就不再贅述了。

程式碼實現

public int GetNumberOfK(int [] array , int k) {
    if(array == null || array.length <= 0) {
        return 0;
    }
    int left = getFirstIndex(array, k);
    int right = getLastIndex(array, k);
    if(left != -1 && right != -1) {
        return right - left + 1;
    }
    return 0;
}

public int getFirstIndex(int[] array, int k) {
    int start = 0;
    int end = array.length - 1;
    while(start <= end) {
        int mid = start + ((end - start) >> 1);
        if(k == array[mid]) {
            if(mid == 0 || array[mid - 1] != k) {
                return mid;
            } else {
                end = mid - 1;
            }
        } else if(k < array[mid]) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    
    return -1;
}

public int getLastIndex(int[] array, int k) {
    int start = 0;
    int end = array.length - 1;
    while(start <= end) {
        int mid = start + ((end - start) >> 1);
        if(k == array[mid]) {
            if(mid == end || array[mid + 1] != k) {
                return mid;
            } else {
                start = mid + 1;
            }
        } else if(k < array[mid]) {
            end = mid - 1;
        } else {
            start = mid + 1;
        }
    }
    
    return -1;
}
複製程式碼

題目二描述

一個長度為 n 的遞增陣列中的所有數字都是唯一的,並且每個數字都在 [0, n] 範圍內,在 [0, n] 範圍內的 n+1 個數字中有且只有一個數字不在陣列中,請找出這個數字。

解題思路

由於陣列是有序的,所以陣列開始的一部分數字與它們對應的下標是相等。如果不在陣列中的數字為 m,則它前面的數字與它們的下標都相等,它後面的數字比它們的下標都要小。

可以使用二分查詢,如果中間元素的值和下標相等,則在陣列右半部分查詢;如果不相等,則需要進一步判斷,如果它前面沒有元素,或者前面的數字和它的下標相等,則找到了 m;否則繼續在左半部分查詢。

程式碼實現

public static int getMissingNumber(int[] arr) {
    if (arr == null || arr.length <= 0) {
        return -1;
    }

    int start = 0;
    int end = arr.length - 1;
    while (start <= end) {
        int mid = start + ((end - start) >> 1);
        if (arr[mid] != mid) {
            // 當前不相等,前一個相等,表示找到了
            if (mid == 0 || arr[mid - 1] == mid - 1) {
                return mid;
            // 左半邊查詢
            } else {
                end = mid - 1;
            }
        } else {
            //右半邊查詢
            start = mid + 1;
        }
    }

    if (start == arr.length) {
        return arr.length;
    }
    return -1;
}
複製程式碼

56. 陣列中數字出現的次數

題目一描述

一個整型陣列裡除了兩個數字之外,其他的數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。

解題思路

這裡解題思路有些巧妙,使用位元素來解決,由於兩個相等的數字異或後結果為 0,所以遍歷該陣列,依次異或陣列中的每一個元素,那麼最終的結果就是那兩個只出現一次的數字異或的結果。

由於這兩個數字肯定不一樣,異或的結果也肯定不為 0,也就是它的二進位制表示中至少有一位是 1,將該位求出後記為 n

可以將以第 n 位為標準將原陣列分為兩個陣列,第一個陣列中第 n 位是 1,而第二個陣列中第 n 位是 0,而兩個只出現一次的數字必然各出現在一個陣列中,並且陣列中的元素異或的結果就是隻出現一次的那個數字。

程式碼實現

public void findNumsAppearOnce(int [] array,int num1[] , int num2[]) {
    if(array == null || array.length <= 0) {
        return;
    }
    int num = 0;
    for(int i = 0; i < array.length; i++) {
        num ^= array[i];
    }
    
    int index = bitOf1(num);
    int mark = 1 << index;
    
    for(int i = 0; i < array.length; i++) {
        if((array[i] & mark) == 0) {
            num1[0] ^= array[i];
        } else {
            num2[0] ^= array[i];
        }
    }
}

public int bitOf1(int num) {
    int count = 0;
    while((num & 1) == 0) {
        num >>= 1;
        count++;
    }
    return count;
}
複製程式碼

題目二描述

一個整型陣列裡除了一個數字只出現了一次之外,其他的數字都出現了三次。請找出那個只出現一次的數字。

解題思路

這裡由於出現了三次,雖然不能再使用異或運算,但同樣可以使用位運算。可以從頭到尾遍歷陣列,將陣列中每一個元素的二進位制表示的每一位都加起來,使用一個 32 位的輔助陣列來儲存二進位制表示的每一位的和。

對於所有出現三次的元素,它們的二進位制表示的每一位之和,肯定可以被 3 整除,所以最終輔助陣列中如果某一位能被 3 整除,那麼那個只出現一次的整數的二進位制表示的那一位就是 0,否則就是 1

程式碼實現

public static void findNumberAppearOnce(int[] arr, int[] num) {
    if (arr == null || arr.length < 2) {
        return;
    }

    int[] bitSum = new int[32];
    for (int i = 0; i < arr.length; i++) {
        int bitMask = 1;
        for (int j = 31; j >= 0; j--) {
            int bit = arr[i] & bitMask;
            if (bit != 0) {
                bitSum[j]++;
            }
            bitMask <<= 1;
        }
    }

    int result = 0;
    for (int i = 0; i < 32; i++) {
        result <<= 1;
        result += bitSum[i] % 3;
    }
    num[0] = result;
}
複製程式碼

66. 構建乘積陣列

題目描述

給定一個陣列 A[0,1,...,n-1],請構建一個陣列 B[0,1,...,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×...×A[i-1]×A[i+1]×...×A[n-1]。不能使用除法。

解題思路

這裡要求 B[i] = A[0] * A[1] * ... * A[i-1] * A[i+1] * ... * A[n-1],可以將其分為分為兩個部分的乘積。

A[0] * A[1] * ... * A[i-2] * A[i-1]
A[i+1] * A[i+2] * ... * A[n-2] * A[n-1]
複製程式碼

可以使用兩個迴圈,第一個迴圈採用自上而下的順序,res[i] = res[i - 1] * arr[i - 1] 計算前半部分,第二迴圈採用自下而上的順序,res[i] *= (temp *= arr[i + 1])

程式碼實現

public int[] multiply(int[] arr) {
    // arr [2, 1, 3, 4, 5]
    if (arr == null || arr.length <= 0) {
        return null;
    }

    int[] result = new int[arr.length];
    result[0] = 1;
    for (int i = 1; i < arr.length; i++) {
        result[i] = arr[i - 1] * result[i - 1];
    }
    // result [1, 2, 2, 6, 24]

    int temp = 1;
    for (int i = result.length - 2; i >= 0; i--) {
        temp = arr[i + 1] * temp;
        result[i] = result[i] * temp;
    }
    // temp  60  60   20  5   1
    // result [60, 120, 40, 30, 24]
    return result;
}
複製程式碼

相關文章