【刷题12】分治—归并排序

目录

一、数组排序

题目:

思路:归并排序

代码:

cpp 复制代码
class Solution {
public:
    void msort(vector<int>& nums, int *tmp, int left, int right)
    {
        if(left >= right) return;
        int mid = (right+left)/2;
        msort(nums, tmp, left, mid);
        msort(nums, tmp, mid+1, right);
        int begin1 = left, end1 = mid;
        int begin2 = mid+1, end2 = right;
        int index = begin1;
        while(begin1 <= end1 && begin2 <= end2)
        {
            if(nums[begin1] < nums[begin2])
                tmp[index++] = nums[begin1++];
            else 
                tmp[index++] = nums[begin2++];
        }
        while(begin1 <= end1) tmp[index++] = nums[begin1++];
        while(begin2 <= end2) tmp[index++] = nums[begin2++];
        memcpy(&nums[0]+left, tmp+left, sizeof(int)*(end2-left+1));//begin1会变化,用left
    }
    vector<int> sortArray(vector<int>& nums) {
        int n = nums.size();
        int *tmp = new int[n]{0};
        msort(nums, tmp, 0, n-1);
        delete[] tmp;
        return nums;
    }
};

二、交易逆序对的总数

题目:

思路:在归并排序的基础上统计逆序对数量

  • 为什么两个子数组是有序的?因为上次合并就已经排好序了。是两个子数组各自里面的元素有序
  • nums[begin1] <= nums[begin2]:不确定比nums[begin2]大的位置出现在哪,可能还有部分也是小于nums[begin2]或者相等,所以没法直接统计出比nums[begin2]大的元素个数
  • nums[begin1] > nums[begin2]:说明该位置的元素比nums[begin2]大,可以组成逆序对,同时nums[begin1]后面的元素也都满足条件。
  • 为什么比较的是nums[begin2]?因为逆序对是前大后小,直接用后面的子数组中的nums[begin2]作比较可以减少重复计算。

代码:

cpp 复制代码
class Solution {
public:
    void msort(vector<int>& nums, int *tmp, int left, int right, int &ret)
    {
        if(left >= right) return;
        int mid = (left+right)/2;
        msort(nums, tmp, left, mid, ret);
        msort(nums, tmp, mid+1, right, ret);
        int begin1 = left, end1 = mid;
        int begin2 = mid+1, end2 = right;
        int index = begin1;
        while(begin1 <= end1 && begin2 <= end2)
        {
            if(nums[begin1] <= nums[begin2])
            {
                tmp[index++] = nums[begin1++];
            }
            else 
            {
                ret += end1 - begin1 + 1;
                tmp[index++] = nums[begin2++];
            }
        }
        while(begin1 <= end1) tmp[index++] = nums[begin1++];
        while(begin2 <= end2) tmp[index++] = nums[begin2++];
        memcpy(&nums[0]+left, tmp+left, sizeof(int)*(end2-left+1));
    }
    int reversePairs(vector<int>& record) {
        int n = record.size();
        int *tmp = new int[n];
        int ret = 0;
        msort(record, tmp, 0, n-1, ret);
        delete[] tmp;
        return ret;
    }
};

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

题目:

思路:

上一题是:找该位置元素前面有多少个元素比它大

本题:找该位置元素后面有多少个元素比它小,然后在该位置填个数,最后返回新的数组

采用降序找小的策略:

  • 因为是降序,所以谁大就谁先填入临时数组
  • nums[begin1] <= nums[begin2]:begin2到end2还有部分大于等于nums[begin1],所以nums[begin1]大于剩余的部分是不确定的,不能直接统计有多少个比nums[begin1]小
  • nums[begin1] > nums[begin2]:说明此时begin2到end2闭区间都是比nums[begin1]要小的,直接统计个数即可
  • 为什么以nums[begin1]作为比较?因为begin1这个点靠左,减少重复计算
  • begin1到end1也是降序,为什么不加上这里的?因为'降序'其实是非升序,即可能出现相同的元素,直白的说就是begin1到end1这部分的元素万一都是和nums[begin1]相同怎么办,所以不加上这里的,而一旦nums[begin1] > nums[begin2],那么begin2到end2的个数就是确定的

重点:hash对应下标加上个数

  • 原数组nums和临时数组(交换元素位置用的)tmpnums
  • 记录下标的数组index(对应原数组的下标)和临时数组tmpindex
  • 之前都是nums和tmp可以改变数组元素的位置(这里tmp对应tmpnums)
  • 下标的位置也要同步改变,index数组(每个元素是下标)和tmpindex
  • hash数组记录个数,作为返回对象。它的数组下标是从0开始,对应原数组的下标
  • 如果没有同步下标就导致某个位置的个数加错了
  • 同步了之后,就可以找到原来的下标,然后在hash的该位置加上个数

以上图为例:原数组的元素5是下标0,那么它的右侧小于它的元素个数,应该加在hash下标为0的位置上。当元素5的位置发生改变,如果下标没有同步,它的下标就变成了3,就导致最后加错。下标也同步移动后,begin1指向位置是nums的5,那么对应指向的位置index的0,这才是要hash要加上个数的下标位置。

代码:

cpp 复制代码
class Solution {
    vector<int> hash;// 个数--返回对象
    vector<int> index;// 记录下标 
    vector<int> tmpnums;// 移动nums的临时数组
    vector<int> tmpindex;// 移动index的临时数组
public:
    void msort(vector<int>& nums, int left, int right)
    {
        if(left >= right) return;
        int mid = (left+right)/2;
        msort(nums, left, mid);
        msort(nums, mid+1, right);
        int begin1 = left, end1 = mid;
        int begin2 = mid+1, end2 = right;
        int k = begin1;
        while(begin1 <= end1 && begin2 <= end2)
        {
            if(nums[begin1] <= nums[begin2]) 
            {
                tmpindex[k] = index[begin2];// 同步
                tmpnums[k++] = nums[begin2++];  
            }
            else 
            {
                int count = right-begin2+1;
                hash[index[begin1]] += count;// 找到对应下标位置再加count
                tmpindex[k] = index[begin1];// 同步
                tmpnums[k++] = nums[begin1++]; 
            }
        }
        while(begin1 <= end1) 
        {
            tmpindex[k] = index[begin1];// 同步
            tmpnums[k++] = nums[begin1++]; 
        }
        while(begin2 <= end2) 
        {
            tmpindex[k] = index[begin2];// 同步
            tmpnums[k++] = nums[begin2++]; 
        }
        memcpy(&nums[0]+left, &tmpnums[0]+left, sizeof(int)*(end2-left+1));// 同步
        memcpy(&index[0]+left, &tmpindex[0]+left, sizeof(int)*(end2-left+1));
    }
    vector<int> countSmaller(vector<int>& nums) {
        int n = nums.size();
        hash.resize(n); // 初始化
        index.resize(n);// 初始化
        tmpnums.resize(n);// 初始化
        tmpindex.resize(n);// 初始化
        for(int i=0; i<n; i++) index[i] = i;// 原数组下标先对应
        msort(nums, 0, n-1);
        return hash;
    }
};

四、翻转对

题目:

思路:在逆序对的基础上改变应该条件:计算的是前面的元素比后面的元素的2倍大的个数

计算翻转对与合并分开写

注意:用两层for循环不好控制,因为从begin1到end1和从begin2到end2只遍历一遍,也就是说那个完了(begin1>end1或begin2>end2)就结束了。在这过程中,满足题目条件时ret+=(...),然后再begin2++;但是如果用两层for循环ret+=后还是begin1会往后走(本来应该暂时停下的,让begin2++了),可是多个限制条件break,就跳到了外循环;假如内循环是正常走完的,不是break跳出来的,那么根据前面说的,begin1>end1结束,所以内循环结束的下面理所应当要有一个break。但是假如是因为内循环的break到这里来的(ret+=后,begin1还是小于end1的),即内循环还没结束,外循环也没有,就直接跳出了,会出现各种问题。

cpp 复制代码
for(int i=begin2;i<=end2;i++)
        {
            for(int j=begin1;j<=end1;j++)
            {
                if(nums[i] < nums[j]/2.0)
                {
                    ret += end1-j+1;
                    break;
                }
            }
            break;
        }

代码:

cpp 复制代码
class Solution {
public:
    void msort(vector<int>& nums, int *tmp, int left, int right, int &ret)
    {
        if(left >= right) return;
        int mid = (left+right)/2;
        msort(nums, tmp, left, mid, ret);
        msort(nums, tmp, mid+1, right, ret);
        int begin1 = left, end1 = mid;
        int begin2 = mid+1, end2 = right;
        int k = begin1;
        // 计算翻转对 - 两个子数组已经是上次合并后排好序的
        while(begin2 <= end2)
        {
            // nums[begin1] <= nums[begin2]*2 会超出范围
            while(begin1 <= end1 && nums[begin2] >= nums[begin1]/2.0)
                begin1++;
            if(begin1 > end1) break;
            ret += end1-begin1+1;
            begin2++;
        }
        // 合并
        begin1 = left, begin2 = mid+1;
        while(begin1 <= end1 && begin2 <= end2)
        {
            if(nums[begin1] <= nums[begin2])
            {
                tmp[k++] = nums[begin1++];
            }
            else 
            {
                tmp[k++] = nums[begin2++];
            }
        }
        while(begin1 <= end1)
            tmp[k++] = nums[begin1++];
        while(begin2 <= end2)
            tmp[k++] = nums[begin2++];
        memcpy(&nums[0]+left, tmp+left, sizeof(int)*(end2-left+1));
    }
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        int *tmp = new int[n];
        int ret = 0;
        msort(nums, tmp, 0, n-1, ret);
        return ret;
    }
};
相关推荐
低保和光头哪个先来3 分钟前
牛客算法简单题(JS版)
javascript·算法
王老师青少年编程3 分钟前
CSP/信奥赛C++刷题训练:经典前缀和例题(2):洛谷P6568:水壶
c++·算法·前缀和·csp·信奥赛
圣保罗的大教堂19 分钟前
leetcode 219. 存在重复元素 II
leetcode
尤蒂莱兹6 小时前
qt的c++环境配置和c++基础【正点原子】嵌入式Qt5 C++开发视频
java·c++·qt
wingのpeterPen7 小时前
mac 上使用 cmake 构建包含 OpenMP 的项目
c++·macos
凡人的AI工具箱8 小时前
15分钟学 Go 第 21 天:标准库使用
开发语言·后端·算法·golang·1024程序员节
得鹿梦鱼、8 小时前
Qt/C++ 调用迅雷开放下载引擎(ThunderOpenSDK)下载数据资源
c++·qt·thunderopensdk
zhousiyuan05158 小时前
手撕FFT
算法
不做笔记的程序员不是好的码农8 小时前
C++-类与对象总结
开发语言·javascript·c++
战术摸鱼大师9 小时前
线性代数&群论应用:正逆运动学 & 变换矩阵
线性代数·算法·矩阵