归并排序、小和问题、逆序对

目录

一.归并排序

二.小和问题

三.逆序对


一.归并排序

1.算法思想

归并排序采取分治法的思想。

第一步将一个数组按规模依次分为两部分 ,先左侧排序,再右侧排序,最后再通过辅助数组整体外排,将2个有序数列合并为最终有序。

主要是如何将2个有序数列合并。只要从比较2个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。

2.时间复杂度O(N*logN),额外空间复度O(N),算法稳定

3.实现代码

public static void mergeSort(int[] arr) {
		if (arr == null || arr.length < 2) {
			return;
		}
		mergeSort(arr, 0, arr.length - 1);
	}

	public static void mergeSort(int[] arr, int l, int r) {
		if (l == r) {
			return;
		}
		int mid = l + ((r - l) >> 1);
		mergeSort(arr, l, mid);
		mergeSort(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 i = 0;
		int p1 = l;
		int p2 = m + 1;
		while (p1 <= m && p2 <= r) {
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= m) {
			help[i++] = arr[p1++];
		}
		while (p2 <= r) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
	}

二.小和问题

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.思路分析

这是归并排序的过程,主要考虑在合并两个有序序列时,计算小和。

左边比当前数小的个数,相当于研究右边有多少比当前数大的。p1指向L,p2指向m+1,若前开始小于后开始,则前开始小于后面整个部分,小和为前面当前值*(后序列当前长度),否则没有小和。同时指针把较小数拷贝到辅助数组,较小数指针指针后移,最后再把多出的部分也添加到辅助数组。即最后要把两个有序序列合并到辅助数组并有序。

每次在合并前,先递归处理左半段,右半段。则左右有序,且左右的小和可以得到,再计算左右半段合并时的小和。

归并排序,是分治的思想。先递归分为两个部分,左右两部分分开之后,merge实现合并的过程。首先1,3产生小和1,辅助数组[1,3];然后25产生小和2,有序数组[2,5];13(为内层的有序数组返回来的),4产生小和1,3,有序数组[134];134,25产生小和1*2,3,4,有序数组[12345]。则小和=1+2+1+3+2+3+4=16。

指针移动的过程,比如p1为1比2小,则产生小和1*2,1进辅助数组,p1到3;3比2大,2进辅助数组,p2后移到5;3比5小,3进辅助数组,产生小和3,p1后移到4;同理4进辅助数组,产生小和4,5最后再进辅助数组。

此过程核心代码如下。

while (p1 <= m && p2 <= r) {
			res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
			help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= m) {
			help[i++] = arr[p1++];
		}
		while (p2 <= r) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}

3.实现代码

public class Code_12_SmallSum {

	public static int smallSum(int[] arr) {
		if (arr == null || arr.length < 2) {
			return 0;
		}
		return mergeSort(arr, 0, arr.length - 1);
	}

	public static int mergeSort(int[] arr, int l, int r) {
		if (l == r) {
			return 0;
		}
		int mid = l + ((r - l) >> 1);
		return mergeSort(arr, l, mid) + mergeSort(arr, mid + 1, r) + merge(arr, l, mid, r);
	}

	public static int merge(int[] arr, int l, int m, int r) {
		int[] help = new int[r - l + 1];
		int i = 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[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
		}
		while (p1 <= m) {
			help[i++] = arr[p1++];
		}
		while (p2 <= r) {
			help[i++] = arr[p2++];
		}
		for (i = 0; i < help.length; i++) {
			arr[l + i] = help[i];
		}
		return res;
	}

三.逆序对

参考moonbaby数组中的逆序对博客

归并排序是一种经典的排序算法,可以用于计算逆序对的数量。逆序对指的是在一个数组中,两个元素的顺序与它们在原数组中的顺序相反。 对于给定的数组,我们可以使用归并排序来计算逆序对的数量。具体步骤如下: 1. 将数组不断地分成两半,直到每个子数组只有一个元素。 2. 递归地将这些子数组合并起来。在合并的过程中,统计逆序对的数量。 3. 在合并两个子数组时,需要维护两个指针,分别指向两个子数组的开头。比较这两个指针所指向的元素的大小,如果前一个指针所指向的元素大于后一个指针所指向的元素,则存在逆序对。 4. 在合并过程中,将较小的元素放入一个临时数组,并将指针向后移动。如果存在逆序对,则逆序对的数量等于第一个子数组中剩余的元素个数。 以下是一个示例代码,用于计算逆序对的数量: ```python def merge_sort(nums): if len(nums) <= 1: return nums, 0 mid = len(nums) // 2 left, count_left = merge_sort(nums[:mid]) right, count_right = merge_sort(nums[mid:]) merged = [] count = count_left + count_right i, j = 0, 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) count += len(left) - i j += 1 merged += left[i:] merged += right[j:] return merged, count def reversePairs(nums): _, count = merge_sort(nums) return count ``` 对于题目中给定的数组,调用 `reversePairs` 函数即可计算出逆序对的数量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值