优先算法---分治-归并 题目算法分析 代码实现

第一道就是归并排序 提了些需要的注意地方   后三道是分治归并的题目练习

一.排序数组(归并)

归并排序和快排一样都在之前排序算法中学习过了   快排是种前序遍历 而归并是种后序遍历

归并主要的思路是递归不断把数组二分 直到只有一个元素 也就是left==right的情况 此时结束了不断递的过程 从两个数组每组一个数的合并开始 然后一层层归回去

二分mid选取  

①mid=(left+right)>>1  其实就是(left+right)/2 不过位运算效率高

 数据量大的话 可以用left+(right-left)>>2 防止溢出 

这种选取方式和②(left+right+1)>>2的区别在于  对于偶数个数的情况  ①选取的是中间偏左的②选取的是中间偏右的   不同选取方式在左右区域递归的左右范围选取上有区别 这里都用①的方式

两数组合并

需要一个额外的数组vv来接受当前层合并的结果 然后把vv更新后的内容拷贝回原来的num

定义两个变量cur1指向第一个数组的第一个位置 cur2指向第二个数组的第一个位置 每次从两个位置找小的 最终结果是正序结果 每次从两个位置找大的 最终结果是逆序结果

在 while (cur1 <= mid && cur2 <= right)结束后 还会有一个数组还没有处理完 对于没有处理完的数组直接把里面的内容放到vv中 

稳定性

 if (nums[cur1] <= nums[cur2])   这里带=可以保证稳定性 即相同的元素在排序前后相对的前后顺序不会改变

额外数组vv

一种方式是把它放到局部    一种是放到全局   

放到局部的方式每一层递归都需要重定义且用resize给他提前放相应个需要的元素(right-left+1)  而全局的方式只需要创建一次   全局的方式效率会提升很多

class Solution {
public:
vector<int> vv;
void gbsort(vector<int>& nums, int left, int right)
{
    if (left >= right) return;

    int mid = (left + right) >> 1;
    //[left,mid] [mid+1,right]
    gbsort(nums, left, mid);
    gbsort(nums, mid + 1, right);

    //已经排序好的两个数组合并
    int cur1 = left, cur2 = mid + 1;
    int n = 0;
    while (cur1 <= mid && cur2 <= right)
    {
        if (nums[cur1] <= nums[cur2])
            vv[n++] = nums[cur1++];
        else
            vv[n++] = nums[cur2++];
    }
    while (cur1 <= mid)  vv[n++] = nums[cur1++];
    while (cur2 <= right)  vv[n++] = nums[cur2++];
    for (int i = left; i <= right; ++i)
        nums[i] = vv[i- left];
}
vector<int> sortArray(vector<int>& nums) {
    vv.resize(nums.size());
    gbsort(nums, 0, nums.size() - 1);
    return nums;
}
};

二.交易逆序对的总数

算法分析

 暴力求解的方式

第一层for循环遍历从第一个元素到倒数第二个元素(i=0;i<size-1;++i)

第二次for循环遍历从当前位置到最后一个位置(int j=i+1;j<size,++j)

if(nums[j]<nums[i]  con++     时间复杂度为O(N^2) 会超时

归并排序

1.从小到大排序

在递归到一组只有一个数据时候深度结束 此时在往回 归回去 此时从一组有一个元素开始需要合并 在结束之后这两组合成的新的一个有序数组作为下一层的其中一组  

所以 在任何一次的合并中 这两个数组一定是已经排好序的数组即从小到大排序   此时根据cur1和cur2位置的值进行统计 只需统计当前层左边每一个数  看其在右边的数组中有没有符合要求的情况

因为最终一定是一层一层递归回去的 (从两两合并开始)  所有如果到了这一层说明当前的两个数组已经排好了 只需要考虑当前数组和另一个数组的比较  或者通过递归理解

2.从大到小排序

和第一种是类似的   但是也有区别  这里在cur1++结束之后是一下把此时这个cur1位置符合要求的都统计了 而第一种方式并不是在cur1结束后统计它符合要求的情况 而是在cur2++ cur2结束后统计其对左边数组符合要求数做的贡献(这道题目无所谓 对第三题有区别 )

另外这两种方式其实也可以换个角度考虑 从右边的数组考虑  对于右边的每一个元素 在左边数组里面找比它小的元素  

代码实现

1.从小到大

class Solution {
public:
 int con=0; //统计
 vector<int>vv;
 void gbsort(vector<int>& nums,int left,int right)
 { 
    if(left==right) return ;

    int mid=(left+right)>>1;
    //[left,mid] [mid+1,right]
    gbsort(nums,left,mid);
    gbsort(nums,mid+1,right);
    //合并
    int cur1=left,cur2=mid+1;
    int n=0;
    while(cur1<=mid&&cur2<=right)
    {
       if(nums[cur1]<=nums[cur2]) vv[n++]=nums[cur1++];   //带=可以保持稳定性
       else 
       {
          con+=mid-cur1+1;
          vv[n++]=nums[cur2++]; 
       } 
    }
    while(cur1<=mid) vv[n++]=nums[cur1++];
    while(cur2<=right) vv[n++]=nums[cur2++];
    for(int i=left;i<=right;++i)
    {
        nums[i]=vv[i-left];
    }
 }
    int reversePairs(vector<int>& record) {
        if(record.size()==0) return 0;
        vv.resize(record.size());
        gbsort(record,0,record.size()-1);
      return con;
    }
};

2.从大到小

class Solution {
public:
 int con=0; //统计
 vector<int>vv;
 void gbsort(vector<int>& nums,int left,int right)
 { 
    if(left==right) return ;

    int mid=(left+right)>>1;
    //[left,mid] [mid+1,right]
    gbsort(nums,left,mid);
    gbsort(nums,mid+1,right);
    //合并
    int cur1=left,cur2=mid+1;
    int n=0;
    while(cur1<=mid&&cur2<=right)
    {
       if(nums[cur1]>nums[cur2])
       {
        con+=right-cur2+1;
       vv[n++]=nums[cur1++];      
       } 
       else 
       {
          vv[n++]=nums[cur2++]; 
       } 
    }
    while(cur1<=mid) vv[n++]=nums[cur1++];
    while(cur2<=right) vv[n++]=nums[cur2++];
    for(int i=left;i<=right;++i)
    {
        nums[i]=vv[i-left];
    }
 }
    int reversePairs(vector<int>& record) {
        if(record.size()==0) return 0;
        vv.resize(record.size());
        gbsort(record,0,record.size()-1);
      return con;
    }
};

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

算法分析

同样的关键还是在两个数组合并的这里  和上一题类似

上一题是对每一个元素在右边找比其小的元素的个数 然后把所有和加起来  所以上一题直接用con来统计就好  而这一题是分别求  

所以这里需要用一个数组v2来统计每一个元素所符合要求的情况   而在统计的时候v2需要知道当前统计元素的下标 如下  v2和数组num的下标一一对应 那么统计的时候只需要根据下标就能知道需要统计是哪个元素 

但是这里有一个问题  在之前层的处理中可能元素的位置改变了!

之前是通过下标相同 一个位置来对应一个元素  这是通过每个元素的初始下标来对应的  但是如下 按照从大到小的顺序排序  在[5 2] [1 6]这层合并的时候 此时第二组[1 6]上一层的排序之后已经变成了[6  1] 

那么此时第一个位置和第二个位置还是统计num数组中下标相同对应的值所要求的 但是因为num数组中6和1交换了顺序 之前v2第三个位置对应1第四个位置对应6 现在第三个位置对应6 第四个位置对应1了   就不能正确统计了

所以我们需要一个额外的v3来存数组元素的初始下标 让v3和num数组同步交换位置 这样v3中始终存的是num中同等下标的元素对应的初始下标  v3[i]的意义是num数组中此时与i相同下标对应的初始下标

哈希表的方式可以吗?---v3用哈希表的方式 通过数组里面的值来映射出一个下标 进而统计  对于无重复的情况是可以的 但是如果是有重复是不行的

所以  我们要让v3来记录的和此时同等下标元素的初始下标 如下 此时数组里面的值通过v3只要知道当前位置的下标就能找到真正的初始下标  然后v2通过这个初始下标就能一一对应元素了

如下面的1此时初始下标就是v3中同等下标2里面的值2  在1和6交换顺序后 v3中2和3也同步交换 此时1的下标是3 v3中同等下标3对应的值仍是2也就是初始下标 这样v2统计的时候 只要知道当前位置的下标 就可以通过v3来找到初始下标

此时还需要一个v5类似v1的作用  v1是来统计当层合并后值的结果 v5是来统计当层同步交换后值当前下标对应的初始下标

1.从大到小方式--从左考虑

cur1位置元素>cur2位置元素  此时对于cur1位置元素右边数组从cur2到right都是符合要求的 通过v3由当前下标cur1来找到初始下标 然后通过cur2来统计 

这里统计找初始下标的时候 应该用v3不能用v5 v5是用来记录更新后的当前下标对应的初始下标

然后更新v1和v5 即合并后数组及记录着合并后的当前下标对应的初始下标 这里v1统计的是从0开始的 而v5是从left开始的  这里的不同影响着后面更新num及v3

cur1位置元素<=cur2位置元素 此时只需要更新v1及v5

2.从小到大的方式--从左考虑

cur1位置元素<=cur2位置元素 此时只需要更新v1及v5

cur1位置元素>cur2位置元素 此时cur1及其后面的都满足大于cur2位置元素都需要统计

  (1)如果用for循环的方式把cur1到mid位置都统计 最终会有样例超时

(2)用变量的方式来存对于cur1之后应该记录的   但是在什么地方来加需要考虑很多的地方




然后就是二题最后提到的可以转换思路  可以对于右边数组的每一个元素  从左边数组找比其大的

用从右考虑思路的从小到大的方式就和从左考虑的从大到小一样了  从右考虑从大到小的就和从左考虑从小到大遇到的问题一样了 这里转换思路的方式不写了

代码实现

1.从大到小--从左考虑

class Solution {
public:
vector<int> v1, v2, v3,v5;  //v1是中介  v2是返回的  v3来存下标 v5存一轮合并后当前位置所对应下标 类似v1功能
void gbsort(vector<int>& nums, int left, int right)
{
    if (left >= right) return;

    int mid = (left + right) >> 1;
    gbsort(nums, left, mid);
    gbsort(nums, mid + 1, right);
    //合并
    int cur1 = left, cur2 = mid + 1, n = 0;
    while (cur1 <= mid && cur2 <= right)
    {                     //在过程中需要更新v2的地方就是>的情况  而v2更新需要用到下标 这里使用到的下标是v3中存的旧下                         标 v5把当前层数组内改变后的下标存起来
        if (nums[cur1] > nums[cur2])
        {
            v2[v3[cur1]]+= right-cur2+1;
            v5[left+n]=v3[cur1];
            v1[n++] = nums[cur1++];
        }
        else
        {
            v5[n + left] = v3[cur2];
            v1[n++] = nums[cur2++];
        }
    }
    while (cur1 <= mid)
    {
        v5[n + left] = v3[cur1];
        v1[n++] = nums[cur1++];
    }
    while (cur2 <= right)
    {
        v5[n + left] = v3[cur2];
        v1[n++] = nums[cur2++];
    }
    for (int i = left; i <= right; ++i)
    {
         v3[i] = v5[i];
         nums[i] = v1[i - left];
    }
    
}
vector<int> countSmaller(vector<int>& nums) {
    int n = nums.size();
    v1.resize(n); v2.resize(n);
    for (int i = 0; i < n; ++i)
        v3.push_back(i);
     v5=v3;

  
    gbsort(nums, 0, n - 1);
    return v2;
}
};

2.从小到大 从左考虑--超时

class Solution {
public:
vector<int> v1, v2, v3,v5;  //v1是中介  v2是返回的  v3来存下标 v5存一轮合并后当前位置所对应下标 类似v1功能
void gbsort(vector<int>& nums, int left, int right)
{
    if (left == right) return;

    int mid = (left + right) >> 1;
    gbsort(nums, left, mid);
    gbsort(nums, mid + 1, right);
    //合并
    int cur1 = left, cur2 = mid + 1, n = 0;
    while (cur1 <= mid && cur2 <= right)
    {
        if (nums[cur1] <= nums[cur2])
        {
            v5[n + left] = v3[cur1];
            v1[n++] = nums[cur1++];
        }
        else
        {
          for (int i = cur1; i <= mid; ++i)
                v2[v3[i]]++;
            v5[n + left] = v3[cur2];
            v1[n++] = nums[cur2++];
        }
    }
    while (cur1 <= mid)
    {
        v5[n + left] = v3[cur1];
        v1[n++] = nums[cur1++];
    }
    while (cur2 <= right)
    {
        v5[n + left] = v3[cur2];
        v1[n++] = nums[cur2++];
    }
    for (int i = left; i <= right; ++i)
    {
          v3[i] = v5[i];
          nums[i] = v1[i - left];
    }

}
vector<int> countSmaller(vector<int>& nums) {
    int n = nums.size();
    v1.resize(n); v2.resize(n),v5.resize(n);
    for (int i = 0; i < n; ++i)
        v3.push_back(i);
    v5=v3;
  
    gbsort(nums, 0, n - 1);
    return v2;
}
};

二三题总结

如果是正序 且从右边数组考虑在左数组找小 或者是逆序从从左边数组考虑在右数组找大的话就会对要考虑的那一边的每一个元素给计算出符合要求的情况 

而另外的两种情况要做到对每一个元素计算出符合要求的情况很麻烦  例如对于第三题如果用逆序从左考虑或者正序从右考虑就很麻烦 

而第二题因为考虑的是总的个数  不用考虑每一个元素的情况 四种都可以做出来

例如下面第一个图就是正序 从右考虑的情况 可以统计出考虑一边(右边)每一个元素符合要求的情况 

图二正序 从左考虑 不能考虑出考虑的一边(左边)每一个元素符合要求的情况 而是在cur2++时候才考虑到了cur2这个位置到左边所做的贡献

另外的逆序从左考虑  类似于正序 从右边考虑的情况   逆序从右考虑 类似于正序从左考虑的情况

四.翻转对

算法分析

和第二题很类似 不过要求更加严格 后面的元素小于当前元素的二分之一才算一个翻转对

之前的二三题因为直接就是大小的比较可以和两数组合并的过程同步进行  这一题不是直接大小的比较 所有不能和两数组合并同时进行  应在两数组合并之前进行

把之前的内容理解了之后  这题唯一注意的点也就是不能和合并的过程同时进行

这一题因为不需要单独每个数考虑 所以对于四种方式都可以 如果对于每一个数要单独算的话就只有正序从右考虑 和逆序从左考虑好写了

代码实现

从小到大正序 从右考虑

class Solution {
public:
int con=0;
vector<int>v1;
void gbsort(vector<int>& nums,int left,int right)
{
    if(left>=right) return;
    int mid=(left+right)>>1;
    gbsort(nums,left,mid);
    gbsort(nums,mid+1,right);
   int cur1=left,cur2=mid+1;
   while(cur1<=mid&&cur2<=right)
   {
    if(nums[cur1]>(long long)nums[cur2]*2)
    {
        con+=mid-cur1+1;
        cur2++;
    }
    else
        cur1++;
   }

     cur1=left,cur2=mid+1;
     int n=0;
    while(cur1<=mid&&cur2<=right)
    {
      if(nums[cur1]<=nums[cur2]) v1[n++]=nums[cur1++];
      else   v1[n++]=nums[cur2++];
    }
    while(cur1<=mid) v1[n++]=nums[cur1++];
    while(cur2<=right) v1[n++]=nums[cur2++];
    for(int i=left;i<=right;++i)
         nums[i]=v1[i-left];
}
    int reversePairs(vector<int>& nums) {
         v1.resize(nums.size());
        gbsort(nums,0,nums.size()-1);
       return con;
    }
};

从大到小逆序 从左考虑

class Solution {
public:
int con=0;
vector<int>v1;
void gbsort(vector<int>& nums,int left,int right)
{
    if(left>=right) return;
    int mid=(left+right)>>1;
    gbsort(nums,left,mid);
    gbsort(nums,mid+1,right);
   int cur1=left,cur2=mid+1;
   while(cur1<=mid&&cur2<=right)
   {
    if(nums[cur1]>(long long)nums[cur2]*2)
    {
        con+=right-cur2+1;
        cur1++;
    }
    else
        cur2++;
   }

     cur1=left,cur2=mid+1;
     int n=0;
    while(cur1<=mid&&cur2<=right)
    {
      if(nums[cur1]>=nums[cur2]) v1[n++]=nums[cur1++];
      else   v1[n++]=nums[cur2++];
    }
    while(cur1<=mid) v1[n++]=nums[cur1++];
    while(cur2<=right) v1[n++]=nums[cur2++];
    for(int i=left;i<=right;++i)
         nums[i]=v1[i-left];
}
    int reversePairs(vector<int>& nums) {
         v1.resize(nums.size());
        gbsort(nums,0,nums.size()-1);
       return con;
    }
};

从小到大正序 从左考虑

class Solution {
public:
int con=0;
vector<int>v1;
void gbsort(vector<int>& nums,int left,int right)
{
    if(left>=right) return;
    int mid=(left+right)>>1;
    gbsort(nums,left,mid);
    gbsort(nums,mid+1,right);
   int cur1=left,cur2=mid+1;
   while(cur1<=mid&&cur2<=right)
   {
    if(nums[cur1]>(long long)nums[cur2]*2)
    {
        con+=mid-cur1+1;
        cur2++;
    }
    else
        cur1++;
   }

     cur1=left,cur2=mid+1;
     int n=0;
    while(cur1<=mid&&cur2<=right)
    {
      if(nums[cur1]<=nums[cur2]) v1[n++]=nums[cur1++];
      else   v1[n++]=nums[cur2++];
    }
    while(cur1<=mid) v1[n++]=nums[cur1++];
    while(cur2<=right) v1[n++]=nums[cur2++];
    for(int i=left;i<=right;++i)
         nums[i]=v1[i-left];
}
    int reversePairs(vector<int>& nums) {
         v1.resize(nums.size());
        gbsort(nums,0,nums.size()-1);
       return con;
    }
};

从大到小逆序 从右考虑

class Solution {
public:
int con=0;
vector<int>v1;
void gbsort(vector<int>& nums,int left,int right)
{
    if(left>=right) return;
    int mid=(left+right)>>1;
    gbsort(nums,left,mid);
    gbsort(nums,mid+1,right);
   int cur1=left,cur2=mid+1;
   while(cur1<=mid&&cur2<=right)
   {
    if(nums[cur1]>(long long)nums[cur2]*2)
    {
        con+=right-cur2+1;
        cur1++;
    }
    else
        cur2++;
   }

     cur1=left,cur2=mid+1;
     int n=0;
    while(cur1<=mid&&cur2<=right)
    {
      if(nums[cur1]>=nums[cur2]) v1[n++]=nums[cur1++];
      else   v1[n++]=nums[cur2++];
    }
    while(cur1<=mid) v1[n++]=nums[cur1++];
    while(cur2<=right) v1[n++]=nums[cur2++];
    for(int i=left;i<=right;++i)
         nums[i]=v1[i-left];
}
    int reversePairs(vector<int>& nums) {
         v1.resize(nums.size());
        gbsort(nums,0,nums.size()-1);
       return con;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值