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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Richard_M_pu/article/details/82724635

题目描述

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 ( n 2 )
代码:

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 ( n 2 ) 的算法效率不是很高。

2. 思路二

经过一定的观察与思考我们可以发现,统计这个某个数字后面比这个数字小的数字个数,不就是统计包含这个数字的逆序对个数吗,而我们可以利用归并排序的过程来求一个数组的逆序对个数,其算法效率为 O ( n log n )

分治思路:将原数组分为左右两个数组,则左边数组中某个元素的逆序对数 = 其在左数组中的逆序对数 + 右数组中比它小的元素个数。

具体来说,就是每次将数组从中间平分成两个数组,分别对左右数组进行排序,排序完毕之后,进行归并操作(即首先用两个指针分别指向左右两个数组的第一个元素,比较两个元素的大小,将小的那个元素加入有序数组,并将相应的指针后移一位)。在归并操作过程中,每当将右边数组的元素加入有序队列的时候,说明该元素比当前左边数组剩余的所有元素都要小,则左边所有元素的统计结果均加一。

代码:

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 ( n log n ) ,而这种算法效率大大缩短了运行时间。

猜你喜欢

转载自blog.csdn.net/Richard_M_pu/article/details/82724635