1. 程式人生 > >leetcode [Divide and Conquer] No.315 Count of Smaller Numbers After Self

leetcode [Divide and Conquer] No.315 Count of Smaller Numbers After Self

題目描述

You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].

Example:

Input: [5,2,6,1] Output: [2,1,1,0] Explanation: To the right of 5 there are 2 smaller elements (2 and 1). To the right of 2 there is only 1 smaller element (1). To the right of 6 there is 1 smaller element (1). To the right of 1 there is 0 smaller element.

解題思路

1. 思路一

看完題目第一個想法也是最樸素的想法便是重迴圈,對於每一個數,遍歷其後的所有數,統計小於其數字的個數,很容易看出,這種演算法的效率為O(n2)程式碼:

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<int> res;
        for (int i = 0; i < nums.size(); i++) {
            int count = 0;
            for
(int j = i + 1; j < nums.size(); j++) { if (nums[i] > nums[j]) { count++; } } res.push_back(count); } return res; } };

執行結果: 這裡寫圖片描述 可見,O(n2)的演算法效率不是很高。

2. 思路二

經過一定的觀察與思考我們可以發現,統計這個某個數字後面比這個數字小的數字個數,不就是統計包含這個數字的逆序對個數嗎,而我們可以利用歸併排序的過程來求一個數組的逆序對個數,其演算法效率為

O(nlogn)

分治思路:將原陣列分為左右兩個陣列,則左邊陣列中某個元素的逆序對數 = 其在左陣列中的逆序對數 + 右陣列中比它小的元素個數。

具體來說,就是每次將陣列從中間平分成兩個陣列,分別對左右陣列進行排序,排序完畢之後,進行歸併操作(即首先用兩個指標分別指向左右兩個陣列的第一個元素,比較兩個元素的大小,將小的那個元素加入有序陣列,並將相應的指標後移一位)。在歸併操作過程中,每當將右邊陣列的元素加入有序佇列的時候,說明該元素比當前左邊陣列剩餘的所有元素都要小,則左邊所有元素的統計結果均加一。

程式碼:

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        //統計計算結果的向量
        vector<int> res(nums.size(), 0);
        //用於歸併排序的向量,其中pair的第一個元素表示值,第二個元素表示該元素在初始陣列中的座標
        vector<pair<int, int> > vec;
        for (int i = 0; i < nums.size(); i++) {
            vec.push_back(make_pair(nums[i], i));
        }
        //使用歸併排序的方法,計算結果
        subCounter(vec, res);
        return res;
    }

private:
    void subCounter(vector<pair<int, int> >& nums, vector<int>& res) {
        //遞迴終止條件
        if (nums.size() <= 1) return;
        //將陣列平分為左右兩個陣列
        int mid = nums.size() / 2;
        vector<pair<int, int> > left(nums.begin(), nums.begin() + mid);
        vector<pair<int, int> > right(nums.begin() + mid, nums.end());

        //分別對左右陣列進行排序和計算逆序對個數
        subCounter(left, res);
        subCounter(right, res);

        //歸併操作
        vector<pair<int, int> > conquer;
        auto liter = left.begin(), riter = right.begin();
        int count = 0;
        while (liter != left.end() && riter != right.end()) {
            //左陣列的元素小,將它加入有序佇列,並累加相應的逆序對數
            if (liter->first <= riter->first) {
                conquer.push_back(*liter);
                res[liter->second] += count;
                liter++;
            //右陣列的元素小,將它加入有序佇列,逆序對數+1
            } else {
                conquer.push_back(*riter);
                count++;
                riter++;
            }
        }
        //將左右陣列中剩餘的元素加入有序佇列
        while(liter != left.end()) {
            conquer.push_back(*liter);
            res[liter->second] += count;
            liter++;
        }
        while(riter != right.end()) {
            conquer.push_back(*riter);
            riter++;
        }
        //將排序後的結果賦值給原無序陣列
        nums.assign(conquer.begin(), conquer.end());
    }   
};

執行結果: 這裡寫圖片描述 通過分治的策略,我們將演算法效率提升到了O(nlogn),而這種演算法效率大大縮短了執行時間。