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

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

⭐分治⭐

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


前言

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

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

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

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

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

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

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

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

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

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

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


2. 翻转对(hard)

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

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

算法思路:

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

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

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

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

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

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

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

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

代码实现:

java 复制代码
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;
    }

}

总结

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

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

相关推荐
懒鸟一枚24 分钟前
深入理解 Linux 内存、Swap 交换分区与分页机制的关系
java·linux·数据库
Kx_Triumphs1 小时前
HDU4348 To the moon(主席树区间修改模板)
算法·题解
旖-旎1 小时前
《LeetCode647 回文子串 || LeetCode 5 最长回文子串》
c++·算法·leetcode·动态规划·哈希算法
我命由我123452 小时前
执行 Gradle 指令报错,无法将“grep”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
考虑考虑2 小时前
Sentinel安装
java·后端·微服务
轻颂呀2 小时前
约瑟夫环问题
算法
凤凰院凶涛QAQ4 小时前
《Java版数据结构 & 集合类剖析》栈与队列:“push/pop 是栈的灵魂,offer/poll 是队列的骨架——四组 API,两种人生”
java·开发语言·数据结构
科技大视界4 小时前
投资AI项目,传统尽调不够用了——李章虎律师拆解算法、数据、算力三大雷区
人工智能·算法·数据挖掘
郝学胜-神的一滴4 小时前
算法实战:最小k个数——大顶堆的优雅解法
开发语言·数据结构·c++·python·程序人生·算法·排序算法