分治(快排、归并)算法

分治算法分为快排和归并

快排

1、颜色分类

75. 颜色分类 - 力扣(LeetCode)

算法原理

将数组分为如下四个部分

[0,left]        0
[left+1,i-1]    1
[i,right-1]     未扫描元素
[right,size-1]  2 

当nums[i]==0交换swap(nums[++left],nums[i++])

当nums[i]==1时 i++

当nums[i]==2时swap(nums[--right],nums[i]); 注意这里不要i++

代码

class Solution {
public:
    void sortColors(vector<int>& nums) {
        int i=0;
        int left=-1,right=nums.size();
        while(i<right)
        {
            if(nums[i]==0)
            {
                swap(nums[++left],nums[i++]);
            }
            else if(nums[i]==1)
            {
                i++;
            }
            else//nums[i]==2
            {
                //这里i不++,因为交换之后i位置上的是没有扫描过的元素
                //[i,right-1]之间是没有扫过的元素
                swap(nums[--right],nums[i]);
            }
        }
    }
};

2、排序数组

912. 排序数组 - 力扣(LeetCode)

算法原理

方法一:快排

方法二:归并排序

代码

class Solution {
public:

    // void QuickSort(vector<int>& nums,int l,int r)
    // {
    //     if(l>=r)
    //         return;

    //     int ra=rand();
    //     int left=l-1,right=r+1;
    //     //生成一个随机数来作为key
    //     int key=nums[ra%(r-l+1)+l];
    //     int i=l;
    //     while(i<right)
    //     {
    //         if(nums[i]<key)
    //         {
    //             swap(nums[++left],nums[i++]);
    //         }
    //         else if(nums[i]==key)
    //         {
    //             i++;
    //         }
    //         else
    //         {
    //             swap(nums[--right],nums[i]);
    //         }
    //     }
    //     QuickSort(nums,l,left);
    //     QuickSort(nums,right,r);
    // }
    vector<int> tmp;

    void MergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right)
            return;
        //1、找中间节点
        //这中间节点不一定是整个数组的中间节点,所以不要使用(right-left)/2
        int mid=(right+left) /2;
        //2、分发
        MergeSort(nums, left, mid);
        MergeSort(nums, mid + 1, right);
        //3、合并数组
        int cur1=left;
        int cur2=mid+1;
        int i=0;
        while(cur1<=mid&&cur2<=right)
        {
            tmp[i++]=nums[cur1]<nums[cur2]?nums[cur1++]:nums[cur2++];
        }
        //4、处理没有的排序的数组
        while(cur1<=mid)
        {
            tmp[i++]=nums[cur1++];
        }
        while(cur2<=right)
        {
            tmp[i++]=nums[cur2++];
        }

        //5、还原
        for(int i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }
    }
    
    vector<int> sortArray(vector<int>& nums) 
    {
    //     快排
    //     srand(time(NULL));
    //     int n=nums.size();
    //     int left=0,right=n-1;
    //     QuickSort(nums,left,right);
    //     return nums;

        //归并排序
        tmp.resize(nums.size());
        MergeSort(nums,0,nums.size()-1);
        return nums;
    }

};

3、数组中的第K个最大元素

215. 数组中的第K个最大元素 - 力扣(LeetCode)

算法原理:

方法一:优先级队列

优先级队列内部维护了一个堆排序,默认为大根堆,所以只需要把所有元素插入,然后把数据pop最后得到的结果就是第k大。

方法二:快排

1、将数组分为三个部分,数据个数分别为a,b,c

[0,left]       <key         a

[left+1,right-1]  ==key       b

[right,r]      >key        c

这里就可以分为三个情况

1) c>=k 直接在第三段区间找第k大

2) b+c>=k 说明在第二段区间,而第二段区间所有值都相等,直接返回key

3)如果1和2都不成立,那么在第一段区间找第k-b-c大

代码

//方法一
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> q;
        for(auto e: nums)
        {
            q.push(e);
        }

        for(int i=0;i<k-1;i++)
        {
            q.pop();
        }

        return q.top();
    }
};

//方法二
class Solution {
public:
    int QucikSort(vector<int>& nums,int l,int r,int k)
    {

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


        int b=right-left-1;
        int c=r-right+1;

        if(c>=k)//到第三段区间去找第k大
        {
            return QucikSort(nums,right,r,k);
        }
        else if(b+c>=k)
        {
            return key;
        }
        else
        {
            return QucikSort(nums,l,left,k-b-c);
        }

    }

    int findKthLargest(vector<int>& nums, int k) {
        srand(time(NULL));
        return QucikSort(nums,0,nums.size()-1,k);
    }
};

4、库存管理 III(最小的k个数)

LCR 159. 库存管理 III - 力扣(LeetCode)

算法原理

与上一道题类似,也可以使用优先级队列,建一个小根堆

这里也是分为三个情况,找最小的k个元素

1)k<a 在第一个区域找最小的k个元素

2)k<=a+b 直接返回,当前位置前面k个就是最小的元素

3)如果不是上面两种情况,就在第三个区域去找k-a-b个最小元素

本质就是快排,之所以在里面进行判断,这样把前k小个排出来就行,不需要关注这前k小的排序

代码

class Solution {
public:
    void QuickSort(vector<int>& nums,int l,int r,int k)
    {
        int ra=rand();
        int left=l-1,right=r+1;
        //生成一个随机数来作为key
        int key=nums[ra%(r-l+1)+l];
        int i=l;
        while(i<right)
        {
            if(nums[i]<key)
            {
                swap(nums[++left],nums[i++]);
            }
            else if(nums[i]==key)
            {
                i++;
            }
            else
            {
                swap(nums[--right],nums[i]);
            }
        }
        //这里将数组分为三个区间
        //个数分别为a,b,c
        int b=right-left-1;
        int a=left-l+1;
        if(k<a)
        {
            QuickSort(nums,l,left,k);
        }
        else if(k<=a+b)//直接返回
        {
            return;
        }
        else//在c区间找k-a-b个
        {
            QuickSort(nums,right,r,k-a-b);
        }

    }
    vector<int> inventoryManagement(vector<int>& stock, int cnt) 
    {
        QuickSort(stock,0,stock.size()-1,cnt);
        return {stock.begin(),stock.begin()+cnt};    
    }
};

归并排序

5、交易逆序对的总数

LCR 170. 交易逆序对的总数 - 力扣(LeetCode)

算法原理:

采用归并排序的思想

1、从中间将数组分为两个部分,先找左边有多少个逆序对,排序。

2、然后找右边的逆序对,排序

3、找左边和右边可以组成多少个逆序对

1)nums[cur1]>nums[cur2],说明cur1到mid之间的数都比cur2大,ret+=mid-cur1+1,cur2++

2)nums[cur1]<=nums[cur2],cur1++

3)当cur1==mid或者cur2==right,进行排序

注意:这里排序需要排升序,这样左边只要第一个比右边的大,那么左边所有就比右边大

代码

class Solution {
public:
    int tmp[50010];
    int MergeSort(vector<int>& nums,int left,int right)
    {
        int ret=0;

        if(left>=right)
            return 0;

        //1、找中间节点
        //这中间节点不一定是整个数组的中间节点,所以不要使用(right-left)/2
        int mid=(right+left) /2;
        //2、找到左边部分有多少个逆序对,然后排序
        ret+=MergeSort(nums, left, mid);
        //找到右边部分有多少个逆序对,然后排序
        ret+=MergeSort(nums, mid + 1, right);
        //3、一左一右
        int cur1=left;
        int cur2=mid+1;
        int i=0;
        while(cur1<=mid&&cur2<=right)
        {
            if(nums[cur1]>nums[cur2])
            {
                ret+=mid-cur1+1;
                tmp[i++]=nums[cur2++];
            }
            else
            {
                tmp[i++]=nums[cur1++];
            }

        }
        //4、处理没有的排序的数组
        while(cur1<=mid)
        {
            tmp[i++]=nums[cur1++];
        }
        while(cur2<=right)
        {
            tmp[i++]=nums[cur2++];
        }

        //5、还原
        //把排序后的数组插入原数组
        //nums下标从left开始,tmp从0开始
        for(int i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }

        return ret;
    }

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

6、计算右侧小于当前元素的个数

315. 计算右侧小于当前元素的个数 - 力扣(LeetCode)

算法原理:

与第五题类似,上一道题找的是比右侧大的数,所以排升序

这道题降序排序,当左边的数比右边的第一个数大,说明左边比右边所有数都要大

注意:

1、需要记录原来的下标,使用index数组中的值来记录nums下标,当修改数据,index数组同步修改

2、ret数组,来记录返回值

代码

class Solution {
public:
    vector<int> ret;
    vector<int> index;//在数组元素移动过程中,绑定数组原始下标
    int tmp[500010];//存放临时元素
    int tmp_index[500010];
    void MergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right)
            return;

        //1、找中间节点
        //这中间节点不一定是整个数组的中间节点,所以不要使用(right-left)/2
        int mid=(right+left) /2;
        //2、找到左边部分有多少个逆序对,然后排序
        MergeSort(nums, left, mid);
        //找到右边部分有多少个逆序对,然后排序
        MergeSort(nums, mid + 1, right);
        //3、一左一右
        int cur1=left;
        int cur2=mid+1;
        int i=0;
        while(cur1<=mid&&cur2<=right)
        {
            //使用index[cur1]来表示返回元素对应位置
            if(nums[cur1]>nums[cur2])
            {
                ret[index[cur1]]+=right-cur2+1;
                tmp_index[i]=index[cur1];//记录交换的位置
                tmp[i++]=nums[cur1++];
            }
            else
            {
                tmp_index[i]=index[cur2];
                tmp[i++]=nums[cur2++];
            }
        }
        //4、处理没有的排序的数组
        while(cur1<=mid)
        {
            tmp_index[i]=index[cur1];//第二次之后下标已经改变了,需要使用index[i]
            tmp[i++]=nums[cur1++];
        }
        while(cur2<=right)
        {
            tmp_index[i]=index[cur2];
            tmp[i++]=nums[cur2++];
        }

        //5、还原
        for(int i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
            index[i]=tmp_index[i-left];
        }
    }

    vector<int> countSmaller(vector<int>& nums) 
    {
        int n=nums.size();
        index.resize(n);
        for(int i=0;i<nums.size();i++)
        {
            index[i]=i;
        }
        ret.resize(n);
        if(nums.size()<=1)
            return {0};
        MergeSort(nums,0,n-1);
        return ret;
    }

};

7、 翻转对

493. 翻转对 - 力扣(LeetCode)

算法原理:

与上面题相同,直接排降序,然后进行判断

注意:

但是这里不可以像上面第六题一样,直接判断如果左边比右边第一个大,就比右边所有大,因为这里是比二倍大,排序是直接比大小

代码

class Solution {
public:
    int tmp[50010];
    int MergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right)
            return 0;
        int ret=0;
        //1、找中间节点
        //这中间节点不一定是整个数组的中间节点,所以不要使用(right-left)/2
        int mid=(right+left) /2;
        ret+=MergeSort(nums, left, mid);
        ret+=MergeSort(nums, mid + 1, right);
        int cur1=left;
        int cur2=mid+1;
        //降序
        //这里先对两部分先进行翻转对判断
        //因为这里需要计算,而排序是直接比较两个位置值的大小

        while(cur1<=mid)
        {
            //2147483647 * 2 cannot be represented in type 'int'
            //不要使用*
            while(cur2<=right && nums[cur2]>=nums[cur1]/2.0)
            {
                cur2++;
            }
            if(cur2>right)
                break;
            
            cur1++;
            ret+=right-cur2+1;
        }
        //4、合并数组
        cur1=left;
        cur2=mid+1;
        int 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++];
        }

        //5、还原
        for(int i=left;i<=right;i++)
        {
            nums[i]=tmp[i-left];
        }

        return ret;
    }

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值