首页 > 其他分享 >归并分治

归并分治

时间:2023-12-14 21:44:06浏览次数:23  
标签:归并 int 分治 个数 区间 翻转

归并排序是计算机之父"冯·诺依曼"发明的,但这其中隐藏了一种归并分治的思想。这种思想在一些题目中会帮助我们解决很多问题。

原理

  1. 对于一个大问题的答案,如果等于左边子问题的答案 + 右边子问题的答案 + 跨越左右问题的答案。
  2. 在计算跨越左右问题的答案时,左右两边是有序的是否会对我们计算答案产生便利。
  3. 如果以上两点都成立,那么很可能是用归并分治的思想来解决问题。

一些用归并分治解决的问题大部分也可以用线段树和树状数组来解决。时间复杂度也是最优的。

翻转对

力扣题目链接

解题思路

  1. 我们先分析求一个区间的翻转对的个数,是不是等于求左边区间翻转对的个数加右边区间翻转对的个数,加跨越左右区间翻转对的个数
  2. 很明显,一个区间的翻转对的个数等于左边区间翻转对的个数加右边区间翻转对的个数,加跨越左右区间翻转对的个数。
  3. 用分治法求子数组的最大累加和一样,最大累加和就是左边连续子数组的最大累加和,和右边连续子数组的最大累加和,但这还没有包括全部情况,也就是还有跨越区间的累加和。
  4. 对于这道题,因为i < j,所以翻转对的个数不就是左边区间翻转对的个数和跨越左右的区间的翻转对的个数吗,那么左边就考虑完了,就剩右边了,因为 i < j,那么左边的就肯定不满足翻转对。所以就只要算右边区间翻转对的个数。
  5. 然后再思考左右俩边区间有序对我们计算翻转对的个数会不会带来方便。

代码实现

//归并分治
int help[50000];//辅助数组
int merge(int* a, int l, int m, int r) {
    int ans = 0;
    int len = 0;

    for (int i = l, j = m + 1; i <= m ; i++) {//统计翻转对的个数,j的含义是a[j] * 2 >= a[i]的第一个位置
        while(j <= r && (long long)a[i] > (long long)a[j] * 2) {//如果a[j] * 2 >= a[i]或者越界了,就跳出不然就找下一个 
            j++;//跳下一个
        }
        ans += j - (m + 1);//右边的起始位置是m + 1,而j是a[j] * 2 >= a[i]的位置,因此它前面数都是翻转对,因为是有序的
    }

    //正常合并两个有序表
    int i = l;
    int j = m + 1;

    while(i <= m && j <= r) {
        if (a[i] <= a[j]) {
            help[len++] = a[i++];
        }
        else {
            help[len++] = a[j++];
        }
    }

    while(i <= m) {
        help[len++] = a[i++];
    }

    while(j <= r) {
        help[len++] = a[j++];
    }

    for (int k = 0; k < len; k++) {
        a[l + k] = help[k];
    }

    return ans;
}
//求[l, r]区间上的翻转对,并让[l, r]位置有序
int f(int* a, int l, int r) {
    if (l >= r) {
        return 0;
    }

    int mid = (l + r) / 2;

    return f(a, l, mid) + f(a, mid + 1, r) + merge(a, l, mid, r);//左子问题的答案加上右子问题的答案加上跨越左右问题的答案
}
int reversePairs(int* nums, int numsSize) {
    return f(nums, 0, numsSize - 1);
}

总结

归并分治也是一种分治法,所以分治法是一种重要的思想,归并分治能够使用题目大部分都符合前面两个特征,那么就可以用归并分治。

标签:归并,int,分治,个数,区间,翻转
From: https://www.cnblogs.com/lwj1239/p/17902078.html

相关文章

  • 【合并排序链表】分治/优先队列
    合并两个排序链表模拟维护一个合并链表,每次添加两个排序链表中较小val的节点即可模拟代码publicListNodemergeTwo(ListNodea,ListNodeb){if(a==null)returnb;if(b==null)returna;ListNodeans=newListNode(0);Lis......
  • 241. 为运算表达式设计优先级(分治 +记忆化)
    Problem:241.为运算表达式设计优先级给你一个由数字和运算符组成的字符串expression,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以按任意顺序返回答案。生成的测试用例满足其对应输出值符合32位整数范围,不同结果的数量不超过示例1:输入:expression=......
  • [持续更新][数据结构][算法]涵盖线性表、栈、链表、队列、图、动态规划、分治递归、回
    备考考点整理内部排序表格树的主要考点二叉树的常考紧紧抓住\(n_0=n_2+1\)\(n=n_0+n_1+n_2...n_m\)\(n=n_1+2*n_2+3*n_3...m*n_m\)+1哈夫曼树没有度为1的结点,也就是\(n_1=0\)完全二叉树常考总结最大岛屿问题(dfs模板)#include<iostream>#include<algorith......
  • 树分治全家桶
    树分治全家桶树,(是种在路边的绿化植物)是图论当中的一种特殊图,(由于绿化环境作用非常优秀)由于特殊性质丰富,经常出现在我们身边。本文将主要讲述(如何植树)一种树上优美的暴力——树分治。树分治树分治可以将部分\(O(n^2)\)的暴力降至\(O(n\logn)\)级别,尤其适用于树上距离及其......
  • 链表为什么适合归并排序而不是快速排序?
    链表适合归并排序而不是快速排序的原因主要有以下几点:内存访问模式:快速排序的效率主要来源于引用的局部性,计算机硬件在这里得到了优化,因此访问彼此相邻的内存位置往往比访问分散在内存中的内存位置更快。然而,链表单元格经常分散在内存中,所以访问相邻的链表单元格没有局部性......
  • 【刷题记录】20231124 线段树分治
    做题记录:注意到每次相当于\(0\)后面加\(1\),\(1\)后面加\(0\),因此每次可以合并01和10然后将问题规模减半。黑白染色,白格子=lcm+1,黑格子=prime相乘。发现横着竖着有六个质数,斜着只用四个质数。调整一下顺序即可。状压DP。考虑S作为前缀max时S与U-S的排列方案数。S每......
  • 点分治
    点分治是一种在树上进行的分治,可以方便的求解树上路径等问题。例题:P3806【模板】点分治1给定一棵树,询问树上是否存在长度为k的路径。现在我们假设x为根节点,那么一条路径长度为k有两种情况,一种是经过x,一种不经过x,第一种的两个端点在两个不同子树中,第二种的两个端点在同一......
  • 快速排序与归并排序模版
    快速排序voidquick_sort(intq[],intl,intr){if(l>=r)return;inti=l-1,j=r+1,x=q[l+(r-l>>1)];while(i<j){doi++;while(q[i]<x);doj--;while(q[j]>x);if(i&l......
  • 归并排序知识总结
    归并排序思维导图:知识点:如果原序列中两个数的值是相同的,它们在排完序后,它们的位置不发生变化,那么这个排序是稳定的。快速排序是不稳定的,归并排序是稳定的。快排变成稳定的=>使快排排序数组中的每个数都不同,将ai变成<ai,i>这个二元组,将ai的下标也放进来,使用双关键字排序。快速......
  • 分治与归并
    归并算法:递归+合并,在递归的途中进行分治。递归会把区间越分越小,此时就可以进行分治操作。可以使用全局变量进行分治操作。可以在函数中进行分治操作,在递归树中实现pushup和pushdown,记性父节点与子节点的关系计算。  classSolution{public:structNode{......