归并排序及常见面试题

news/2024/11/19 11:16:46/

请添加图片描述
⭐️前言⭐️

本篇文章主要介绍归并排序,以及与之相关的改写问题,将该类问题总结归纳,便于后续遇见类似题目时候的解答。

🍉欢迎点赞 👍 收藏留言评论 📝私信必回哟😁

🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言

🍉博客中涉及源码及博主日常练习代码均已上传GitHub


请添加图片描述

📍内容导读📍

  • 🍅1.归并排序的实现
  • 🍅2.数组小和
  • 🍅3.数组的降序对
  • 🍅4.翻转对
  • 🍅5.区间和的个数
  • 🍅6.总结归纳

🍅1.归并排序的实现

思想

其中心思想就是合并两个有序数组。先将序列拆解使得每个子序列有序,再二路归并使子序列段间有序,最后得到有序序列。

在这里插入图片描述
代码实现:

public class MergeSort {public static void mergeSort(int[] arr) {if (arr == null || arr.length < 2) {return;}process(arr,0,arr.length-1);}public static void process(int[] arr, int L, int R) {if (L == R) {return;}int mid = L + ((R - L) >> 1);process(arr, L, mid);process(arr, mid + 1, R);merge(arr, L, mid, R);}public static void merge(int[] arr, int L, int M, int R) {int[] help = new int[R - L + 1];int index = 0;int p1 = L;int p2 = M + 1;while (p1 <= M && p2 <= R) {help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];}//要么p1越界,要么p2越界,两个while只会进去一个while (p1 <= M) {help[index++] = arr[p1++];}while (p2 <= R) {help[index++] = arr[p2++];}for (int i = 0; i < help.length; i++) {arr[L + i] = help[i];}}
}

🍅2.数组小和

题目:

在一个数组中,一个数左边比它小的数的总和,叫该数的小和
所有数的小和累加起来,叫数组小和
例子: [1,3,4,2,5]
1左边比1小的数:没有
3左边比3小的数:1
4左边比4小的数:1、3
2左边比2小的数:1
5左边比5小的数:1、3、4、 2
所以数组的小和为1+1+3+1+1+3+4+2=16
给定一个数组arr,求数组小和

题解思路:

如果使用两层for循环的暴力方法,操作结果为等差数列次,所以时间复杂度为O(N^2),可以利用归并排序的方法来降低时间复杂度。

求数组小和,就是需要求出数组中每个数的左边比该数小的所有数的和,这些和加起来就是数组小和;

同样地,这个数组小和的求法可以转换为数组中每个数的右边,有几个数比该数大,有几个则数组小和中加N个该数。

然后就可以利用归并排序的方法,在按区域进行归并排序的时候,也就按区域求出了比该数大的数的个数,也就间接求出了数组小和。

代码实现:

public class SmallSum {public static int smallSum(int[] arr) {if(arr==null||arr.length<2) {return 0;}return process(arr,0,arr.length-1);}public static int process(int[] arr,int L,int R) {if(L==R) {return 0;}int M=L+((R-L)>>1);return process(arr,L,M)+process(arr,M+1,R)+merge(arr,L,M,R);}public static int merge(int[] arr,int L,int M,int R) {int[] help=new int[R-L+1];int index=0;int p1=L;int p2=M+1;int res=0;while (p1<=M&&p2<=R) {//当左组的数比右组的数小时,右组指针所指的数的右边的所有的数都比左组数大//当两者相等时,先拷贝右组的数,因为不知道右组有多少个数比左组当前数大res+=arr[p1]<arr[p2]?(R-p2+1)*arr[p1]:0;help[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];}while (p1<=M) {help[index++]=arr[p1++];}while (p2<=R) {help[index++]=arr[p2++];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}return res;}
}

🍅3.数组的降序对

题目:
在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为降序对
给定一个数组arr,求数组的降序对总数量

题解思路:

类似这种左边比右边的数大或者小的问题,都可以往归并排序上来想,在上道题9.3中求的是右边比该数大的数的个数,这道题求的就是右边比该数小的个数,可以通过使得归并排序时排逆序就可转化成和上一道题一样的解法了。

代码实现:

public class ReversePair {public static int reversePair(int[] arr) {if(arr==null||arr.length<2) {return 0;}return process(arr,0,arr.length-1);}public static int process(int[] arr,int L,int R) {if(L==R) {return 0;}int M=L+((R-L)>>1);return process(arr,L,M)+process(arr,M+1,R)+merge(arr,L,M,R);}public static int merge(int[] arr,int L,int M,int R) {int[] help=new int[R-L+1];int index=0;int p1=L;int p2=M+1;int res=0;while (p1<=M&&p2<=R) {res+=arr[p1]>arr[p2]?R-p2+1:0;help[index++]=arr[p1]>arr[p2]?arr[p1++]:arr[p2++];}while (p1<=M) {help[index++]=arr[p1++];}while (p2<=R) {help[index++]=arr[p2++];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}return res;}
}

🍅4.翻转对

题目:493. 翻转对 - 力扣(Leetcode)

在一个数组中,对于任何一个数num,求有多少个(后面的数*2)依然<num,返回总个数
比如:[3,1,7,0,2]
3的后面有:1,0
1的后面有:0
7的后面有:0,2
0的后面没有
2的后面没有
所以总共有5个

题解思路:

仍然是利用归并排序的方法,需要在归并之前,把符合要求的数统计下来,因为即使是在二路合并之前,左组中和右组中的数都是分别有序的,所以可以直接完成统计。

代码实现:

public class BiggerThanRightTwice {public int reversePairs(int[] arr) {if(arr==null||arr.length<2) {return 0;}return process(arr,0,arr.length-1);}public int process(int[] arr, int L, int R) {if(L==R) {return 0;}int M=L+((R-L)>>1);return process(arr,L,M)+process(arr,M+1,R)+merge(arr,L,M,R);}public int merge(int[] arr,int L,int M,int R) {int ans=0;int windowR=M+1;for (int i = L; i <=M ; i++) {//因为左右数组都是有序的,所以指针windowR不需要回退while (windowR<=R&&(long)arr[i]>(long)2*arr[windowR]) {windowR++;}ans+=windowR-M-1;}int[] help=new int[R-L+1];int index=0;int p1=L;int p2=M+1;while (p1<=M&&p2<=R) {help[index++]=arr[p1]<arr[p2]?arr[p1++]:arr[p2++];}while (p1<=M) {help[index++]=arr[p1++];}while (p2<=R) {help[index++]=arr[p2++];}for (int i = 0; i < help.length; i++) {arr[L+i]=help[i];}return ans;}
}

🍅5.区间和的个数

题目:
给定一个数组arr,两个整数lower和upper,
返回arr中有多少个子数组的累加和在[lower,upper]范围上

Leetcode题目:https://leetcode.cn/problems/count-of-range-sum/

题解思路:
1.i到j的子数组和位于范围内,可转换为两个前缀和数组的差位于范围内,所以可以先将原数组转换为前缀和数组,再通过前缀和数组差求解,以降低时间复杂度。
2.假设i位置的前缀和为X,求必须以i位置结尾的子数组,有多少个在[Lower,Upper]范围内,等同于去求i之前的所有前缀和数组中,有多少个前缀和在[X-Upper,X-Lower]上。
3.利用归并排序的有序分组,分别针对右组的每个数,来左组查看多少个数符合要求,并且因为数组的有序性,可以保证指针不回退。

代码实现:

class Solution {public int countRangeSum(int[] nums, int lower, int upper) {if(nums==null||nums.length==0) {return 0;}long[] sum=new long[nums.length];sum[0]=nums[0];for(int i=1;i<sum.length;i++) {sum[i]=sum[i-1]+nums[i];}return process(sum,0,sum.length-1,lower,upper);}public int process(long[] sum,int L,int R,int lower,int upper) {if(L==R) {return sum[L]>=lower&&sum[L]<=upper?1:0;}int M=L+((R-L)>>1);return process(sum,L,M,lower,upper)+process(sum,M+1,R,lower,upper)+merge(sum,L,M,R,lower,upper);}public int merge(long[] sum,int L,int M,int R,int lower,int upper) {int ans=0;int windowL=L;int windowR=L;// [windowL,windowR)for(int i=M+1;i<=R;i++) {long min=sum[i]-upper;long max=sum[i]-lower;while(windowR<=M&&sum[windowR]<=max) {windowR++;}while(windowL<=M&&sum[windowL]<min) {windowL++;}ans+=windowR-windowL;}long[] help=new long[R-L+1];int index=0;int p1=L;int p2=M+1;while(p1<=M&&p2<=R) {help[index++]=sum[p1]<=sum[p2]?sum[p1++]:sum[p2++];}while(p1<=M) {help[index++]=sum[p1++];}while(p2<=R) {help[index++]=sum[p2++];}for(int i=0;i<help.length;i++) {sum[L+i]=help[i];}return ans;}
}

🍅6.总结归纳

后边的几道题都是归并排序的改写,其本质都是把比较信息变成了有序的东西,这个有序的东西可以帮助我们很快的求很多事。

当再遇见某些数的左组与右组相比符合某些条件,或者右组与左组相比符合某些条件时,可以把问题往归并排序上靠拢,并且借助归并排序的组内数据有序性,可以实现指针的不回退。


⭐️最后的话⭐️
总结不易,希望uu们不要吝啬你们的👍哟(^U^)ノ~YO!!如有问题,欢迎评论区批评指正😁

请添加图片描述


http://www.ppmy.cn/news/39820.html

相关文章

3.10——常类型

常类型的引入&#xff0c;就是为了既保证数据共享又防止数据被改动。常类型是指使用类型修饰符const说明的类型&#xff0c;常类型的变量或对象成员的值在程序运行期间是不可改动的。 常引用 如果在说明引用时用const修饰&#xff0c;则被说明的引用为常引用。如果用常引用作为…

【一起啃书】《机器学习》第三章 线性模型

第三章 线性模型 3.1 基本形式 给定由ddd个属性描述的示例x(x1;x2;...;xd){\bf{x}} ({x_1};{x_2};...;{x_d})x(x1​;x2​;...;xd​)&#xff0c;其中xix_ixi​是x\bf{x}x在第iii个属性上的取值&#xff0c;线性模型试图学得一个通过属性的线性组合来进行预测的函数&#xff0c…

renderdoc 命令行说明

写在前面 1. 本文说明renderdoccmd、qrenderdoc 这2个命令的常见用法&#xff5e; 2. renderdoc相关名词 renderdoccmd what&#xff0c;能干嘛 capture选项&#xff1a; launch 一个应用&#xff0c; 用的RENDERDOC_ExecuteAndInject(), 不支持android10及以上的hook&…

「UG/NX」Block UI 集列表SetList

目录 控件说明界面效果公有属性对话框标题 DialogLabel(仅创建)控件灰显 Enable分组 Group(仅创建)控件显隐 Show控件标题 Label国籍文本 AllowInternationalTextInput(仅创建)显示密文 IsPassword(仅创建)本地化 Localize(仅创建)保存值 RetainValue属性界面代码实现…

数据仓库、数据集市、数据湖,你的企业更适合哪种数据管理架构?

建设企业级数据平台&#xff0c;首先需要了解企业数据&#xff0c;确认管理需求&#xff0c;并选择一个数据管理架构。那么面对纷繁复杂的数据来源&#xff0c;多元化的数据结构&#xff0c;以及他们的管理使用需求&#xff0c;企业数据平台建设该从何处入手呢&#xff1f;哪个…

SQL 学习 day1

day1 SQL 学习 1. 数据库概述 database: .数据持久化 - 将数据保存到能够⻓久保存数据的存储介质中&#xff0c;在掉电的情况下数据也不会丢失 excel 在数据体量方面有限&#xff0c;且解决问题的方法较为麻烦 数据库优点&#xff1a;不关注底层的存储细节、高效的数据访问…

vscode 配置编译调试环境

这里记载一下配置vscode调试和编译的tips。 VScode配置文件 在使用“运行和调试”的时候&#xff0c;往往会在".vscode"下生成两个文件&#xff1a; launch.jsontasks.json launch.json launch.json是运行和调试的入口&#xff0c;在“运行和调试”选项的上方&a…

POSIX正则表达式

维基百科 POSIX基本表达式 https://en.wikibooks.org/wiki/Regular_Expressions/POSIX_Basic_Regular_Expressions POSIX扩展正则表达式 https://en.wikibooks.org/wiki/Regular_Expressions/POSIX-Extended_Regular_Expressions 正则表达式 https://en.wikipedia.org/wiki/R…