【LeetCode热题100】分治-归并

这篇博客记录了分治-归并的几道题目,包括排序数组、逆序对、计算右侧小于当前元素的个数、翻转对这几道题目。

//归并排序
class Solution 
{
    //创建一个全局变量,这样可以提高效率
    vector<int> tmp;
public:
    void _sortArray(vector<int>& nums,int left,int right)
    {
        if(left >= right) return;
        //1.选择中间点划分区间
        int mid = left + (right - left)/2;
        //[left,mid] [mid+1,right]
        //2.把左右区间排序
        _sortArray(nums,left,mid);
        _sortArray(nums,mid+1,right);
        //3.合并两个有序数组
        int p1 = left,p2 = mid+1,i=0;
        while(p1 <= mid && p2 <= right)
            tmp[i++] = nums[p1]<=nums[p2]?nums[p1++]:nums[p2++];
        //4.处理没有遍历的数组
        while(p1 <= mid) tmp[i++] = nums[p1++];
        while(p2 <= right) tmp[i++] = nums[p2++];
        //5.还原
        for(int i = left ; i <= right ; i++)
        {
            nums[i] = tmp[i-left];
        }
    }
    vector<int> sortArray(vector<int>& nums) 
    {
        tmp.resize(nums.size());
        _sortArray(nums,0,nums.size()-1);
        return nums;
    }
    
};

题目分析:我们可以归并排序,归并排序的思想就是先找一个中间值,然后把其左边区间也归并排序,然后其右边区间也归并排序,然后再把两个排序好的左右两个有序数组合并,合并两个有序数组的思路是双指针,分别指向左右区间的起始,另创建一个空数组,比较这两个指针指向的元素大小,小的放进这个空数组,直到遍历完。

所以,归并排序类似二叉树的后序遍历,而快速排序类似二叉树的前序遍历

class Solution {
    int v[50001];
public:
    int MergeSort(vector<int>& nums,int left,int right)
    {
        if(left >= right) return 0;
        //1.找中间点,将数组分成两部分
        //int mid = left+(right-left)/2;
        int mid = (left + right) >> 1;
        //[left,mid] [mid+1,right] 升序
        int ret = 0;
        //2.左边的个数+排序 右边的个数+排序
        ret += MergeSort(nums,left,mid);
        ret += MergeSort(nums,mid+1,right);
        int cur1 = left,cur2 = mid+1,i=0;
        //3.一左一右的个数
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] > nums[cur2]) 
            {   
                ret += mid - cur1 + 1;
                v[i++] = nums[cur2++];
            }
            else
            {
                v[i++] = nums[cur1++];
            }
        }
        // while(cur1 <= mid && cur2 <= right) v[i++] = nums[cur1] < nums[cur2] ? nums[cur1] : nums[cur2];
        while(cur1 <= mid) v[i++] = nums[cur1++];
        while(cur2 <= right) v[i++] = nums[cur2++];

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

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

题目分析:我们可以使用归并排序解决这道题,将数组分为两部分,左半部分->排序->右半部分->排序->一左一右,其中我们在一左一右中找逆序对时,只需要固定右半部分一个值,然后去左半部分找有多少大于这个值的元素,这种方法要求归并排序是升序

也可以固定左半部分一个值,然后去右半部分找有多少小于这个值的元素,这种方法要求归并排序是降序

假设是升序,在将数组分成一左一右后,[left,mid][mid+1,right],cur1和cur2分别遍历这两部分,nums[cur1]和nums[cur2]的大小关系有两种情况,

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

2)nums[cur1] > nums[cur2],ret += mid - cur1+1,cur2++

class Solution {
    vector<int> ret;
    vector<int> nums_tmp;
    int index[500010];
    int index_tmp[500010];

public:
    void MergeSort(vector<int>& nums,int left,int right)
    {
        if(left >= right) return;
        int mid = (left + right) >> 1;
        MergeSort(nums,left,mid);
        MergeSort(nums,mid+1,right);
        int cur1 = left,cur2 = mid+1,i = 0;
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] > nums[cur2])
            {
                ret[index[cur1]] += (right - cur2 + 1);
                index_tmp[i] = index[cur1];
                nums_tmp[i++] = nums[cur1++];
            }
            else
            {
                index_tmp[i] = index[cur2];
                nums_tmp[i++] = nums[cur2++];
            }
        }
        while(cur1 <= mid)
        {
            index_tmp[i] = index[cur1];
            nums_tmp[i++] = nums[cur1++];
        }
        while(cur2 <= right)
        {
            index_tmp[i] = index[cur2];
            nums_tmp[i++]= nums[cur2++];
        }
        for(int i = left; i <= right ; i++)
        {
            nums[i] = nums_tmp[i-left];
            index[i] = index_tmp[i-left];
        }
    }
    vector<int> countSmaller(vector<int>& nums) 
    {
        ret.resize(nums.size());
        nums_tmp.resize(nums.size());
        for(int i = 0; i < nums.size() ; i++)
        {
            index[i]=i;
        }
        MergeSort(nums,0,nums.size()-1);

        return ret;
    }
};

题目分析:我们要找出之后有多少元素比我小,依照上题思路,需要选择降序,也是采用分支排序的方法,

为了找到对应的下标,刚开始就要创建一个数组index,里面是各个元素下表,在排序过程中,index也要跟着nums元素位置变化而变化。

class Solution {
    //vector<int> tmp;
    int tmp[50010];
public:
    int _reversePairs(vector<int>& nums,int left,int right)
    {
        if(left >= right) return 0;
        int ret = 0;
        int mid = (left + right)>>1;
        ret += _reversePairs(nums,left,mid);
        ret += _reversePairs(nums,mid+1,right);
        int cur1 = left,cur2 = mid+1;
        //1.先找翻转对的数量
        while(cur1 <= mid && cur2 <= right)
        {
            if(0.5*nums[cur1] > nums[cur2])
            {
                ret += (right - cur2 + 1);
                cur1++;
            }
            else
            {
                cur2++;
            }
        }
        //2.合并两个有序数组
        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++];
        for(i = left; i <= right ; i++)
        {
            nums[i] = tmp[i-left];
        }
        return ret;
    }
    int reversePairs(vector<int>& nums) 
    {
        //tmp.resize(nums.size());
        return _reversePairs(nums,0,nums.size()-1);   
    }
};

题目分析:这道题我们依然用分治的思想。将一个数组分成两部分,先找左半部分的翻转对,再找右半部分的翻转对,最后再找一左一右的翻转对。

仍然有两个策略

1)计算当前元素后面,有多少元素的两倍比我少,要求数组降序。

if(nums[cur1] > 2*nums[cur2]) ret += right-cur2+1

2)计算当前元素之前,有多少元素的一半比我大,要求数组升序。

if(nums[cur1] > 2*nums[cur2]) ret += mid-left+1

猜你喜欢

转载自blog.csdn.net/qq_48460693/article/details/142929867