【分治算法 7】翻转对(hard)(每日一题)

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

                                      ⭐分治⭐

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


前言

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

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

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

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

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

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

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

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

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

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

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


2. 翻转对(hard)

题目链接:493. 翻转对 - 力扣(LeetCode)

算法思路:解法(归并排序):

算法思路:

⼤思路与求逆序对的思路⼀样,就是利⽤归并排序的思想,将求整个数组的翻转对的数量,转换成 三部分:左半区间翻转对的数量,右半区间翻转对的数量,⼀左⼀右选择时翻转对的数量。

重点就 是在合并区间过程中,如何计算出翻转对的数量。 与上个问题不同的是,上⼀道题我们可以⼀边合并⼀遍计算,但是这道题要求的是左边元素⼤于右 边元素的两倍,如果我们直接合并的话,是⽆法快速计算出翻转对的数量的。

例如left=[4,5,6]right=[3,4,5]时,如果是归并排序的话,我们需要计算left数组中有多少个 能与3组成翻转对。但是我们要遍历到最后⼀个元素6才能确定,时间复杂度较⾼。 因此我们需要在归并排序之前完成翻转对的统计。

下⾯依旧以⼀个⽰例来模仿两个有序序列如何快速求出翻转对的过程: 假定已经有两个已经有序的序列left=[4,5,6]right=[1,2,3]。 ⽤两个指针cur1cur2遍历两个数组。

◦ 对于任意给定的left[cur1]⽽⾔,我们不断地向右移动cur2,直到left[cur1]<=2* right[cur2]。此时对于right数组⽽⾔,cur2之前的元素全部都可以与left[cur1]构成翻转 对。

◦ 随后,我们再将cur1向右移动⼀个单位,此时cur2指针并不需要回退(因为left数组是升序 的)依旧往右移动直到left[cur1]<=2*right[cur2]。不断重复这样的过程,就能够求出所有 左右端点分别位于两个⼦数组的翻转对数⽬。

由于两个指针最后都是不回退的的扫描到数组的结尾,因此两个有序序列求出翻转对的时间复杂度 是O(N)。

综上所述,我们可以利⽤归并排序的过程,将求⼀个数组的翻转对转换成求左数组的翻转对数量+ 右数组中翻转对的数量+左右数组合并时翻转对的数量。

 代码实现:

class Solution {
    private int[] tmp;
    public int reversePairs(int[] nums) {
      //这道题跟逆序对的题很相似,但又有出入
      //归并排序 + 分治
      int n = nums.length;
      tmp = new int[n];
      return merge(nums,0,n-1);
    }
    public int merge(int[] nums,int left,int right){
        if(left>=right) return 0;
        int ret = 0;
        //先找左边再找右边 最后一左一右
        int mid = (left+right)/2;
        ret+= merge(nums,left,mid);
        ret+= merge(nums,mid+1,right);

        //先找符合要求条件的值 注意:这里 i 为 0
        int cur1 = left, cur2 = mid+1,i=0;
        while(cur1<=mid&&cur2<=right){
            //这里可能给的数据非常大,乘2之后就越出int范围了,所以申请long来比较
            long key1 = nums[cur1],key2 = nums[cur2]*2L;
            if(key1<=key2){
               cur2++;
            }else{
                ret+=(right-cur2+1);
                cur1++;
            }
        }
        //排序 降序
        cur1 = left;cur2=mid+1;
         while(cur1<=mid&&cur2<=right){
            if(nums[cur1]<=nums[cur2]){
               tmp[i++] = nums[cur2++];
            }else{
                tmp[i++] = nums[cur1++];
            }
        }
        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-left];
        }
        return ret;
    }

}


总结

这里是 归并排序对分治算法的应用,这是最后一题了。这就是分治算法的一系列咯。还有点小难,但是相信我,这些题给他刷刷就会咯。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值