【ONE·基础算法 || 分治·快排&并归】

在这里插入图片描述

总言

  主要内容:编程题举例,理解分治的思想(主要是对快排、并归的应用)。
  
  


  
  
  
  
  

1、基本介绍

  分治是一种解决问题的策略,它将一个大问题分解成若干个小问题,这些小问题与原问题类型相同但规模更小,然后递归地解决这些小问题,最后将小问题的解合并,从而得到原问题的解。这种策略在许多算法中都有应用,如排序、搜索、图论问题等。

  快速排序就是基于分治策略的一种排序算法。它的基本思想是选取一个基准值,通过一趟排序将待排序的数据分割成独立的两部分,其中一部分的所有数据都比另一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。快速排序的时间复杂度为O(nlogn),是目前基于比较的内部排序里被认为是最好的方法,特别适用于大数据集。

  归并排序则是另一种基于分治策略的排序算法。它的主要思路是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的,然后再把有序子序列合并为整体有序序列。归并排序的过程可以分为分解和合并两个步骤,分解是将序列每次折半划分,合并则是将划分后的序列段两两合并后排序。这种排序算法是稳定的,适用于各种规模的数据集。
  
  
  
  

2、颜色分类(medium)

  题源:链接

在这里插入图片描述

  

2.1、题解

  1)、思路分析
  此题的思路本质可划分为双指针(双指针复习链接:移动零。),但对此进行了一点变动,使用的是三指针:一个用于遍历数组的指针+两个用于划分区域的指针

在这里插入图片描述
  PS:上述指针如何取名完全可随意,不必生搬硬套。
  

  如何判断移动?
  1、初始时: i = 0left = -1right = numsSize(若数组尾元素下标为n-1right指向尾元素下一个位置,即n
  2、i从左到右遍历过程中:
  ①、若nums[cur] == 0 ;说明 i 位置指向的元素要纳入左区域范围内。因此,交换 left + 1 与 i 位置的元素,并让 left++ (因为纳入了新元素, 0 序列的右边界应当右滑扩展),cur++ (为什么可以 ++ ?因为 left + 1 位置要么是 0 ,要么是 1,交换完毕之后,这个位置的值已经符合我们的要求,因此 cur++ );
  ②、nums[cur] == 1 ;说明这个位置应该在 left 和 cur 之间,此时无需交换,直接让 cur++ ,判断下⼀个元素即可;
  ③、nums[cur] == 2 ;说明这个位置的元素应该在 right - 1 的位置,因此交换 right - 1 与 cur 位置的元素,并且让 right-- (指向 2 序列的左边界),cur 不变(因为交换过来的数是没有被判断过的,因此需要在下轮循环中判断)
  
  
  2)、题解

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int left = -1, right = nums.size(), i = 0;

        while(i < right)//right表⽰的是2序列的左边界,因此当碰到right时,说明已经将所有数据扫描完毕了
        {
            if(nums[i] == 0)
                swap(nums[i++],nums[++left]);
            else if(nums[i] == 1)
                i++;
            else //nums[i] == 2
                swap(nums[i],nums[--right]);//cur 不变,因为交换过来的数是没有被判断过的
        }
    }
};

  
  
  
  
  
  

3、快速排序(medium)

  题源:链接

在这里插入图片描述
  
  

3.1、题解:三指针版本的快排

  1)、思路分析
  这题的题解方法有多种,这里我们主要学习快排,后续讲以同样的题讲解归并。
  
  快排,我们在排序章节讲解过几种写法:Hoare法挖坑法双指针法,并且当时对key值的优化使用的是三数取中。这里我们讲解三指针法,以及随机数法选取key值。
  
  三指针法: 这里三指针的原理同上述颜色分类。
  从待排序的数组中选择一个元素作为基准值(key),以该基准值进行排序,将数组划分为左、中 、右三部分(以升序为例)。
在这里插入图片描述
  三指针排序的过程在上述颜色分类讲解过,这里不做赘述。
  
  在对当前序列进行三块划分处理后,再去递归排序左边部分和右边部分即可(可以舍去大量的中间部分,减少不必要的交换操作。 在处理有大量重复元素的数组时,效率会大大提升)。
在这里插入图片描述
  
  
  随机数法取key值: 在主函数中种一颗随机数种子, 由于我们要随机产生一个满足[left,right]区间范围内的基准元素值,因此可以将生成随机数转换成为生成当前[left,right]区间的随机下标。如何做到?

(rand() % (right -left +1 )) + left

  解释:这里我们设区间大小为n,让随机数 % 上区间大小,可得 [0,n-1]范围内的随机数,加上区间的左边界,即可得到 [left, left + (n -1)] 范围内的值,即[left, right]区间范围内的值。
  
  
  
  2)、题解

class Solution {
public:

    void quick(vector<int>& nums, int left, int right)
    {
        if(left >= right) return; //递归结束条件

        //随机数法选取key值
        int n = right - left + 1;//当前段区间内元素总数
        int index = (rand() % n) + left;//获取到[left, right]区间段内的随机下标
        int key = nums[index];//这里不能直接用key的下标进行排序(即与nums[index]比较是错误的)。
        //因为快排属于交换排序,index下标位置的值会在排序过程中被交换。

        //三指针法进行区间排序:[left,right]
        int i = left, l = left - 1, r = right + 1;
        while(i < r)
        {
            if(nums[i] < key) swap(nums[++l],nums[i++]);
            else if(nums[i] == key) i++;
            else swap(nums[--r],nums[i]);//交换过来的数是没有被判断过,此处i不能++
        }
        //对左右排序:[left, l] [l+1, r-1] [r,right]
        quick(nums,left,l);
        quick(nums,r,right);
    }

    vector<int> sortArray(vector<int>& nums) {
        srand(time(nullptr));// 种下⼀个随机数种⼦
        quick(nums,0,nums.size()-1);//使用递归法排序,这里传入[left,right]区间
        return nums;
    }
};

  
  
  
  
  
  

4、快速选择算法(medium)

  题源:链接

在这里插入图片描述

  

4.1、题解

  1)、思路分析
  这题若使用的是选择排序,比如直接选择排序或堆排序(堆排时间复杂度为 N l o g N NlogN NlogN),也可以解决此题:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        //建堆
        priority_queue<int> maxHeap(nums.begin(),nums.end());

        //取前K-1个数
        while(--k)//此写法下要用前置自减
        {
            maxHeap.pop();
        }

        //取第K个数
        return maxHeap.top();
    }
};

  
  
  这里我们主要学习应用快排:

在这里插入图片描述

  
  

  2)、题解
  使用升序的写法:

class Solution {
public:
    int qsort(vector<int>& nums, int k, int left, int right) {
        if (left >= right) // 递归调用
            return nums[left];

        int n = right - left + 1;   // 当前区间的元素总数
        int id = rand() % n + left; // 获取当前区间段的随机下标
        int key = nums[id]; // 获取该下标上的元素值(作为key值使用)

        int i = left, l = left - 1, r = right + 1;

        while (i < r) // 排升序,找第k大。
        {
            if (nums[i] < key)
                swap(nums[i++], nums[++l]);
            else if (nums[i] == key)
                i++;
            else
                swap(nums[i], nums[--r]);
        }

        // 用于判断下一轮递归区间:[left,l]  [l+1,r-1]  [r, right]
        int a = l - left + 1;
        int b = r - 1 - (l + 1) + 1;
        int c = right - r + 1;

        if (c >= k)
            return qsort(nums, k, r, right);
        else if (b + c >= k)
            return key;
        else
            return qsort(nums, k - b - c, left, l);
    }

    int findKthLargest(vector<int>& nums, int k) {
        srand(time(nullptr)); // 种下随机种子
        return qsort(nums, k, 0, nums.size() - 1); // 传入的区间边界应该是闭区间[left,right]
    }
};

  使用降序的写法:只是对当前序列的排序部分及判断下一轮递归的条件做了变动。

        while (i < r) // 排降序,找第k大。
        {
            if (nums[i] < key)
                swap(nums[i], nums[--r]);
            else if (nums[i] == key)
                i++;
            else
                swap(nums[i++], nums[++l]);
        }

        // 用于判断下一轮递归区间:[left,l]  [l+1,r-1]  [r, right]
        int a = l - left + 1;
        int b = r - 1 - (l + 1) + 1;
        int c = right - r + 1;

        if (a >= k)
            return qsort(nums, k, left, l);
        else if (a + b >= k)
            return key;
        else
            return qsort(nums, k - b - a, r, right);

  
  
  
  
  

5、最小的 k 个数(medium)

  题源:链接

在这里插入图片描述
  
  

5.1、题解

  1)、思路分析
  此题解法多种,可用堆的TOP-K( N l o g K NlogK NlogK)、可用排序(如快排: N l o g N NlogN NlogN)、还可以用快速选择算法(即上述题4讲解的原理。)
  这里主要演示快速选择算法。
在这里插入图片描述
  
  
  2)、题解
  以升序为例: 最后我们返回时,前K个元素区间虽然没有完全有序,但已经满足了找出题目最小的K个数的要求。

class Solution {
public:
    void qsort(vector<int>& arr, int left, int right, int k)
    {
        if(left >= right) return;
            
        int n = right - left + 1;
        int index = rand() % n + left;
        int key = arr[index];

        //三指针排序: [left,right],排升序
        int L = left - 1 , R = right + 1, i = left;
        while(i < R)
        {
            if(arr[i] < key) swap(arr[++L], arr[i++]);
            else if(arr[i] == key) ++i;
            else swap(arr[--R], arr[i]);
        }

        //对左右区间:[left, L] [L+1, R-1] [R,right]
        int a = L - left + 1;
        int b = R-1 - (L+1) + 1;
        int c = right - R + 1;
        if(a >= k)  qsort(arr, left, L, k);
        else if(a + b >= k) return;
        else qsort(arr, R, right, k-a-b);
    }

    vector<int> smallestK(vector<int>& arr, int k) {
        srand(time(nullptr));
        qsort(arr,0,arr.size()-1,k);
        return {arr.begin(),arr.begin()+k};
    }
};

  
  
  
  
  
  
  
  

6、归并排序(medium)

  题源:链接

在这里插入图片描述

  

6.1、题解:复习快排(二路归并)

  1)、思路分析
  在上述,我们用快排解过此题,这里我们使用归并排序来解决。PS:归并排序相关复习见博文:常见排序
  这里主要是指二路归并。 总体思想不变,细节实现看个人写法:
  1、分割: 将待排序的数组从中间分割成两个子数组,直到子数组的大小为1(即每个子数组只包含一个元素,自然是有序的)。
  2、递归排序: 递归地对子数组进行归并排序(「左半部分排序」 + 「右半部分排序」)。
  3、合并: 将两个已排序的子数组合并成一个有序的大数组。(通常借助一个辅助数组,可以是每次归并时创建临时变量,也可以用全局变量或者在堆区开辟
  
  
  2)、题解

class Solution {
public:

    vector<int> temp;

    void MergeSort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return;

        //这里使用二路归并,选择中间点将区间划分为两段:[left,mid][mid+1,right]
        int mid = ( right + left ) / 2;//题中给出数据不大(也可以使用防越界版本的求中间值)
        //先对左右区间排序
        MergeSort(nums,left,mid);
        MergeSort(nums,mid+1,right);
        //再合并两个有序数组(来到此处,意味着当前左右两段区间各自有序)
        int cur1 = left; 
        int cur2 = mid+1;
        int i = 0;
        while(cur1 <= mid && cur2 <= right)
            temp[i++] =  nums[cur1] < nums[cur2] ? nums[cur1++] : nums[cur2++];

        //处理剩余区间
        while(cur1 <= mid) temp[i++] = nums[cur1++];
        while(cur2 <= right) temp[i++] = nums[cur2++];

        //将排序好的元素重新返回原数组中(可借助memcpy等函数)
        for(int j = left; j <= right; ++j)
        {
            nums[j] = temp[j - left];
        }

    }

    vector<int> sortArray(vector<int>& nums) {

        temp.resize(nums.size());//归并需要借助一个辅助数组,这里可以每次归并时定义,也可以直接搞一个全局变量(或者堆上申请)
        MergeSort(nums,0,nums.size()-1);
        return nums;
    }
};

  
  关于将排序好的元素重新返回原数组中,其它方法:

	//可以使用memcpy进行拷贝
    memcpy(&nums[begin], &temp[begin], sizeof(int)* (end - begin + 1));

    // 也可以使用std::copy替代memcpy  
    std::copy(temp.begin() + begin, temp.begin() + end + 1, nums.begin() + begin); 

  相关文档查阅:链接
在这里插入图片描述

  
  
  题外话:
  理论上也可以实现k路归并排序(k-way merge sort),其中k是大于1的整数。在k路归并排序中,待排序的数组被分割成k个更小的子数组,而不是仅仅两个。然后,这k个子数组被分别排序,并最终合并成一个完整的有序数组。
  k路归并排序在某些特定场景下可能具有优势,比如当待排序的数据分布在多个不连续的内存区域时,或者当使用并行计算资源时。然而,实现k路归并排序通常比实现二路归并排序更为复杂,并且不一定总是带来性能上的提升。因此,在实际应用中,二路归并排序更为常见和流行。
  
  
  
  
  
  
  
  

7、数组中的逆序对(hard)

  题源:链接

在这里插入图片描述

  
  

7.1、题解

  1)、思路分析
  直接解法: 双层循环暴力枚举,看在难度hard的份上,大概率超时。
  
  问题一:为什么可以使用归并排序?
  1、题目要求找出数组中所有逆序对,既如此,将数组一分为二,先找出左区间中的逆序对,再找出右区间的逆序对,最后选出一左一右在两区间的逆序对。效果与从左到右/从右到左线性遍历查找等同。
在这里插入图片描述

  
  2、对此进行优化。在找出左区间中的逆序对后我们对其排个序,同理,在找出右区间的逆序对后也对其排序。此时,尽管左右两区间内部元素顺序发生改变,但对于在两区间中分别选取元素组成一左一右的逆序对并无影响。
  3、回顾上述历程,这不就和归并的思想一样吗?当然,在选取出一左一右的逆序对后,我们要对当前整体区间也进行排序

归并排序的过程:
• 先排序左数组;
• 再排序右数组;
• 左数组和右数组合⼆为⼀。

逆序对中两个元素:
• 全部从左数组中选择逆序对
• 全部从右数组中选择逆序对
• ⼀个选左数组另⼀个选右数组,组成逆序对

  
  
  
  问题二:如何使用归并排序?及,为什么这里使用归并会提高效率(单调性)
  除了这里举例的两种(升序+固定右值找左侧;降序+固定左值找右侧),如何选取与组合看个人风格。
在这里插入图片描述
  
  
  
  
  2)、题解
  升序写法如下:

class Solution {
public:
    int tmp[50005]={0};

    int mergesort(vector<int>& record, int left, int right)
    {
        if(left >= right) return 0;

        //取中数,分区间:[left, mid] [mid+1, right]
        int mid = (left+right) / 2;
        int sum = 0;//用于统计
        //左区间找逆序对+排序、右区间找逆序对+排序
        sum += mergesort(record,left,mid)+ mergesort(record,mid+1,right);
        //一左一右找逆序对+排序
        int cur1 = left, cur2 = mid + 1;
        int i = 0;
        while(cur1 <= mid && cur2 <= right)//排升序,找左数比右数大
        {
            if(record[cur1] <= record[cur2])
                tmp[i++] = record[cur1++];
            else//record[cur1] > record[cur2]
            {
                sum += mid - cur1 + 1;
                tmp[i++] = record[cur2++];
            }
        }

        //排序:处理剩余元素
        while(cur1 <= mid) tmp[i++] = record[cur1++];
        while(cur2 <= right) tmp[i++] = record[cur2++];

        //将有序数列返回原数列中
        for(int j = left; j <= right; ++j)
            record[j]=tmp[j-left];

        //返回
        return sum;
    }


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

  降序写法总体不变,只需稍加改动即可:

        while(cur1 <= mid && cur2 <= right)//排降序,找左数比右数大
        {
            if(record[cur1] <= record[cur2])
                tmp[i++] = record[cur2++];
            else//record[cur1] > record[cur2]
            {
                sum += right - cur2 + 1;
                tmp[i++] = record[cur1++];
            }
        }

  
  
  
  
  
  
  
  
  

8、计算右侧小于当前元素的个数(hard)

  题源:链接

在这里插入图片描述
  
  

8.1、题解

  1)、思路分析
  有了上一题的铺垫,这一道题的解法与之类似,但是这一道题要求的不是求总的个数,而是要返回一个数组,记录每一个元素的右边有多少个元素比自己小
在这里插入图片描述

  这里存在一个问题,在归并排序的过程中,元素的下标是会跟着变化的。 当前我们查找出的顺序与需要返回的顺序不一定完全匹配。
在这里插入图片描述
  因此,为了确保返回时数组输出的元素顺序,我们需要一个额外数组 index,将数组元素和对应的下标绑定在一起归并,也就是在归并nums中元素的时候,顺势将其下标index也转移到对应的位置上。(意味着辅助数组也需要两个,分别用于排序归并后的nums、index。)
  
  
  
  2)、题解
  这是vector<int> indexvector<int> count使用全局变量的版本。

class Solution {
public:
    int tmp_n[100005];//辅助数组1:归并时用于排序nums
    int tmp_i[100005];//辅助数组2:归并时用于排序index
    vector<int> index;//记录元素对应下标
    vector<int> count;//用于返回输出结果

    void mergesort(vector<int>& nums, int left, int right)
    {
        if(left>=right) return;

        //求中值划分区间:[left,mid] [mid+1,right]
        int mid = (left + right) /2;
        
        //先对左右区间进行找数+归并排序:
        mergesort(nums, left, mid);
        mergesort(nums,mid+1, right);

        //再对当前整段区间进行找数+排序:
        int cur1 = left, cur2 = mid+1;
        int i = 0;
        while(cur1<=mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
            {
                tmp_n[i] = nums[cur2];
                tmp_i[i++] = index[cur2++];//注意这里一次处理两个数组
            }
            else//nums[cur1] > nums[cur2]
            {
                count[index[cur1]] += right - cur2 + 1;
                tmp_n[i] = nums[cur1];
                tmp_i[i++] = index[cur1++];
            }
        }

        //处理剩余区间
        while(cur1 <= mid)
        {
            tmp_n[i]=nums[cur1];
            tmp_i[i++]=index[cur1++];
        }
        while(cur2 <= right)
        {
            tmp_n[i]=nums[cur2];
            tmp_i[i++]=index[cur2++];
        }

        //将获取到的有序数据写回原数组中
        for(int j = left; j <= right; ++j)
        {
            nums[j]=tmp_n[j-left];
            index[j]=tmp_i[j-left];
        }
    }


    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        index.resize(n);//重新扩容
        count.resize(n);

        for(int i = 0; i < n; ++i)//初始化下标
        {
            index[i] = i;
        }

        mergesort(nums, 0, n-1);
        
        return count;
    }
};

  
  如果不使用全局变量,也可如下,但需要传参时多增几个参数。总之写法不一。

class Solution {
public:
    int tmp_n[100005];
    int tmp_i[100005];

    void mergesort(vector<int>& nums, int left, int right, vector<int>& index, vector<int>& count)
    {
        if(left >= right) return;

        //[left,mid] [mid+1,right]
        int mid = (left + right) /2;
        
        //左右区间:
        mergesort(nums, left, mid, index,count);
        mergesort(nums,mid+1, right, index,count);

        //当前整个区间:
        int cur1 = left, cur2 = mid+1;
        int i = 0;
        while(cur1<=mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
            {
                tmp_n[i] = nums[cur2];
                tmp_i[i++] = index[cur2++];
            }
            else//nums[cur1] > nums[cur2]
            {
                count[index[cur1]] += right - cur2 + 1;
                tmp_n[i] = nums[cur1];
                tmp_i[i++] = index[cur1++];
            }
        }

        //处理剩余区间
        while(cur1 <= mid)
        {
            tmp_n[i]=nums[cur1];
            tmp_i[i++]=index[cur1++];
        }
        while(cur2 <= right)
        {
            tmp_n[i]=nums[cur2];
            tmp_i[i++]=index[cur2++];
        }

        //写回原数组中
        for(int j = left; j <= right; ++j)
        {
            nums[j]=tmp_n[j-left];
            index[j]=tmp_i[j-left];
        }
    }


    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        vector<int> index(n);
        vector<int> count(n);

        for(int i = 0; i < n; ++i)
        {
            index[i] = i;
        }

        mergesort(nums, 0, n-1, index, count);
        return count;
    }
};

  
  关于count[index[cur1]] += right - cur2 + 1; 为什么要用+=,而不能使用+
  要知道归并的过程,元素位置是变化的。 使用 += 是因为我们需要累计 nums[cur1] 右侧小于它的元素数量。在归并排序的合并过程中,cur1 会遍历左子数组的所有元素,每次遇到一个比右子数组当前元素大的左子数组元素时,我们都需要将右子数组中剩余的元素数量(即 right - cur2 + 1)累加到对应的 count 中。因此,这是一个累加操作,每次都需要将新的数量加到之前累计的数量上。

  如果使用 + 而不是 +=,那么每次只会将 right - cur2 + 1 赋值给 count[index[cur1]],而不会保留之前累计的数量。这会导致 count 数组中每个位置只存储了最后一次计算得到的数量,而不是所有累计的数量,从而得到错误的结果。

在这里插入图片描述
  
  
  
  
  
  
  
  
  

9、翻转对(hard)

  题源:链接

在这里插入图片描述

  
  

9.1、题解

  1)、思路分析
  翻转对和逆序对的定义大同小异,逆序对是前面的数要大于后面的数。而翻转对是前面的一个数要大于后面某个数的两倍。因此,我们依旧可以用归并排序的思想来解决这个问题。
  
  而与上个问题不同的是,上一道题我们可以一边合并一遍计算小于当前元素右侧元素,但是这道题要求的是左边元素大于右边元素的两倍,若直接合并的话,是无法快速计算出翻转对的数量的。
  因此,在归并排序之前,我们可以借助左右区间已经排序好的单调性,先完成对翻转对的统计。由于两个指针都是不回退的的扫描到数组的结尾,因此两个有序序列求出翻转对的时间复杂度是 O ( N ) O(N) O(N)

在这里插入图片描述

  
  2)、题解
  以升序为例:

class Solution {
public:
    int tmp[50005] = {0};

    int mergesort(vector<int>& nums, int left, int right) {
        if (left >= right)
            return 0;

        //[left,mid] [mid+1, right]
        int mid = (left + right) / 2;
        int sum = 0;
        sum += mergesort(nums, left, mid) + mergesort(nums, mid + 1, right);

        // 统计:(单调性为升序)
        int cur1 = left, cur2 = mid + 1;
        while(cur1 <= mid && cur2 <= right)
        {

            if(nums[cur1]/2.0 > nums[cur2])
            {
                sum += mid - cur1 + 1;
                cur2++;
            }
            else cur1++;
        }

        // while (cur2 <= right) // 统计部分其它写法:升序的情况
        // {
        //     while (cur1 <= mid && nums[cur2] >= nums[cur1] / 2.0)
        //         cur1++;
        //     if (cur1 > mid)
        //         break;
        //     sum += mid - cur1 + 1;
        //     cur2++;
        // }

        // 排序:升序版,[left, mid][mid+1, right]
        cur1 = left, cur2 = mid + 1;
        int i = left;
        while (cur1 <= mid && cur2 <= right) {
            if (nums[cur1] > nums[cur2])
                tmp[i++] = nums[cur2++];
            else
                tmp[i++] = nums[cur1++];
        }

        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];
        }

        return sum;
    }

    int reversePairs(vector<int>& nums) {
        return mergesort(nums, 0, nums.size()-1);
    }
};

  
  
  
  
  以降序为例:

class Solution {
public:
    int tmp[50005] = {0};

    int mergesort(vector<int>& nums, int left, int right) {
        if (left >= right)
            return 0;

        //[left,mid] [mid+1, right]
        int mid = (left + right) / 2;
        int sum = 0;
        sum += mergesort(nums, left, mid) + mergesort(nums, mid + 1, right);

        // 统计:(单调性为降序)
        int cur1 = left, cur2 = mid + 1;
        while (cur1 <= mid && cur2 <= right) {

            if (nums[cur1] / 2.0 > nums[cur2]) {
                sum += right - cur2 + 1;
                cur1++;
            } else
                cur2++;
        }

        // while (cur1 <= mid) // 统计部分其它写法:降序的情况
        // {
        //     while (cur2 <= right && nums[cur2] >= nums[cur1] / 2.0)
        //         cur2++;
        //     if (cur2 > right)
        //         break;
        //     ret += right - cur2 + 1;
        //     cur1++;
        // }

        // 排序:降序版,[left, mid][mid+1, right]
        cur1 = left, cur2 = mid + 1;
        int i = left;
        while (cur1 <= mid && cur2 <= right) {
            tmp[i++] = nums[cur1] > nums[cur2] ? nums[cur1++] : 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];
        }

        return sum;
    }

    int reversePairs(vector<int>& nums) {
        return mergesort(nums, 0, nums.size() - 1);
    }
};

  
  
  
  
  
  
  
  
  
  
  
  
  

Fin、共勉。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值