面試題51.數組中的逆序對-詳解歸併排序java實現

題目

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

示例 1:輸入: [7,5,6,4]
輸出: 5

限制:0 <= 數組長度 <= 50000

思路一:

暴力解題:依題意,我們可以用雙層for循環累計求和。

java代碼

class Solution {
    public int reversePairs(int[] nums){
    	int count = 0;
	for(int i=0;i<nums.length-1;i++){
		for(int j = i+1;j<nums.length;j++){
			if(nums[i]>nums[j]){
				count++;
			}
		}
	}
	return count;
    }

}

這是一道面試題,答案不可能這麼簡單。運行後會發現,當數據過大,會超時。
此種方法被pass掉。

思路二:

爲了解決這個問題,我們需要設想這樣一個場景。
這道題是要我們輸出逆序數組對,那麼我們怎樣實現這個方式呢,答案是採用分而治之的思想,既然它要我們找逆序數對,我們不妨採用排序算法在排序過程中統計逆序數對。

歸併排序實現逆序數對的統計

首先,我們需要了解什麼是歸併排序,它的思想是什麼。

歸併排序是一種典型的分治策略。
它包含這樣三個步驟:
分解: 待排序的區間爲 l, r,令 m = (l+r)/2,我們把 l, r 分成 [l, m ]和 [m + 1, r],採用遞歸不斷分解。
解決: 使用歸併排序遞歸地排序兩個子序列
合併: 把兩個已經排好序的子序列 [l, m] 和 [m + 1, r] 合併起來

這裏借用網上的圖來給大家分析:
首先我們需要採用遞歸來把數組給劃分成最小單元。然後排序進行合併。

在這裏插入圖片描述

這裏我們對拆分的數組進行合併,原理是開闢一個新的數組,將分散的2個有序數組進行比較並存入新數組中,最後返回這個新數組。

在這裏插入圖片描述

瞭解了歸併排序,我們就可以在此基礎上對我們這一題進行求解。由圖我們可以看出,我們在合併的過程中進行統計逆序數對。只要第一個數組的元素大於第二個數組的元素,我們就可以進行統計。
統計公式爲:count = mid-i+1

java代碼:

  public int reversePairs(int[] nums) {
        int ret = 0;
        int len = nums.length;
        //判空操作
        if(len<2){
            return ret;
        }
        return mergeSort(nums, 0, nums.length - 1);
    }

    private int mergeSort(int[] nums, int left, int right) {
        if (left >= right) {
            return 0;
        }
        //拆分
        int mid = (left+right)/2;
        int leftPairs = mergeSort(nums, left, mid);
        int rightPairs = mergeSort(nums, mid + 1, right);

        //歸併
        int crossPairs = merge(nums, left, mid, right);
        //將歸併的所有結果彙總返回
        return  leftPairs+ rightPairs + crossPairs;
    }

    private int merge(int[] nums, int left, int mid, int right) {
        //指向第一個數組指針
        int i = left;
        //指向第二個數組指針
        int j = mid + 1;

        int k = 0;
        //計數
        int count = 0;
        //開闢新數組存放合併數組
        int res[] = new int[right - left + 1];
        //開始合併
        while (i <= mid && j <= right) {
            //如果j位置小於i位置,那麼j位置小於i位置後所有的左半邊的數
            if (nums[i] > nums[j]) {
                count += mid - i + 1;
                res[k++] = nums[j++];
            }else{
                res[k++] = nums[i++];
            }
//            res[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
        }
        //合併剩下的數組
        while (i <= mid) {
            res[k++] = nums[i++];
        }
        while (j <= right) {
            res[k++] = nums[j++];
        }
        //將值賦值回原數組
        for (int m = 0; m < res.length; m++) {
            nums[left + m] = res[m];
        }
        return count;
    }

如若對此題還未理解,歡迎私信博主。或者是參考力扣官方題解:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/shu-zu-zhong-de-ni-xu-dui-by-leetcode-solution/

歸併算法是一個經典算法,博主提醒小夥伴們務必掌握!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章