【分治】归并排序

1. 排序数组

1. 1题目来源

912. 排序数组

1.2 题目描述

给你一个整数数组 nums,请你将该数组升序排列。

  1. 示例 1:
    输入:nums = [5,2,3,1]
    输出:[1,2,3,5]
  2. 示例 2:
    输入:nums = [5,1,1,2,0,0]
    输出:[0,0,1,1,2,5]

提示:
1 <= nums.length <= 5 * 104
-5 * 104 <= nums[i] <= 5 * 104

1.3 题目解析

本次实现我们使用归并的思想,这里也可以使用其他的排序算法,比如快排。而快排我们在上一期也进行讲解过了——分支-快速排序,快排的原理在八大排序算法中也有详细的讲解,不了解的可以复习一下。

class Solution {
public:
    void MergeSort(vector<int>& nums, int left, int right, vector<int>& temp)
    {
        if (left >= right) return;

        //1. 分解
        int mid = left + (right - left) / 2;
        MergeSort(nums, left, mid, temp);
        MergeSort(nums, mid + 1, right, temp);

        // 合并
        int l = left, r = mid + 1;
        int k = left;
        while (l <= mid && r <= right)
        {
            if (nums[l] < nums[r])
                temp[k++] = nums[l++];
            else temp[k++] = nums[r++];
        }

        while (l <= mid)
            temp[k++] = nums[l++];
        while (r <= right)
            temp[k++] = nums[r++];
        
        // 3. 返回
        for (int i = left; i <= right; i++)
        {
            nums[i] = temp[i];
        }
    }

    vector<int> sortArray(vector<int>& nums) 
    {
        vector<int> temp;
        int n = nums.size();
        temp.resize(n);
        MergeSort(nums, 0, n - 1, temp);
        return nums;  
    }
};

2. LCR 170. 交易逆序对的总数

2. 1题目来源

[LCR 170. 交易逆序对的总数](https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/description/

2.2 题目描述

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

示例 1:
输入:record = [9, 7, 5, 4, 6]
输出:8
解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。

限制:
0 <= record.length <= 50000

2.3 题目解析

这里我们一开始想到的是直接暴力枚举的,但是可以看到0 <= record.length <= 50000,暴力枚举的时间复杂度是O(N^2)肯定是会超时的。所以这里我们不能使用暴力枚举。

在这里插入图片描述

所以这里我们想到了使用归并的方法,这里我们回顾一下归并的方法。归并的方法是先求出中间节点,将其分成左右部分,接着根据左右两部分再次重复上述动作,直到划分出只有一个节点之后,就开始进行重新合并,在此之间就可以进行排序的动作。
在这里插入图片描述

所以在合并的时候我们就可以来实现找逆序对的动作。在我们实现归并的动作时我们可以在实现排序的动作之前将逆序对计算出来。而这个时候对于下次归并来讲是已经有序的了,而对于有序的左右两部分的话我们要计算左右中的逆序对是很好计算的。

因为再上一步我们已经计算出来左右各部分的逆序和,所以就算排好序了也不会影响计算左右的逆序对,反而会更好计算了。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    void MergeSort(vector<int>& nums, int left, int right, vector<int>& temp, int &ret)
    {
        if (left >= right) return;

        int mid = left + (right - left) / 2;
        MergeSort(nums, left, mid, temp, ret);
        MergeSort(nums, mid + 1, right, temp, ret);
        int k = left, l = left, r = mid + 1;
        while (l <= mid && r <= right)
        {
            if (nums[l] <= nums[r]) 
                temp[k++] = nums[l++];
            else 
            {
                ret += mid - l + 1;
                temp[k++] = nums[r++];
            }
        }

        while (l <= mid) 
            temp[k++] = nums[l++];
        while (r <= right) 
            temp[k++] = nums[r++];

        for (int i = left; i <= right; i++)
            nums[i] = temp[i];
    }
    int reversePairs(vector<int>& record) {
        int ret = 0;
        vector<int> temp;
        int n = record.size();
        temp.resize(n);
        MergeSort(record, 0, n - 1, temp, ret);
        return ret;
    }
};

3. 计算右侧小于当前元素的个数

3. 1题目来源

315. 计算右侧小于当前元素的个数

3.2 题目描述

给你一个整数数组 nums ,按要求返回一个新数组 counts 。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

  1. 示例 1:
    输入:nums = [5,2,6,1]
    输出:[2,1,1,0]
    解释:
    5 的右侧有 2 个更小的元素 (2 和 1)
    2 的右侧仅有 1 个更小的元素 (1)
    6 的右侧有 1 个更小的元素 (1)
    1 的右侧有 0 个更小的元素
  2. 示例 2:
    输入:nums = [-1]
    输出:[0]
  3. 示例 3:
    输入:nums = [-1,-1]
    输出:[0,0]

提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104

3.3 题目解析

本题其实和上一题LCR 170. 交易逆序对的总数其实是一样的道理,只不过上一题要求的是总数,而本题求的是每一个数据对应前面比他小的数的个数。而这个时候如果我们使用hash的话,数据又是可以重复的无法使用哈希。所以这里最大的难点在于我们在进行归并的时候如何快速的定位到数据,并根据该数据随对应的原始下标进行计数。

所以这里我们要用到一个辅助数组。
在这里插入图片描述
在这里插入图片描述
所以这个时候我们需要定义两个辅助数组,一个辅助原始数组,一个辅助index数组。

采用降序的方式,进行比较。

在这里插入图片描述
所以具体步就是定义temp,index,index_temp, ret四个容器,ret用来存放返回的数据,temp用来辅助原始数组,index用来存放与原始数组的绑定,indx_temp用来辅助index。

  1. 先直接进行归并的常规操作
  2. 判断nums[l] > nums[r]是否成立,如果成立说明找到了一个数据后面的数比前面的数要写,并需要更新此时l标识数据的下标存放到index_temp中。
  3. 后序同样进行归并的操作,并更新小标索引
  4. 不要忘记重新将辅助数组中的数据放回原来的数组中。
class Solution {
public:
    vector<int> temp,index,index_temp, ret;
    void MergeSort(vector<int>& nums, int left, int right)
    {
        if (left >= right) return;
        int mid = left + (right - left) / 2;

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

        int l = left, r = mid + 1;
        int k = left;
        
        while (l <= mid && r <= right)
        {
            if (nums[l] > nums[r])
            {
                ret[index[l]] += right - r + 1;
                index_temp[k] = index[l];
                temp[k++] = nums[l++];
            }
            else
            {
                index_temp[k] = index[r];
                temp[k++] = nums[r++];
            }
        }
        while (l <= mid) 
        {
            index_temp[k] = index[l];
            temp[k++] = nums[l++];
        }
        while (r <= right) 
        {
            index_temp[k] = index[r];
            temp[k++] = nums[r++];
        }

        for (int i = left; i <= right; i++)
        {
            nums[i] = temp[i];
            index[i] = index_temp[i];
        }
    }
    vector<int> countSmaller(vector<int>& nums) 
    {
        int n = nums.size();
        temp.resize(n);
        index.resize(n);
        index_temp.resize(n);
        ret.resize(n);
        for (int i = 0; i < n; i++) index[i] = i;
        MergeSort(nums, 0, n - 1);
        return ret;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三问走天下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值