6. 归并排序
6.1 基础归并排序
分层两半,而后合并。
重点:MargeSort把比较变成了有序的东西,这个有序的东西可以帮我们做很多事情
6.1.1 递归的归并排序
两个函数:
分:process(arr,L,R) --> 保证[L,R]范围上有序。
public static void mSort(int[] arr , int l, int r){
if(l == r){
return;
}
int mid = (l+r)/2;
mSort(arr,l,mid);
mSort(arr,mid+1,r);
marge(arr,l,mid,r);
}
合:每个process中都要利用marge合一下,利用一个辅助数组长度就是R-L。把这两部分按顺序复制到辅助数组中,再把辅助数组替换到L-R之间。
public static void marge(int[] arr , int l,int mid, int r){
int[] help = new int[r-l+1];
int i = l, j = mid+1;
int k = 0;
while(i <= mid && j <= r){
if(arr[i] <= arr[j]){
help[k] = arr[i];
i++;
}else {
help[k] = arr[j];
j++;
}
k++;
}
while(i <= mid){
help[k] = arr[i];
i++;
k++;
}
while(j <= r){
help[k] = arr[j];
j++;
k++;
}
for (int m = 0; m < help.length; m++) {
arr[l+m] = help[m];
}
}
6.1.2 非递归的归并排序
非递归模拟步长来进行计算,marge不变。
step从1开始不断翻倍直到超过N,停止。
按照步长为step从0到n遍历,可以计算出right和mid,然后marge即可。
public static void mSortWithoutRecursion(int[] arr ){
int N = arr.length;
int step = 1;
// 5 4 8 2 3
while(step < N){
int left = 0, mid, right;
while(left < N){
right = left+step*2-1 >= N-1?N-1:(left+step*2-1);
mid = (left+right)/2;
marge(arr,left,mid,right);
left = right+1;
}
//// 假设integer.max = 2e9,N = 2e9; step = 1e9+1 ,翻倍就炸了
if(step > N/2){
break;
}
step <<=1;
}
}
6.2 数组小和
1. 题目
在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。
例子: [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
2. 思路
数组最小和也可以看成:假设第i个数后面有x个比他大,那么在数组最小和中,第i个数出现的次数就是i*x次。
第i个数出现的次数似乎就是x次,而累加的值为arr[i]*x。(2023年9月24日)
ps:通过降序考虑也可以。
因此通过归并排序,在归并的时候,一个数组有分成左右部分(L/R),此时两部分应该都有序,规则如下:
-
L中的数如果入小于R中的数(也就是进入辅助数组的时候),记录R中剩下的元素的个数(也就是比L[i]大的数的个数),这个数乘上L[i]就是当前这个阶段,比L[i]大的数的累加值。
-
R中的数如果进入辅助数组,不进行相加,因为R在右侧,右侧的数不在乎有什么数在左面。
-
递归只需要返回smallNum(左)+smallNum(右)+marge(左右合并的数组)即可。
3.代码
public static int smallSum(int[] arr , int l, int r){
if(l == r || arr.length == 0){
return 0;
}
int mid = (l+r)/2;
return smallSum(arr,l,mid)+
smallSum(arr,mid+1,r)+
marge(arr,l,mid,r);
}
public static int marge(int[] arr , int l,int mid, int r){
int[] help = new int[r-l+1];
int i = l, j = mid+1;
int k = 0;
int ans = 0;
while(i <= mid && j <= r){
if(arr[i] < arr[j]){
// 这里记录了左侧数组进入后右侧数组剩余的元素个数
ans += arr[i]*(r-j+1);
help[k++] = arr[i++];
}else {
help[k++] = arr[j++];
}
}
while(i <= mid) help[k++] = arr[i++];
while(j <= r) help[k++] = arr[j++];
for (int m = 0; m < help.length; m++) arr[l+m] = help[m];
return ans;
}
6.3 翻转对
1. 题目
https://leetcode.cn/problems/reverse-pairs/
在一个数组中,对于每个数num,求有多少个后面的数 * 2 依然比num小,求总个数。
比如:[3,1,7,0,2]
3的后面有:1,0
1的后面有:0
7的后面有:0,2
0的后面没有
2的后面没有
所以总共有5个
2. 思路
如果已经部分排好序了,如[1,3] 和[0,2,7],可以O(n)的方式知道左数组中的数在右数组中的btt的个数。
初始arr[i] = 1,arr[j] = 0;
如果arr[j]*2 < arr[i] :j++并且记录答案为j; --- 越界可以开long
否则 i++; (因为arr[i] < arr[i+1],所以j前面的数一定也可以满足前提,直接从j判断即可)
右侧的数不需要操作,因为需要去重复
3.代码
public static int marge(int[] arr , int l,int mid, int r){
// 假设L[l...mid],R[mid+1...r]都已经排好序了,求此时L中的数对于R中的数的btt
int ans = 0;
int j = 0;
for(int i = l; i <= mid; i++){
while(j+mid+1 <= r &&(long)arr[i] > (long)arr[j+mid+1]*2){
j++;
}
ans += j;
}
// 正常的归并
int[] help = new int[r-l+1];
int i = l;
j = mid+1;
int k = 0;
while(i <= mid && j <= r){
if(arr[i] <= arr[j]){
help[k] = arr[i];
i++;
}else {
help[k] = arr[j];
j++;
}
k++;
}
while(i <= mid){
help[k] = arr[i];
i++;
k++;
}
while(j <= r){
help[k] = arr[j];
j++;
k++;
}
for (int m = 0; m < help.length; m++) {
arr[l+m] = help[m];
}
return ans;
}
6.4 逆序对
1. 题目
https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/
在一个数组中,任何一个前面的数a,和任何一个后面的数b,如果(a,b)是降序的,就称为逆序对。返回数组中逆序对的个数。
2. 思路
归并的本质就是无序的代码可以让我们有序比较。
对于有序数组L[l...mid] 和 R[mid+1...r]来说,
1. L数组加入help的时候无序更改(因为左侧代表已经算过逆序对的数)
2. R数组中加入help的时候,逆序对个数增加为:mid-i+1个。即,此时L数组中还未加入help中的数的个数。
3.代码
public int merge(int[] nums,int l,int mid,int r){
if(l == r){
return 0;
}
// 核心代码开始
// 假设L[l...mid] 和 R[mid+1...r]都有序的情况下
int j = 0;
int ans = 0;
// i为遍历R数组的index
for(int i = l; i <= mid; i++){
while(j+mid+1 <= r && nums[i] > nums[j+mid+1]){
ans += mid-i+1;
j++;
}
}
// 核心代码结束
int[] help = new int[r-l+1];
int i = l;
j = mid+1;
int k = 0;
while(i <= mid && j <= r){
if(nums[i] < nums[j]) help[k++] = nums[i++];
else help[k++] = nums[j++];
}
while(i <= mid) help[k++] = nums[i++];
while(j <= r) help[k++] = nums[j++];
for(int m = 0; m < help.length; m++){
nums[m+l] = help[m];
}
return ans;
}
6.7 区间和的个数(未完成)
1. 题目
https://leetcode.cn/problems/count-of-range-sum/
给你一个整数数组 nums 以及两个整数 lower 和 upper 。求数组中,值位于范围 [lower, upper] (包含 lower 和 upper)之内的 区间和的个数 。
区间和 S(i, j) 表示在 nums 中,位置从 i 到 j 的元素之和,包含 i 和 j (i ≤ j)。
输入:nums = [-2,5,-1], lower = -2, upper = 2
输出:3
解释:存在三个区间:[0,0]、[2,2] 和 [0,2] ,对应的区间和分别是:-2 、-1 、2 。
输入:nums = [0], lower = 0, upper = 0
输出:1
2. 思路
首先,求一个范围内的值在lower,upper之间,就要先确定一个范围内的值 => 优化:前缀和来求。
然后用归并排序,得到
3.代码
标签:归并,06,arr,int,mid,marge,数组,排序 From: https://www.cnblogs.com/ylw-blogs/p/17818904.html