剑指offer-35 -- 数组中的逆序对 - C++

题目描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

输入描述:

题目保证输入的数组中没有的相同的数字

数据范围:

对于%50的数据,size<=10^4

对于%75的数据,size<=10^5

对于%100的数据,size<=2*10^5

示例1

输入

1,2,3,4,5,6,7,0

输出

7

归并排序思想

首先还是提出两个问题,带着问题来看题解,我觉得效率更好。
Q1:为什么归并排序需要额外的空间?
Q2:为什么此题的最优解法可以借助归并排序的思想?

 

A1: 首先回答一下第一个问题,为什么归并排序需要额外空间?
显然我们知道,归并排序的过程就是,递归划分整个区间为基本相等的左右区间,直到左右区间各只有一个数字,然后就合并两个有序区间。
问题就出在了合并两个有序区间上,需要额外的空间。
为什么呢?
这里我举个例子,比如需要合并的两个有序区间为[3 4] 和 [1 2]
我们需要得到最后的结果为[1 2 3 4], 如果不需要额外的空间的话,是做不到的,
当比较1 和 3 的时候, 1 比 3 小,就会覆盖原来的位置。

A2:回答第二个问题之前,先了解一下归并排序的过程,主要有以下两个操作:

  • 递归划分整个区间为基本相等的左右两个区间
  • 合并两个有序区间

可能看了代码,更好理解:

// 合并过程
void merge__(vector<int> &arr, int l, int mid, int r) {
    // 在这个地方创建额外空间,是一种不好的做法,更好的做法,等下讲
    vector<int> tmp(r - l + 1);
    int i = l, j = mid + 1, k = 0;
 
    while (i <= mid && j <= r) {
        if (arr[i] >= arr[j]) {
            tmp[k++] = arr[j++];
        }
        else {
            tmp[k++] = arr[i++];
        }
    }
 
    while (i <= mid) {
        tmp[k++] = arr[i++];
    }
    while (j <= r) {
        tmp[k++] = arr[j++];
    }
 
    for (k = 0, i = l; i <= r; ++i, ++k) {
        arr[i] = tmp[k];
    }
}
 
// 递归划分过程
void merge_sort__(vector<int> &arr, int l, int r) {
    // 只有一个数字,则停止划分
    if (l >= r) {
        return;
    }
 
    int mid = l + ((r - l) >> 1);
    merge_sort__(arr, l, mid);
    merge_sort__(arr, mid + 1, r);
    // 合并两个有序区间
    merge__(arr, l, mid, r);
}
// 要排序的数组 arr
void merge_sort(vector<int>& arr) {
    merge_sort__(arr, 0, arr.size() - 1);
}

明白了归并排序的过程,那么回答问题2.
如果两个区间为[4, 3] 和[1, 2]
那么逆序数为(4,1),(4,2),(3,1),(3,2),同样的如果区间变为有序,比如[3,4] 和 [1,2]的结果是一样的,也就是说区间有序和无序结果是一样的。
但是如果区间有序会有什么好处吗?当然,如果区间有序,比如[3,4] 和 [1,2]
如果3 > 1, 显然3后面的所有数都是大于1, 这里为 4 > 1, 明白其中的奥秘了吧。所以我们可以在合并的时候利用这个规则。

直接上代码:

class Solution {
private:
    const int kmod = 1000000007;
public:
    int InversePairs(vector<int> data) {
        int ret = 0;
        merge_sort__(data, 0, data.size() - 1, ret);
        return ret;
    }
 
 
    void merge_sort__(vector<int> &arr, int l, int r, int &ret) {
        if (l >= r) {
            return;
        }
 
        int mid = l + ((r - l) >> 1);
        merge_sort__(arr, l, mid, ret);
        merge_sort__(arr, mid + 1, r, ret);
        merge__(arr, l, mid, r, ret);
    }
 
    void merge__(vector<int> &arr, int l, int mid, int r, int &ret) {
        vector<int> tmp(r - l + 1);
        int i = l, j = mid + 1, k = 0;
 
        while (i <= mid && j <= r) {
            if (arr[i] > arr[j]) {
                tmp[k++] = arr[j++];
                // 奥妙之处
                ret += (mid - i + 1);
                ret %= kmod;
            }
            else {
                tmp[k++] = arr[i++];
            }
        }
 
        while (i <= mid) {
            tmp[k++] = arr[i++];
        }
        while (j <= r) {
            tmp[k++] = arr[j++];
        }
 
        for (k = 0, i = l; i <= r; ++i, ++k) {
            arr[i] = tmp[k];
        }
    }
};

刚才提到在函数内部开辟额外空间的做法很不好。因为这样会涉及到频繁的构建 vector 和析构vector,所以比较好的做法是:直接在最外层开辟一个足够大的数组,然后传引用到函数。
代码如下:

class Solution {
private:
    const int kmod = 1000000007;
public:
    int InversePairs(vector<int> data) {
        int ret = 0;
        // 在最外层开辟数组
        vector<int> tmp(data.size());
        merge_sort__(data, tmp, 0, data.size() - 1, ret);
        return ret;
    }
 
    void merge_sort__(vector<int> &arr, vector<int> &tmp, int l, int r, int &ret) {
        if (l >= r) {
            return;
        }
 
        int mid = l + ((r - l) >> 1);
        merge_sort__(arr, tmp, l, mid, ret);
        merge_sort__(arr, tmp, mid + 1, r, ret);
        merge__(arr, tmp, l, mid, r, ret);
    }
 
    void merge__(vector<int> &arr, vector<int> &tmp, int l, int mid, int r, int &ret) {
        int i = l, j = mid + 1, k = 0;
 
        while (i <= mid && j <= r) {
            if (arr[i] > arr[j]) {
                tmp[k++] = arr[j++];
                ret += (mid - i + 1);
                ret %= kmod;
            }
            else {
                tmp[k++] = arr[i++];
            }
        }
 
        while (i <= mid) {
            tmp[k++] = arr[i++];
        }
        while (j <= r) {
            tmp[k++] = arr[j++];
        }
 
        for (k = 0, i = l; i <= r; ++i, ++k) {
            arr[i] = tmp[k];
        }
    }
 
};

时间复杂度:O(NlogN)
空间复杂度:O(N)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值