首页 > 编程语言 >(算法)翻转对————<分治-归并排序>

(算法)翻转对————<分治-归并排序>

时间:2024-08-22 15:50:57浏览次数:13  
标签:归并 right nums cur1 int 分治 mid 翻转 left

1. 题⽬链接:493.翻转对

2. 题⽬描述:

题⽬解析:

翻转对和逆序对的定义⼤同⼩异,逆序对是前⾯的数要⼤于后⾯的数。⽽翻转对是前⾯的⼀个数要 ⼤于后⾯某个数的两倍。因此,我们依旧可以⽤归并排序的思想来解决这个问题。

3. 解法(归并排序):

算法思路:

⼤思路与求逆序对的思路⼀样,就是利⽤归并排序的思想,将求整个数组的翻转对的数量,转换成 三部分:左半区间翻转对的数量,右半区间翻转对的数量,⼀左⼀右选择时翻转对的数量。重点就 是在合并区间过程中,如何计算出翻转对的数量。

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

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

因此我们需要在归并排序之前完成翻转对的统计。

下⾯依旧以⼀个⽰例来模仿两个有序序列如何快速求出翻转对的过程:

假定已经有两个已经有序的序列left=[4,5,6] right=[1,2,3]。

⽤两个指针cur1 cur2遍历两个数组。

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

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

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

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

降序版本

C++算法代码: 

class Solution
{
	int tmp[50010];
public:
	int reversePairs(vector<int>& nums)
	{
		return mergeSort(nums, 0, nums.size() - 1);
	}
	int mergeSort(vector<int>& nums, int left, int right)
	{
		if (left >= right) return 0;
		int ret = 0;
		// 1. 先根据中间元素划分区间 
		int mid = (left + right) >> 1;
		// [left, mid] [mid + 1, right]
		// 2. 先计算左右两侧的翻转对 
		ret += mergeSort(nums, left, mid);
		ret += mergeSort(nums, mid + 1, right);
		// 3. 先计算翻转对的数量 
		int cur1 = left, cur2 = mid + 1, i = left;
		while (cur1 <= mid) // 降序的情况 
		{
			while (cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) cur2++;
			if (cur2 > right)
				 break;
			ret += right - cur2 + 1;
			cur1++;
		}
		// 4. 合并两个有序数组 
		cur1 = left, cur2 = mid + 1;
		while (cur1 <= mid && cur2 <= right)
			tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : 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];

		return ret;
	}
};

 Java算法代码:

class Solution
{
	int[] tmp;
	public int reversePairs(int[] nums)
	{
		int n = nums.length;
		tmp = new int[n];
		return mergeSort(nums, 0, n - 1);
	}
	public int mergeSort(int[] nums, int left, int right)
	{
		if (left >= right) return 0;
		int ret = 0;
		// 1. 根据中间元素,将区间分成两部分 
			int mid = (left + right) / 2;
		// [left, mid] [mid + 1, right]
		// 2. 求出左右两个区间的翻转对 
		ret += mergeSort(nums, left, mid);
		ret += mergeSort(nums, mid + 1, right);
		// 3. 处理⼀左⼀右 - 先计算翻转对 
		int cur1 = left, cur2 = mid + 1, i = left;
		// 降序版本 
		while (cur1 <= mid)
		{
			while (cur2 <= right && nums[cur2] >= nums[cur1] / 2.0) cur2++;
			if (cur2 > right)
				break;
			ret += right - cur2 + 1;
			cur1++;
		}
		// 4. 合并两个有序数组 
		cur1 = left; cur2 = mid + 1;
		while (cur1 <= mid && cur2 <= right)
			tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur2++] : 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];

		return ret;
	}
}

升序版本

C++算法代码:

class Solution
{
	int tmp[50010];
public:
	int reversePairs(vector<int>& nums)
	{
		return mergeSort(nums, 0, nums.size() - 1);
	}
	int mergeSort(vector<int>& nums, int left, int right)
	{
		if (left >= right) return 0;
		int ret = 0;
		// 1. 先根据中间元素划分区间 
		int mid = (left + right) >> 1;
		// [left, mid] [mid + 1, right]
		// 2. 先计算左右两侧的翻转对 
		ret += mergeSort(nums, left, mid);
		ret += mergeSort(nums, mid + 1, right);
		// 3. 先计算翻转对的数量 
		int cur1 = left, cur2 = mid + 1, i = left;
		while (cur2 <= right) // 升序的情况 
		{
			while (cur1 <= mid && nums[cur2] >= nums[cur1] / 2.0) cur1++;
			if (cur1 > mid)
				break;
			ret += mid - cur1 + 1;
			cur2++;
		}
		// 4. 合并两个有序数组 
		cur1 = left, cur2 = mid + 1;
		while (cur1 <= mid && cur2 <= right)
			tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
		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];

		return ret;
	}
};

Java算法代码:

class Solution
{
	int[] tmp;
	public int reversePairs(int[] nums)
	{
		int n = nums.length;
		tmp = new int[n];
		return mergeSort(nums, 0, n - 1);
	}
	public int mergeSort(int[] nums, int left, int right)
	{
		if (left >= right) return 0;
		int ret = 0;
		// 1. 根据中间元素,将区间分成两部分 
		int mid = (left + right) / 2;
		// [left, mid] [mid + 1, right]
		// 2. 求出左右两个区间的翻转对 
		ret += mergeSort(nums, left, mid);
		ret += mergeSort(nums, mid + 1, right);
		// 3. 处理⼀左⼀右 - 先计算翻转对 
		int cur1 = left, cur2 = mid + 1, i = left;
		// 升序版本 
		while (cur2 <= right)
		{
			while (cur1 <= mid && nums[cur2] >= nums[cur1] / 2.0) cur1++;
			if (cur1 > mid)
				break;
			ret += mid - cur1 + 1;
			cur2++;
		}
		// 4. 合并两个有序数组 - 升序 
			cur1 = left; cur2 = mid + 1;
		while (cur1 <= mid && cur2 <= right)
			tmp[i++] = nums[cur1] <= nums[cur2] ? nums[cur1++] : nums[cur2++];
		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];

		return ret;
	}
}

标签:归并,right,nums,cur1,int,分治,mid,翻转,left
From: https://blog.csdn.net/2301_79580018/article/details/141430125

相关文章

  • 移动端上传图片翻转的解决方案
    最近在处理移动端选择图片实时预览并上传时遇到一个问题:上传前图片预览正常,但上传到服务器上的图片展示到页面上时,有时会出现图片翻转的问题,一般是翻转90度。后经一翻研究找到了问题所在,特在此记录一下。问题描述接上面提到的问题,经过一些测试,发现:如果选取的图片是在横屏状......
  • Codeforces Round 967 (Div. 2) C题 类分治解法
    废话不多说,先上代码t=int(input())whilet>0:n=int(input())pre_d={1:[iforiinrange(2,n+1)]}pair_l=[]whilelen(pre_d)!=0:item=pre_d.items()now_d={}fork,vinitem:forii......
  • 点分治
    点分治点分治是一个求树上路径问题的算法,算法流程通常是:找到子树中的重心,计算重心的子树的每一个点与重心的路径的数据,接着统计整体答案。CloseVertices思路很明显,这是一道点分治题目,但有两个限制条件,考虑将两个条件排序起来,双指针找第一个条件,树状数组维护第二个条件,但是同......
  • 2个有序数组,归并重拍
    publicstaticvoidmerge(int[]a,int[]b){varpa=a.Length-1;varpb=b.Length-1;varc=newint[b.Length];varpc=b.Length-1;while(pc>=0){if(a[pa]>b[pb]){c[pc]=a[pa];......
  • 经典分治算法
    RT,主要介绍一些经典的分治算法CDQ分治经典人类智慧算法。三维偏序问题三维偏序是CDQ分治的一个经典应用,搭配树状数组可以在\(O(n\log^2n)\)的时间复杂度内解决问题。如果我们枚举每一个元素,然后枚举其他的元素的话,可以在\(O(n^2)\)的时间复杂度解决这个问题,但显然无法......
  • 代码随想录算法训练营day09|151.翻转字符串里的单词,卡码网:55.右旋转字符串,28.实现 str
    151.翻转字符串里的单词题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/description/暴力removeExtraSpaces:voidremoveExtraSpaces(string&s){for(inti=s.size()-1;i>0;i--){if(s[i]==''&&s[i]=......
  • 【数据结构与算法】分治法
    分治法目录一.分治法的思想二.分治法的步骤三.举个例子四.具体实现五.完整代码一.分治法的思想将一个大问题,拆解成为若干个小问题,而且大问题与小问题的解决方法一样.说到这里我们可以联想到递归,没错就是用递归的思想.分:递归解决较小的问题治:子问题的解构建原......
  • Note - 树分治(点分治、点分树)
    陈年笔记,现在可能不会了。点分治Q1:基本思想是什么?将路径分为经过\(u\)和不经过\(u\)的两类,在每次分治中计算经过\(u\)的路径数量。Q2:如何统计?一般:遍历\(u\)的每个子节点\(v\),把\(v\)子树内的节点记录下来,得到答案并更新数组。容斥:把\(u\)子树内的节点都记录下......
  • 自定义小灯状态翻转函数
    一、函数原理   函数主要是通过 uint8_tGPIO_ReadInputDataBit(GPIO_TypeDef*GPIOx,uint16_tGPIO_Pin)这个读取指定的I/O口的电平,来实现小灯状态的翻转。二、示例代码voidLED_Blue_Turn(void){ if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)==0) { GPIO_Se......
  • 根号分治莫队
    莫队参考文章:莫队细讲——从零开始学莫队莫队算法——从入门到黑题oiwiki--普通莫队莫队简介莫队算法是由莫涛提出的算法。在莫涛提出莫队算法之前,莫队算法已经Codeforces的高手圈里小范围流传,但是莫涛是第一个对莫队算法进行详细归纳总结的人。莫涛提出莫队算法时,只分析......