剑指offer 学习笔记 数组中的逆序对

面试题51:数组中的逆序对。在数组中的任意两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。如数组{7,5,6,4}中,一共存在5个逆序对。

直观解法是顺序扫描整个数组,每扫描到一个数字,逐个比较该数字和它后面的数字的大小,如果后面的数字比它小,则这两个数字组成一个逆序对。这种算法的时间复杂度是O(n²),太慢了。

当我们每扫描到一个数字时,不能拿它和后面每一个数字进行比较,否则时间复杂度就是O(n²),因此,我们可以考虑先比较两个相邻数字。

我们可以先将数组以中间位置元素为界分为两组,然后对每一组再以中间位置为界来分组,直到分成的两组中的其中一组只有一个元素,然后开始合并,下面是一个例子:
在这里插入图片描述
如上所示,每一次合并两个子数组时,这两个子数组都保证是有序的,合并的具体过程如下:
在这里插入图片描述
如上,我们通过两个指针计算逆序对数,如步骤(a)中,7大于6,因此对于7来说,逆序数为后部子数组中的指针指向的元素及子数组中该指针前面的元素数量之和,即为2,剩下的同理。上面的排序过程本质上就是归并排序,只不过多了一步统计逆序数的过程:

#include <iostream>
#include <vector>
using namespace std;

int InversePairs(vector<int>& nums, vector<int>& copy, int start, int end) {    // 打工函数
    if (start == end) {
        return 0;
    }

    int middle = (start + end) >> 1;    // 中间元素下标
    int endLeft = middle;    // 左部分数组的结尾
    int startRight = middle + 1;    // 右部分数组的开头

    int left = InversePairs(copy, nums, start, endLeft);    // 左部分数组中的逆序数,此处的copy应作为nums的实参,因为每次递归修改的是nums和copy中的一个数组
                                                            // 而下次的使用时需要用到修改过的内容,因此每次交换copy和nums的身份,就可以获取到上次改变的内容
                                                            // 也可以认为因为下面要判断时需要nums的两个子数组已经有序,因此要修改nums值,即把nums当成copy来修改
    int right = InversePairs(copy, nums, startRight, end);    // 右部分数组中的逆序数

    int posInLeft = endLeft;    // 指向左部分数组尾元素的指针
    int posInRight = end;    // 指向右部分数组尾元素的指针
    int posInCopy = end;    // 指向copy数组中存放排序结果的尾位置

    int count = 0;    // 计算当前两个数组合并过程中出现的逆序数的次数
    while (posInLeft >= start && posInRight >= startRight) {
        if (nums[posInLeft] > nums[posInRight]) {
            count += posInRight - startRight + 1;
            copy[posInCopy--] = nums[posInLeft--];
        } else {
            copy[posInCopy--] = nums[posInRight--];
        }
    }

    // 以下两个for循环只能进入一个
    for (; posInLeft >= start; --posInLeft) {
        copy[posInCopy--] = nums[posInLeft];
    }
    for (; posInRight >= startRight; --posInRight) {
        copy[posInCopy--] = nums[posInRight];
    }

    return count + left + right;
}

int InversePairs(vector<int>& nums) {    // 函数入口
    if (nums.size() == 0) {
        return 0;
    }

    vector<int> copy(nums);    // copy初始化为nums
    int count = InversePairs(nums, copy, 0, nums.size() - 1);

    return count;
}

int main() {
    vector<int> nums = { 7,5,6,4 };
    cout << InversePairs(nums) << endl;
}

由于归并排序的时间复杂度是O(nlogn),比直观解法的O(n²)要快,但这是用空间换时间,需要O(n)的空间消耗。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值