【刷題日記】leetcode-493 翻轉對

焜說發表於2020-11-29

一、題目

給定一個陣列 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我們就將 (i, j) 稱作一個重要翻轉對。
你需要返回給定陣列中的重要翻轉對的數量。
示例 1:
輸入: [1,3,2,3,1]
輸出: 2
示例 2:
輸入: [2,4,3,5,1]
輸出: 3
注意:
給定陣列的長度不會超過50000。
輸入陣列中的所有數字都在32位整數的表示範圍內。

二、解題

題目描述的很簡單,拿到題目首先試著用暴力求解,結果果然超時了。想了半天也沒有一個比較清晰的思路,最後去看了題解。

題解基本都先考慮到用歸併排序,在排序的過程中增加滿足條件的資料統計。的確,這是一個很巧妙的想法,利用歸併排序中左陣列都比右陣列小的特性,在歸併過程中增加一次計數的操作就能得到結果,時間複雜度O(nlogn)。詳細的解題思路我就不班門弄斧了,傳送門:「手畫圖解」在歸併排序中加幾行程式碼

在解題之前,我對歸併排序做了個簡單的複習:歸併排序簡單複習。在理解解題思路後,實際編碼中還是有幾個點值得一提:

  1. leetcode上不要用static進行修飾。由於我開始在本地Idea編碼跑單測,用到了static,貼到LC上測試執行都是正常沒有問題的,但是提交程式碼就執行出錯,輸出了錯誤的結果。
  2. 對特例入參要仔細考慮清楚,如空陣列、最大整數,直接*2會導致異常等
  3. 統計翻轉對時,要理解清楚,比較的兩個佇列都是有序的,所以不需要再對佇列下標重置了,見程式碼部分的註釋// i = 0;,開始加了這一句導致還是超時。
class Solution {
    private int fzd = 0;

    public int reversePairs(int[] nums) {
        if(nums.length == 0) {
            return 0;
        }
        mergeDividSort(nums);
        return fzd;
    }
    
    // 歸併排序
    private int[] mergeDividSort(int[] nums) {
        // 只剩一個元素了
        if(nums.length == 1) {
            return nums;
        } else {
            int[] d1 = mergeDividSort(Arrays.copyOfRange(nums, 0, nums.length/2));
            int[] d2 = mergeDividSort(Arrays.copyOfRange(nums, nums.length/2, nums.length));

            int i = 0;
            int j = 0;
            while (i < d1.length && j < d2.length) {
                // 找到第一個翻轉對
                if(d1[i]/2d > d2[j]) {
                    // i 後面的所有都是翻轉對
                    fzd += d1.length - i;
                    // i = 0;
                    j++;
                } else {
                    i++;
                }
            }

            return mergeSort(d1, d2);
        }
    }

    // 針對兩個有序陣列做合併
    private int[] mergeSort(int[] nums1, int[] nums2) {

        int[] merged = new int[nums1.length + nums2.length];

        int i = 0;
        int j = 0;

        // 至少清空一個陣列
        while(i < nums1.length && j < nums2.length) {
            if(nums1[i] < nums2[j]) {
                merged[i+j] = nums1[i];
                i++;
            } else {
                merged[i+j] = nums2[j];
                j++;
            }
        }

        // 將另一個陣列直接新增到合併後的陣列
        if(i == nums1.length && j < nums2.length) {
            for (;j < nums2.length; j++) {
                merged[i+j] = nums2[j];
            }
        }

        if(j == nums2.length && i < nums1.length) {
            for (;i < nums1.length; i++) {
                merged[i+j] = nums1[i];
            }
        }

        return  merged;

    }
}

執行結果:
在這裡插入圖片描述
效率其實還不算高,對比官方題解中歸併排序解法的耗時要稍高一些,主要是我寫的歸併演算法為了方便自己理解,多了一些陣列拷貝相關操作。這裡可以再做優化。

三、小結

這一題還有其他幾種方式的解法,研究後再做更新。

美好的週末從每日一題結束hhh。加油,打工人。

相關文章