算法专题七: 分治归并

发布于:2024-10-17 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. 排序数组

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

算法思路: 本道题使用归并的思路进行排序, 先讲数组分为左右两个区间, 然后合并两个有序数组.

class Solution {
    vector<int> tmp;
public:
    vector<int> sortArray(vector<int>& nums) {
        tmp.reserve(nums.size());
        mergesort(nums,0,nums.size()-1);
        return nums;
    }
    void mergesort(vector<int>& nums,int left,int right)
    {
        if(left >= right) return;
        int mid = (right + left)>>1;
        mergesort(nums,left,mid);
        mergesort(nums,mid+1,right);
        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++];
        for(int i = left; i <= right;i++)
        {
            nums[i] = tmp[i - left];
        }
    }
};

2. 交易逆序对的总数

在这里插入图片描述

算法思路:

在这里插入图片描述

先找左半部分的逆序对, 然后再找右半部分的逆序对, 最后再一左一右的找逆序对, 找完左半部分和右半部分之后给数组排个序对数组的结果不影响, 而且排完序之后找一左一右的逆序对还简单些, 不由得我们就可以想到此方法和归并排序的思路一样, 只不过再排序的过程中增加了统计逆序对的个数. 这里可以采用两种策略, 第一种固定当前位置的值, 然后找前面有多少数是比我大的, 第二种策略, 固定当前位置的值, 找后面有多少元素是比我小的.

  • 首先使用第一种策略, 固定一个数, 在前面找比我大的元素的个数.

在这里插入图片描述
当前为升序的排序, 我们归并的时候如果nums[cur1] <= nums[cur2] 时, 此时我们只需要把cur1++就行, 如果nums[cur1]在某一个位置大于nums[cur2]时,此时已经找到了比nums[cur2]大的元素, 那么mid- cur1 + 1这段区间里面的所有值都比nums[cur2]大, 此时统计逆序对的数量即可, 然后再让cur2++, 但是不要往了我们还要排序, 所以这一步写在归并数组那里即可.
那么既然升序可以, 降序可不可以呢?

在这里插入图片描述
如图所示, 该数组为降序的时候, 固定cur1, cur2位置时,如果cur1大于cur2则开始统计cur1中大于cur2元素的个数, 看似没有什么问题, 但是如果进入下一次循环中, cur1++之后还有可能大于cur2,此时在统计那不是重复了嘛, 显然结果是错误的

  • 第二种策略: 固定一个数, 然后找后面比我小的元素的个数

在这里插入图片描述
首先先来看降序的数组, 来统计固定一个位置之后, 后面有多少元素比我小, 当nums[cur1] <= nums[cur2]时, 此时不是我们想要的结果, 我们需要的是nums[cur1]>nums[cur2], 这个时候, 我们统计后面有多少个元素比我小即可, 很显然此时数组是降序的, 所以right - cur2 + 1 即为结果

if(nums[cur1] <= nums[cur2])
tmp[i++] = nums[cur2++];
else
{
ret += right - cur2 + 1;
tmp[i++] = nums[cur1++];
}

反之我们来看一下升序

在这里插入图片描述

此时数组为升序, 我们需要找后面比前面小的元素的个数, 当固定到cur1之后, nums[cur1] > nums[cur2]时, 此时开始找结果, 在后面看在mid到cur2之间的这部分都是比nums[cur1]小的, 但是下一次cur2++之后还有可能是小于nums[cur1]的, 所以此时重复统计了 , 结果是错误的.

代码部分:

策略1

class Solution {
    int tmp[50000];
public:
    int reversePairs(vector<int>& record) {
        return mergeSort(record,0,record.size()-1);
    }
    int mergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return 0;
        int ret = 0;
        int mid = (right + left) >> 1;
        ret += mergeSort(nums,left,mid);
        ret += mergeSort(nums,mid+1,right);
        //程序到这里左边与右边已经处理完毕, 现在处理左右
        int cur1 = left, cur2 = mid + 1, i = 0;
        //升序 找比当前位置大的个数
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
                tmp[i++] = nums[cur1++];
            else
            {
                ret += 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 ret;
    }
};

策略2

class Solution {
    int tmp[50000];
public:
    int reversePairs(vector<int>& record) {
        return mergeSort(record,0,record.size()-1);
    }
    int mergeSort(vector<int>& nums,int left,int right)
    {
        if(left>=right) return 0;
        int ret = 0;
        int mid = (right + left) >> 1;
        ret += mergeSort(nums,left,mid);
        ret += mergeSort(nums,mid+1,right);
        //程序到这里左边与右边已经处理完毕, 现在处理左右
        int cur1 = left, cur2 = mid + 1, i = 0;
        //降序 找比当前位置小的个数
        while(cur1 <= mid && cur2 <= right)
        {
            if(nums[cur1] <= nums[cur2])
                tmp[i++] = nums[cur2++];
            else
            {
                ret += right - cur2 + 1;
                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 ret;
    }
};

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

在这里插入图片描述

算法思路:

本道题更上一道题的思路是一模一样的, 上一道题我们采用了策略一的方法, 本道题我们采用策略二, 找后面比当前位置元素小的元素的个数, 但是与上一道题不同的是本道题要求返回对应位置的结果, 这是个问题, 如何解决呢, 我们可以采用哈希的思想, 如果使用哈希表那么如果遇到重复的元素, 我们就无法进行解决, 但是我们可以采用这种思想, 创建一个新的数组, 这个数组中存放的是对应位置原始的下标, 然后这个数组跟着nums一起变化, 但是无论怎样变换, 里面的下标还是最原始的下标位置.

在这里插入图片描述

编写代码:

class Solution {
    vector<int> index;
    vector<int> ret;
    int tmpNums[500010];
    int tmpIndxt[500010];
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        ret.resize(n);
        index.resize(n);
        for(int i = 0;i < n;i++)
            index[i] = i;
        mergeSort(nums,0,n-1);
        return ret;
    }
    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])
            {
                tmpNums[i] = nums[cur2];
                tmpIndxt[i++] = index[cur2++];
            }
            else
            {
                ret[index[cur1]] += right - cur2 + 1;
                tmpNums[i] = nums[cur1];
                tmpIndxt[i++] = index[cur1++];
            }
        }
        while(cur1 <= mid)
        {
            tmpNums[i] = nums[cur1];
            tmpIndxt[i++] = index[cur1++];
        }
        while(cur2 <= right)
        {
            tmpNums[i] = nums[cur2];
            tmpIndxt[i++] = index[cur2++];
        }
        for(int i = left; i <= right; i++)
        {
            nums[i] = tmpNums[i - left];
            index[i] = tmpIndxt[i - left];
        }
    }
};

4. 翻转对

在这里插入图片描述

算法思路:

首先我们还是先考虑两个策略. 策略一, 计算后面有多少个元素的两倍比我小, 策略二. 计算前面有多少元素的一半比我大. 不同于前两题, 本道题无法在归并的时候进行计算, 因为这里的比较逻辑和归并的比较逻辑是不同的, 所以这里我在归并逻辑之前采用双指针的方法先进行统计, 然后在进行归并排序.

在这里插入图片描述

在这里插入图片描述

那么首先来看策略一, 在处理完左右之后并排序之后, 我们处理一左一右的情况, 使用双指针, 计算后面有多少元素的两倍比我小, 如果nums[cur1] <= nums[cur2] * 2, 则说明后面元素的两倍比我大, 那么继续移动cur2, 知道找到cur2的位置两倍比我小, 然后进行统计, 此时cur1++, 进行下一次的统计, 这里cur2还需要回退到mid+1的位置嘛?
答案是不需要的, 因为当前位置cur2位置前面元素的两倍都是比我大的, 那cur1++之后降序, 那么一定也是比我大的, 所以直接统计就可以了, 所以整体的时间复杂度为O(NlogN).

class Solution {
    int tmp[50000];
public:
    int reversePairs(vector<int>& nums) {
        return mergeSort(nums, 0 , nums.size() - 1);
    }
    int mergeSort(vector<int>&nums, int left , int right)
    {
        if(left >= right) return 0;
        int ret = 0;
        int mid = (left + right) >> 1;
        ret += mergeSort(nums,left,mid);
        ret += mergeSort(nums,mid+1,right);
        int cur1 = left, cur2 = mid + 1, i = left;
        while(cur1 <= mid)
        {
            while(cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) cur2++;
            if(cur2 > right) break;//小优化
            ret += right - cur2 + 1;
            cur1++;
        }
        //降序
        cur1 = left, cur2 = mid + 1;
        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 ret;
    }
};

策略二: 找前面有多少元素的一半比我大

在这里插入图片描述

其实更策略一的逻辑是类似的, 只不过这里的数组是升序排列的, 找前面有多少元素的一半比我大, 先让cur1走, 如果/2之后大于nums[cur2]则, cur1++, 找到位置之后统计ret, 然后让cur2++,进行下一次的统计, 此时cur1也不需要回去, cur1之前的位置一半都是比cur2小的, 那么cur2++之后一定还是比cur1小, 所以cur2直接++即可.

class Solution {
    int tmp[50000];
public:
    int reversePairs(vector<int>& nums) {
        return mergeSort(nums, 0, nums.size()-1);
    }
    int mergeSort(vector<int>& nums,int left, int right)
    {
        if(left >= right) return 0;
        int mid = (left + right) >> 1;
        int ret = 0;
        ret += mergeSort(nums,left,mid);
        ret += mergeSort(nums,mid+1,right);
        int cur1 = left, cur2 = mid + 1, i = left;
        //升序, 先统计逆序对
        while(cur2 <= right)
        {
            while(cur1 <= mid && nums[cur1] / 2.0 <= nums[cur2]) cur1++;
            if(cur1 > mid) break;
            ret += mid - cur1 + 1; 
            cur2++;
        } 
        //合并有序数组
        cur1 = left, cur2 = mid + 1;
        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 ret;
    }
};