分治_归并_归并排序(逆序对)

912. 排序数组

上一次我们做这道题时用的是数组划分三块的思想 搭配随机选择基准元素的⽅法。

随机选择一个数,以这个数key为基准划分数组,小于key的数在左边,大于key的数在右边。再把被划分的两部份再找key值划分,直到只剩1或者0个元素返回。

遍历完最后一层该数组就排序完成
接下来我们采用归并的思想来做。

1.以中间点mid把数组分为两部份

2.一直划分直到 到最后一层只剩一个元素或者没有元素返回

3.**之下而上返回时,要对左右两部份数组进行排序。**借助一个临时数组 两个指针分别指向两个数组,由小到大排,先把小的放里面。再把临时数组排好的内容写回原数组。

4.以此类推,直到返回最上层,合并完左右两个有序数组,排序完成。

归并排序递归实现过程就像二叉树的后续遍历

class Solution {
    vector<int> tmp;
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.resize(nums.size());
        mergeSort(nums,0,nums.size()-1);
        return tmp;
    }
    void mergeSort(vector<int>&nums,int left,int right)
    {
        if(left>=right) return;
        //1.选择中间点划分区域
        int mid=(left+right)>>1;
        //2.把左右区间排序
        mergeSort(nums,left,mid);
        mergeSort(nums,mid+1,right);
        //3.合并两个有序数组
        int cur1=left,cur2=mid+1,i=0;
        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++];
        //将tmp中排好序的数字写回nums
        for(int i=left;i<=right;i++)
            nums[i]=tmp[i-left];
    }
};

1.借助临时数组时,最好把它定义成全局变量,提前申请好空间,提高效率。

2.合并左右两数组时,可能有一方数组提前结束,后面还需要把另一个数组的剩余部分元素继续合并。

3.int mid=(left+right)>>1表示/2。如果数据溢出可以mid=left+(right-left)/2

LCR 170. 交易逆序对的总数

逆序对,第一个数>第二个数 且第一个数在数组的位置要在第二个数前面。

方法:归并

1.从一个数组中找逆序对,我们把该数组分为两半,左边部分和右半部分。

那逆序对的个数=左半部分逆序对的个数+右半部分逆序对的个数+左边取一个数和右边取一个数组成的逆序对的个数。

2.如果我们再计算完左右部分内逆序对的个数后,**对左右部分内进行排序,一左一右的逆序对个数会改变吗?**不会,因为左右两个数的相对位置没有改变,左半部分数仍在右半部分数的前面。一左一右挑完逆序对后,排序也不影响。

所以 总数=左半部分+排序 +右半部分+排序 +一左一右+排序

3.利用归并。算一个数组的逆序对个数,先把它分为左右两部份。求左部分的个数,是不是也可以把它再分为左右两部分,直到分到只剩1 0个元素 逆序对个数就为0。这和归并排序的规律是不是很像呢?

所以在递归过程中我们就可以把左右两部分的个数算出来。想一想,如果第一次原数组,分为左右部分,左部分的个数=它的左部分+它的右部分+它的一左一右。因此,我们只要算出一左一右的个数,第一层左部分 右部分的个数在递归中就可以算出来。

4.怎么算一左一右的个数?

首先为什么要排序呢?因为,当左右部分内排完序 左右部分都是升序/降序,进行归并时 当我们固定cur2 只要算出在左部分有多少个大于cur2的数就可以了。

策略一:找出该数前,有多少个数比我大

先来看升序时归并的代码,我们先规定cur2不动,找左部分大于cur2的个数。cur1++直到cur1>cur2,个数就为mid-cur1+1 (cur1右边),最后cur2++ 继续找。

1.nums[cur1]<=nums[cur2] cur1++;

2.nums[cur1]>nums[cur2] re+=mid-cur1+1; cur2++;

但我们反过来,先固定cur1不动,让cur2找比cur1小的个数。cur2++直到cur2>cur1,个数为cur2-(mid+1)+1 (cur2左边) ,最后cur1++ cur2++继续找。但cur2向右移,等再找到时,cur2左边的个数就包含的上一次找到的个数,就会重复。所以 升序时要先固定cur2

        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur1++];
            else 
            {
                re+=mid-cur1+1;//cur1右边个数
                tmp[i++]=nums[cur2++];
            }
        }

策略二:当前元素的后面,有多少个元素比我小

降序也可以完成,不过我们先规定cur1不动,找右部分小于cur1的个数。cur2++直到cur2<cur1,个数就为right-cur2+1 (cur2右边),最后cur1++ 换下一个位置 继续找。

1.nums[cur1]<=nums[cur2] cur2++;

2.nums[cur1]>nums[cur2] re+=right-cur2+1; cur1++;

        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur2++];
            else 
            {
                re+=right-cur2+1;//cur2右边的个数
                tmp[i++]=nums[cur1++];
            }
        }
class Solution {
    vector<int> tmp;
public:
    int reversePairs(vector<int>& record) {
        tmp.resize(record.size());
        return Sort(record,0,record.size()-1);
    }
    int Sort(vector<int>&nums,int left,int right)
    {
        int re=0;
        if(left>=right) return 0;
        //1.找中间数,把数组分为两半
        int mid=(left+right)>>1;
        //2.左边的个数+排序 右边的个数+排序
        re+=Sort(nums,left,mid);
        re+=Sort(nums,mid+1,right);
        //3.一左一右的个数 (升序)
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]<=nums[cur2]) tmp[i++]=nums[cur1++];
            else 
            {
                re+=mid-cur1+1;
                tmp[i++]=nums[cur2++];
            }
        }
        while(cur1<=mid) tmp[i++]=nums[cur1++];
        while(cur2<=right) tmp[i++]=nums[cur2++];
        for(int i=left;i<=right;i++)
            nums[i]=tmp[i-left];
        return re;
    }
};

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

这道题也是算逆序对的个数,不过 上一道是算逆序对总数。这道是算在该下标下后面的数中可以组成逆序对的个数。

在排序中会把元素的下标改变,所以我们要有一个数组index记录元素在原数组的下标,并在排序过程中随着的元素同步改变 保证能找到该元素在原数组的下标。


我们以降序为基础,cur1固定 cur2++找cur2<cur1,right-cur2+1(cur2右边)的数就是可以和cur1组成逆序对的数,再根据index映射到cur1在原数组的下标。

class Solution {
    vector<int> tmp;
    vector<int> tmp2;
    vector<int> index;
    vector<int> re;
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n=nums.size();
        re.resize(n);
        tmp.resize(n);
        tmp2.resize(n);
        index.resize(n);
        for(int i=0;i<n;i++) index[i]=i;
        sort(nums,0,n-1);
        return re;
    }
    void sort(vector<int>&nums,int left,int right)
    {
        if(left>=right) return;
        int mid=(right-left)/2+left;
        sort(nums,left,mid);
        sort(nums,mid+1,right);
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]<=nums[cur2])
            {
                tmp2[i]=index[cur2];
                tmp[i++]=index[cur2++];
            }
            else 
            {
                re[index[cur1]]+=right-cur2+1;
                tmp2[i]=index[cur1];
                tmp[i++]=nums[cur1++];
            }
        }
        while(cur1<=mid)
        {
            tmp2[i]=index[cur1];
            tmp[i++]=nums[cur1++];
        }
        while(cur2<=right)
        {
            tmp2[i]=index[cur2];
            tmp[i++]=nums[cur2++];
        }
        for(int i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
            index[i]=tmp2[i-left];
        }
    }
};

1.数组降序排序

2.index数组建立元素和它在原数组的下标的映射

3.index数组中的对应元素要在nums元素改变位置时同步改变

4.re[index[cur1]]+=right-cur2+1;

index[cur1]因为是固定cur1不动,进行映射时要找cur1

+= 在向上递归时 不断在合并的右数组中找逆序对

493. 翻转对

这道和找逆序对总个数的题不同的是,[n,m]前面n较大的值要>后面n小的值的2倍。

原本是n>m就可以了,这会导致无法在排序时一起找出翻转对。

1.我们要在排序前,利用单调性先找出翻转对的个数

2.再进行排序并合并 进行递归
依旧是把数组以中间点mid分为左右两部份,cur1 cur2分别指向左右部分。

1.降序 cur1固定 cur2++ 找cur2*2<cur1的点 re+=rgiht-cur2+1(cur2右边)

2.升序 cur2固定 cur1++ 找cur1/2>cur2的点 re+=mid-cur1+1(cur1右边)


降序 注意nums[cur2]*2时会出现整数溢出,可以选择用中间变量long long存取后再比较。

或者判断条件nums[cur1]<=nums[cur2]*2两边都除2.0 -> nums[cur1]/2.0<=nums[cur2]

为什么除2.0 而不是2?因为除整型2会除不尽

class Solution {
    vector<int> tmp;
public:
    int reversePairs(vector<int>& nums) {
        int n=nums.size();
        tmp.resize(n);
        return sort(nums,0,nums.size()-1);
    }
    int sort(vector<int>&nums,int left,int right)
    {
        if(left>=right) return 0;
        int re=0;
        int mid=(left+right)>>1;
        re+=sort(nums,left,mid);
        re+=sort(nums,mid+1,right);
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            long long c2=nums[cur2];
            c2*=2;
            if(nums[cur1]<=c2)
            {
                cur2++;
            }
            else
            {
                re+=right-cur2+1;
                cur1++;
            }
        }        
        cur1=left,cur2=mid+1,i=0;
        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 i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }
        return re;
    }
};

升序 改变cur1 cur2顺序就可以

class Solution {
    vector<int> tmp;
public:
    int reversePairs(vector<int>& nums) {
        int n=nums.size();
        tmp.resize(n);
        return sort(nums,0,nums.size()-1);
    }
    int sort(vector<int>&nums,int left,int right)
    {
        if(left>=right) return 0;
        int re=0;
        int mid=(left+right)>>1;
        re+=sort(nums,left,mid);
        re+=sort(nums,mid+1,right);
        int cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]/2.0<=nums[cur2])
            {
                cur1++;
            }
            else
            {
                re+=mid-cur1+1;
                cur2++;
            }
        }        
        cur1=left,cur2=mid+1,i=0;
        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]<=nums[cur2])
            {
                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 i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }
        return re;
    }
};
相关推荐
机器学习之心11 分钟前
一区正弦余弦算法!SCA-SVM正弦余弦算法优化支持向量机多特征分类预测
算法·支持向量机·分类·sca-svm·正弦余弦算法优化
a栋栋栋44 分钟前
刷算法心得
算法
華華3551 小时前
读程序题...
开发语言·c++·算法
m0_547486661 小时前
西安交通大学2001年数据结构试题
数据结构
宸码2 小时前
【机器学习】手写数字识别的最优解:CNN+Softmax、Sigmoid与SVM的对比实战
人工智能·python·神经网络·算法·机器学习·支持向量机·cnn
太空漫步112 小时前
逆序对个数
java·算法·排序算法
CHENWENFEIc2 小时前
基础排序算法详解:冒泡排序、选择排序与插入排序
c语言·数据结构·算法·排序算法·学习方法·冒泡排序
养佳工具人2 小时前
Java各种排序
java·算法·排序算法
都适、隶仁ミ2 小时前
【密码学】SM4算法
linux·运维·服务器·算法·网络安全·密码学·网络攻击模型