【分治算法 7】计算右侧⼩于当前元素的个数(hard)(每日一题)

 🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇

                                      ⭐分治⭐

🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇🎇


前言

分治算法(Divide and Conquer),顾名思义,将一个大问题分成多个小问题,然后分别解决这些小问题,最后将它们的解合并起来得到整体的解。这个算法的核心思想是将问题分割成更小的、相同结构的子问题,然后将子问题的解合并成原问题的解。

分治算法的运行流程通常包括三个步骤:分解、解决和合并。

  1. 分解:将原问题分解成若干个规模较小的子问题。这个步骤通常通过递归实现,直到子问题足够简单可以直接求解。

  2. 解决:递归地求解子问题。这可以通过相同的方法再次应用分治算法,继续将子问题分解成更小的子问题,直到达到基本情况可以直接求解。

  3. 合并:将子问题的解合并成原问题的解。当所有子问题都解决之后,将它们的解合并起来,得到原问题的解。

分治算法通常适用于满足两个条件的问题:

  1. 可以将原问题分解成相同结构的子问题。
  2. 子问题的解可以通过合并得到原问题的解。

分治算法在计算机科学中应用广泛,例如在排序算法中具有重要地位。著名的排序算法,如归并排序和快速排序,都是基于分治算法的思想。

归并排序通过分治的方式将一个无序数组分割成更小的子数组,然后递归地对子数组进行排序,最后将排好序的子数组合并成一个有序的数组。

快速排序也是通过分治的方式将一个无序数组分割成两个子数组,然后递归地对子数组进行排序,最后合并得到完整的有序数组。

总而言之,分治算法是一种非常有用的算法设计技巧,能够有效地解决一些复杂问题。它将问题分解成较小的子问题,通过递归解决子问题,并最终将它们的解合并成整体解,从而实现高效的问题求解。 


2.计算右侧⼩于当前元素的个数(hard)

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

算法思路:(利⽤归并排序的过程---分治)

这⼀道题的解法与求数组中的逆序对的解法是类似的,但是这⼀道题要求的不是求总的个数,⽽ 是要返回⼀个数组,记录每⼀个元素的右边有多少个元素⽐⾃⼰⼩。 但是在我们归并排序的过程中,元素的下标是会跟着变化的,因此我们需要⼀个辅助数组,来将数 组元素和对应的下标绑定在⼀起归并,也就是再归并元素的时候,顺势将下标也转移到对应的位置 上。 由于我们要快速统计出某⼀个元素后⾯有多少个⽐它⼩的,因此我们可以利⽤求逆序对的第⼆种⽅ 法。

算法流程:

• 创建两个全局的数组:

vector index:记录下标 vector

ret:记录结果

index ⽤来与原数组中对应位置的元素绑定,ret⽤来记录每个位置统计出来的逆序对的个数。

• countSmaller()主函数:

a. 计算nums数组的⼤⼩为n;

b. 初始化定义的两个全局的数组;

    i. 为两个数组开辟⼤⼩为n的空间

    ii. index 初始化为数组下标;

    iii. ret 初始化为0 c. 调⽤mergeSort()函数,并且返回ret结果数组。

• voidmergeSort(vector&nums,intleft,intright )函数:

函数设计:

通过修改全局的数组ret,统计出每⼀个位置对应的逆序对的数量,并且排序;

⽆需返回值,因为直接对全局变量修改,当函数结束的时候,全局变量已经被修改成最后的结果。

• mergeSort()函数流程:

a. 定义递归出⼝:left>=right时,直接返回;

b. 划分区间:根据中点mid,将区间划分为[left,mid]和[mid+1,right];

c. 统计左右两个区间逆序对的数量:

    i. 统计左边区间[left,mid]中每个元素对应的逆序对的数量到ret数组中,并排序;

    ii. 统计右边区间[mid+1,right]中每个元素对应的逆序对的数量到ret数组中,并排序。

d. 合并左右两个有序区间,并且统计出逆序对的数量:

    i. 创建两个⼤⼩为right-left+1⼤⼩的辅助数组:

       • numsTmp:排序⽤的辅助数组; 

       • indexTmp:处理下标⽤的辅助数组。

    ii. 初始化遍历数组的指针:cur1=left(遍历左半部分数组)cur2=mid+1(遍历右半边数 组)dest=0(遍历辅助数组)curRet(记录合并时产⽣的逆序对的数量);

    iii. 循环合并区间: 

       • 当nums[cur1]nums[cur2]时,⽆需统计,直接归并,注意index也要跟着归并。

    iv. 处理归并排序中剩余的元素;

       • 当左边有剩余的时候,还需要统计逆序对的数量;

       • 当右边还有剩余的时候,⽆需统计,直接归并。

    v. 将辅助数组的内容替换到原数组中去;

 代码实现:


class Solution {
    // tmp 是放当前数组排序后的数组
   static int[] tmp;
   static int[] tmp2;
    //返回集合
   static int[] ret;

   //这里的代码我在idea上边调试了,直接赋值过来了,所以带了个 main 和都带了 static
    public static void main(String[] args) {
        System.out.println(countSmaller(new int[]{2,5,6,1}).toString());
    }
    public static List<Integer> countSmaller(int[] nums) {
        //这道题和之前逆序对的题很相似,这里是在当前这个数的后边有多少个数比它小
        //使用逆序对的降序
        // 归并 + 跟随下标数组
        int n = nums.length;
        // index 是用来装 nums 数组中排序后数的下标
        int[] index = new int[n];
        for(int i=0;i<n;i++){
            index[i] = i;
        }
        tmp = new int[n];
        tmp2 = new int[n];
        ret = new int[n];
        List<Integer> l = new ArrayList<>();
        merge(nums,index,0,n-1);
        for(int x : ret){
            l.add(x);
        }
        return l;
    }
    public static void merge(int[] nums,int[] index,int left,int right){
        // 如果只有一个就直接返回
        if(left>=right){
            return;
        }
        //先计算左右
        // [left,mid]  [mid+1,right]
        int mid = (left+right)/2;
        merge(nums,index,left,mid);
        merge(nums,index,mid+1,right);

        //在一左一右
        int cur1 = left,cur2 = mid+1,i=0,j=0;
        while(cur1<=mid&&cur2<=right){
            if(nums[cur1]<=nums[cur2]){
                tmp[i++] = nums[cur2];
                tmp2[j++] = index[cur2++];
            }else{
                tmp[i++] = nums[cur1];
                tmp2[j++] = index[cur1];
                //重点:因为这里的对指定位置加数所以必须要先放数组里
                //     如果这里放 List 里边会报访问越界
                ret[index[cur1]] += right-cur2+1;
                cur1++;
            }
        }
        //把剩下的加进tmp
        while(cur1<=mid){
            tmp[i++] = nums[cur1];
            tmp2[j++] = index[cur1++];
        }
        while(cur2<=right){
            tmp[i++] = nums[cur2];
            tmp2[j++] = index[cur2++];
        }

        //把 tmp 中排好的子数组放回原数组
        for(int x = left;x<=right;x++){
            nums[x] = tmp[x-left];
            index[x] = tmp2[x-left];
        }
    }
}


总结

这道题和之前那道题挺类似的,不过这里要注意返回值的问题.

点个赞👍,会让作者开心很久的,感谢阅览。

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值