[LC优选算法#7] 分治:快排&归并 | 库存管理 III | 排序数组 | 交易逆序对的总数

1. 分治:快速排序

关于快速排序算法的详细介绍请移步至此博文:

LC优选算法#5 分治:快排 | 颜色分类 | 排序数组 | 第K大元素

1.1 库存管理 III

库存管理 III

解题思路:

这道题是经典的TopK问题,要求返回数组中前小的数,通用解法是通过堆排序 (大根)逐个筛选出前k小的数,时间复杂度是O(Nlogk)。但是由于返回顺序不定,因此我们可以选用快速选择 算法,在筛选出符合条件的区间后,直接返回。时间复杂度为O(N)

具体的实现方式依旧是随机选key + 数组分三块

cpp 复制代码
class Solution {
public:
    void Quick_Sort_K(vector<int>& nums, int l, int r, int k)
    {
        if(l >= r) return;

        int key = getRandom(nums, l,r);
        int left = l - 1;
        int right = r + 1;
        int i = l;
        
        while(i < right)
        {
            if(nums[i] < key) swap(nums[++left], nums[i++]);
            else if(nums[i] > key) swap(nums[--right], nums[i]);
            else i++; 
        }

        int a = left - l + 1;
        int b = right - left - 1;

        if(k <= a) Quick_Sort_K(nums, l, left, k);
        else if(k <= a + b) return;
        else Quick_Sort_K(nums, right, r, k - a - b);
    }

    int getRandom(vector<int>& nums, int l, int r)
    {
        return nums[rand() % (r - l) + l];
    }

    vector<int> inventoryManagement(vector<int>& stock, int cnt)
    {
        srand(time(NULL));

        Quick_Sort_K(stock, 0, stock.size()-1, cnt);

        return {stock.begin(), stock.begin() + cnt};


    }
};

2. 分治:归并排序

关于归并排序方法的基本介绍可移步至此篇博文:

C语言:排序(二)

在算法中,归并排序思想并没有在原本排序的基础上有很大改动,时间复杂度为O(NlogN)。如果说快速排序算法 是一种前序 遍历排序,那么归并排序算法 就是一种后序 遍历排序:

在归并排序中,我们通常会新建一个临时数组用于存放原数组部分排序后的元素,而在算法中我们采取的方式是新建一个全局 数组tmp,每次归并后逐一赋值给原数组,这样可以省去创建和销毁局部变量的时间。

2.1 排序数组

排序数组

解题思路:

这道题可以手搓任意时间复杂度小于等于O(NlogN)的排序解答,而归并排序的时间复杂度为O(NlogN),符合题意,算是一道练手题。

cpp 复制代码
class Solution {
public:
    vector<int> tmp;
    void Merge_Sort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return;

        int mid = (left + right) / 2;

        Merge_Sort(nums, left, mid);
        Merge_Sort(nums, mid + 1, right);

        int begin1 = left;
        int end1 = mid;
        int begin2 = mid + 1;
        int end2 = right;
        int i = left;

        while(begin1 <= end1 && begin2 <= end2)
        {
            tmp[i++] = nums[begin1] <= nums[begin2] ? nums[begin1++] : nums[begin2++];
        }

        while(begin1 <= end1) tmp[i++] = nums[begin1++];
        while(begin2 <= end2) tmp[i++] = nums[begin2++];

        for(int i=left; i<=right; i++)
        {
            nums[i] = tmp[i];
        }
    }

    vector<int> sortArray(vector<int>& nums)
    {
        tmp.resize(nums.size());
        Merge_Sort(nums, 0, nums.size() - 1);

        return nums;
    }
};

2.2 交易逆序对的总数

交易逆序对的总数

解题思路:

  1. 暴力枚举 O(N^2):双层嵌套for循环枚举出所有两数组合,判断是否符合逆序对,会超时。
  2. 归并排序 O(NlogN):如果把数组分为两块,我们会发现这样一个规律:逆序对的组合无非就是左区间 / 右区间 / 一左一右

    在左、右区间分别找完逆序对后,如何一左一右寻找逆序对呢?答案就是我们可以分别对左右区间排序 ,逐个比对(类似双指针):

    但是排序的次数不止一次,区间的拆分也不只一层,据此我们可以间接联想到用归并排序 的方式解决。
    升序版本

    降序版本 :注意降序版本不能仅仅改变比较运算符,还要注意计算也会重复!

    这道题在原本归并排序的基础上增加了ret这个细节,处理好后代码还是很好写的:
cpp 复制代码
class Solution {
public:
    vector<int> tmp;

    int reversePairs(vector<int>& record)
    {
        tmp.resize(record.size());
        return Merge_Sort1(record, 0, record.size() - 1);
    }

    //升序
    int Merge_Sort1(vector<int>& nums, int left, int right)
    {
        if(left >= right) return 0;

        int ret = 0;
        int mid = (left + right) / 2;

        //左个数 + 排序
        ret += Merge_Sort(nums, left, mid);
        //右个数 + 排序
        ret += Merge_Sort(nums, mid + 1, right);

        int cur1 = left;
        int cur2 = mid + 1;
        int i = 0;

        //左右升序排序
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
            {
                tmp[i++] = nums[cur1++];
            }
            else
            {
                ret += mid - cur1 + 1;
                tmp[i++] = nums[cur2++];
            }
        }

        while(cur1 <= mid) tmp[i++] = nums[cur1++];
        while(cur2 <= right) tmp[i++] = nums[cur2++];

        for(int j=left; j<=right; j++)
        {
            nums[j] = tmp[j - left];
        }

        return ret;
    }

    // //降序
    // int Merge_Sort(vector<int> &nums, int left, int right)
    // {
    //     if(left >= right) return 0;

    //     int mid = (left + right) / 2;
    //     int ret = 0;
        
    //     ret += Merge_Sort(nums, left, mid);
    //     ret += Merge_Sort(nums, mid + 1, right);

    //     int cur1 = left;
    //     int cur2 = mid + 1;
    //     int i = 0;

    //     while(cur1 <= mid && cur2 <= right)
    //     {
    //         if(nums[cur1] > nums[cur2])
    //         {
    //             ret += right - cur2 + 1;
    //             tmp[i++] = nums[cur1++];
    //         }
    //         else
    //         {
    //             tmp[i++] = nums[cur2++];
    //         }
    //     }

    //     while(cur1 <= mid) tmp[i++] = nums[cur1++];
    //     while(cur2 <= right) tmp[i++] = nums[cur2++];

    //     for(int j=left; j<=right; j++)
    //     {
    //         nums[j] = tmp[j - left];
    //     }
        
    //     return ret;
    // }
};

// 本期内容就到这里啦,如果对你有帮助,请三连支持!我是青云,我们下期见^_~