1. 程式人生 > 實用技巧 >劍指Offer_#51_陣列中的逆序對

劍指Offer_#51_陣列中的逆序對

劍指Offer_#51_陣列中的逆序對

劍指offer

Contents

題目

在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個陣列中的逆序對的總數。
示例 1:

輸入: [7,5,6,4]
輸出: 5

限制:

0 <= 陣列長度 <= 50000

思路分析

最直接的思路是暴力迴圈,即用兩層迴圈迴圈判斷每一對數字是否是逆序對,然後計數,時間複雜度為,會超時。
所以必須時間複雜度更低的演算法,這裡用到了歸併排序中的merge方法(詳見演算法第四版2.2節),只需要在標準的merge方法中新增一行計數的程式碼,即可實現本題的需求。

解答

class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;
        if(len < 2) return 0;
        int[] copy = new int[len];
        //如果面試題不允許修改原陣列nums,就需要拷貝原陣列
        for(int i = 0;i <= len - 1;i++){
            copy[i] = nums[i];
        }
        int[] temp = new
int[len]; return reversePairs(copy, 0, len - 1, temp); } //遞迴函式 private int reversePairs(int[] nums,int left,int right,int temp[]){ //出口條件:給出的[left...right]區間內只有一個元素,逆序對是0 if(left == right) return 0; //遞推過程:分別計算當前區間左半段的逆序對,右半段的逆序對,跨越左右半段的逆序對,然後相加 //防止出現大數溢位
int mid = left + (right - left) / 2; //計算[left...mid]區間內的逆序對 int leftPairs = reversePairs(nums, left, mid, temp); //計算[mid+1...right]區間內的逆序對 int rightPairs = reversePairs(nums, mid + 1, right, temp); //如果整個陣列都是有序的,那麼不需要繼續進行歸併了 if(nums[mid] <= nums[mid+1]) return leftPairs + rightPairs; //歸併兩個有序的區間,然後計算這跨越兩個區間的逆序對數目 int crossPairs = mergeAndCount(nums, left, mid, right, temp); return leftPairs + rightPairs + crossPairs; } private int mergeAndCount(int[] nums, int left, int mid,int right,int[] temp){ //複製nums到temp for(int i = left;i <= right;i++){ temp[i] = nums[i]; } int i = left; int j = mid + 1; int count = 0; //歸併[left...mid]和[mid+1,right]兩個有序陣列,使得[left...right]範圍內的數字為有序 for(int k = left;k <= right;k++){ //i指標越界,直接歸併j指向的元素 if(i == mid + 1){ nums[k] = temp[j]; j++; //j指標越界,直接歸併i指向的元素 }else if(j == right + 1){ nums[k] = temp[i]; i++; //這裡不能寫<,否則連相等的數字對也會計算在內 }else if(temp[i] <= temp[j]){ nums[k] = temp[i]; i++; }else{ nums[k] = temp[j]; j++; //相比歸併排序中的merge()方法,僅僅增加了這一行程式碼 count += (mid - i + 1); } } return count; } }