剑指 Offer 51. 数组中的逆序对

剑指 Offer 51. 数组中的逆序对 - 力扣(LeetCode)

暴力法

简单粗暴,复杂度O(n^2),但是也是意料之中的超时:

class Solution {
public:
    int reversePairs(vector<int>& nums) {
        if(!nums.size()) return 0;
        int num = 0;
        for(int i = 0; i < nums.size()-1; ++i){
            for(int j = i+1; j < nums.size(); ++j){
                if(nums[i] > nums[j]) ++num;
            }
        }
        return num;
    }
};

归并排序

参考:
数据结构与算法之美:38 | 分治算法:谈一谈大规模计算框架MapReduce中的分治思想_qq_32523711的博客-CSDN博客

class Solution {
public:
    int num = 0;
    void merge_count(vector<int> &nums, int beg, int mid, int end){
        vector<int> tmp(end-beg+1);
        int i = beg, j = mid+1;
        int k = 0;
        while(i <= mid && j <= end){
            if(nums[i] <= nums[j])  tmp[k++] = nums[i++];
            else{
                num += mid - i +1;//统计beg~mid之间,比nus[j]大的元素
                tmp[k++] = nums[j++];
            }
        }
        while(i <= mid) tmp[k++] = nums[i++];
        while(j <= end) tmp[k++] = nums[j++];
        for(const auto &c : tmp)    nums[beg++] = c;
    }
    void merge_sort(vector<int> &nums, int beg, int end){
        if(beg >= end) return;
        int mid = (end + beg)>>1;
        merge_sort(nums, beg, mid);
        merge_sort(nums, mid+1, end);
        merge_count(nums, beg, mid, end);
    }
    int reversePairs(vector<int>& nums) {
        merge_sort(nums, 0, nums.size()-1);
        return num;
    }
};

复杂度O(nlogn),虽然是解出来了,但是效率却一般。根据官方视频题解说的原因是,每次合并都申请tmp数组再释放,会比较耗时,以及耗空间。所以可以进行修改如下:

class Solution {
public:
    int num = 0;
    vector<int> tmp;//只申请一次tmp数组
    void merge_count(vector<int> &nums, int beg, int mid, int end){
        int i = beg, j = mid+1;
        int k = 0;
        while(i <= mid && j <= end){
            if(nums[i] <= nums[j])  tmp[k++] = nums[i++];
            else{
                num += mid - i +1;//统计beg~mid之间,比nus[j]大的元素
                tmp[k++] = nums[j++];
            }
        }
        while(i <= mid) tmp[k++] = nums[i++];
        while(j <= end) tmp[k++] = nums[j++];
        //for(int i = 0; i < k; ++i)    nums[beg++] = tmp[i];//排序后的元素拷贝回nums
        copy(tmp.begin(), tmp.begin() + k, nums.begin() + beg);//使用标准库函数好像更快一点
    }
    void merge_sort(vector<int> &nums, int beg, int end){
        if(beg >= end) return;
        int mid = beg + (end - beg)/2;
        merge_sort(nums, beg, mid);
        merge_sort(nums, mid+1, end);
        if(nums[mid] <= nums[mid+1])    return; //加一个判断,此时数组已经是有序的,不用进行后续操作了
        merge_count(nums, beg, mid, end);
    }
    int reversePairs(vector<int>& nums) {
        if(nums.size() < 2) return 0;
        tmp = vector<int>(nums.size(), 0);
        merge_sort(nums, 0, nums.size()-1);
        return num;
    }
};

不使用全局变量,使用返回值叠加也是可以的:

class Solution {
public:
    vector<int> tmp;//只申请一次tmp数组
    int merge_count(vector<int> &nums, int beg, int mid, int end){
        int i = beg, j = mid+1;
        int k = 0;
        int num = 0;//计数器
        while(i <= mid && j <= end){
            if(nums[i] <= nums[j])  tmp[k++] = nums[i++];
            else{
                num += mid - i +1;//统计beg~mid之间,比nus[j]大的元素
                tmp[k++] = nums[j++];
            }
        }
        while(i <= mid) tmp[k++] = nums[i++];
        while(j <= end) tmp[k++] = nums[j++];
        copy(tmp.begin(), tmp.begin() + k, nums.begin() + beg);
        return num;
    }
    int merge_sort(vector<int> &nums, int beg, int end){
        if(beg >= end) return 0;
        int mid = beg + (end - beg)/2;
        int left = merge_sort(nums, beg, mid);
        int right = merge_sort(nums, mid+1, end);
        if(nums[mid] <= nums[mid+1])    return left + right; //加一个判断,此时数组已经是有序的,不用进行后续操作了
        int cross = merge_count(nums, beg, mid, end);
        return left + right + cross;
    }
    int reversePairs(vector<int>& nums) {
        if(nums.size() < 2) return 0;
        tmp = vector<int>(nums.size(), 0);
        return merge_sort(nums, 0, nums.size()-1);
    }
};

快速排序

在排序算法里学过,逆序对的个数其实就对应系列的逆序度,那有序对就对应有序度了:

数据结构与算法之美:12 | 排序(下):如何用快排思想在O(n)内查找第K大元素?_qq_32523711的博客-CSDN博客_12下|利用快排思想在o(n)内查找第k大元素

快速排序里面,每交换一个元素,序列的有序度就增加1,逆序度就减少1,那么总的元素交换次数其实就是初始序列的逆序度,也就是逆序对的个数。

快速排序不行,我上面说的是错的,我在放屁。。

二分插入

想了想,我们可以从后往前遍历nums数组,当遍历到每个元素的时候,这个元素后面的元素是遍历过的,也就是如果可以有某种方法记录后面的元素比当前元素小的有多少个的话,那也是可以解这个题的。什么方法呢,将每次遍历的元素一次加入辅助数组data并排序,当前元素在数组data中的插入位置刚好就记录了有多少元素比当前元素小,而插入位置可以使用二分查找。

class Solution {
public:
    int count = 0;
    vector<int> data;
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        if(n < 2) return 0;
        data.push_back(nums[n-1]);//先保存最后一个
        for(int i = n-2; i >= 0; --i){
            auto it = lower_bound(data.begin(), data.end(), nums[i]);//下界,第一个不小于等于给定值的
            count += it - data.begin();
            data.insert(it, nums[i]);
        }
        return count;
    }
};

好吧,想法是美好的,二分虽然复杂度低,但是后续的元素插入是O(n)的,总体还是O(n^2),不幸超时了。

还可以尝试的思路有快速排序的划分,二叉查找树,不过最坏时间复杂度都是O(n^2)。

二叉查找树

也是会超时的

class Solution {
public:
    vector<int> res;
    struct node{
        int val;
        int count ; //用于记录比该节点小的元素个数
        node *left, *right;
        node(int data) : val(data), count(1), left(NULL), right(NULL) {}
    };
    void insert(node* root, int val, int count){
        if(val <= root->val){//插入在左子树
            if(root->left)  insert(root->left, val, count);
            else{
                auto newnode = new node(val);
                root->left = newnode;
                res.push_back(count);
            }
            ++root->count;
        }else{//插入在右子树
            if(root->right) insert(root->right, val, count+root->count);
            else{
                node *newnode = new node(val);
                root->right = newnode;
                res.push_back(count+root->count);
            }
        }
    }

    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        if(n < 2) return 0;
        node *root = new node(nums.back());//最后一个元素为根节点,count初始化为1
        for(int i = n-2; i >= 0; --i)   insert(root, nums[i], 0);
        return accumulate(res.begin(), res.end(), 0);
    }
};

猜你喜欢

转载自blog.csdn.net/qq_32523711/article/details/107900729