剑指 Offer(第2版)面试题 51:数组中的逆序对

剑指 Offer(第2版)面试题 51:数组中的逆序对

  • 剑指 Offer(第2版)面试题 51:数组中的逆序对
    • 解法1:暴力
    • 解法2:归并排序

剑指 Offer(第2版)面试题 51:数组中的逆序对

题目来源:65. 数组中的逆序对

解法1:暴力

Hard 题用暴力解法居然不超时…

代码:

class Solution
{
public:
	int inversePairs(vector<int> &nums)
	{
		int n = nums.size();
		int ans = 0;
		for (int i = 0; i < n; i++)
		{
			for (int j = i + 1; j < n; j++)
			{
				if (nums[i] > nums[j])
					ans++;
			}
		}
		return ans;
	}
};

复杂度分析:

时间复杂度:O(n2),其中 n 是数组 nums 的元素个数。

空间复杂度:O(1)。

解法2:归并排序

O(n2) 的时间复杂度,即使在 AcWing 上能过,面试官也不会让你过的。

那么比暴力更快一点的复杂度为 O(nlogn),怎么才会有 logn 出现呢?

一半一半就会有 logn 出现。

怎么样把一半一半用到这道题上呢?

答案是归并排序。

归并排序详解:归并排序算法C++实现(超详细解析!!!!)。

举个例子:

将数组 [1,2,3,4,5,6,0] 对半切,变成 arr1=[1,2,3,4] 和 arr2=[5,6,0]。

将 arr1 里的 1 和 arr2 的 5 做比较,发现1<5,要是 arr2 里的 0 是 7 或者 8 就好了,这样 1 就比 arr2 里的所有数字都小,就不存在逆序对。

那么假设 arr2 排过序了,是[0,5,6],刚好 arr1 目前也是排过序的。

  1. arr1 里的 1 是大于 arr2 里的0的,那么 arr1 里的 1 且其之后的所有数字都比 0 大,则对于 0 来说,有 4 个数字比它大,所以答案 +4,0 被 arr1 里的所有数字都比较过了(也不是一个个遍历比较,而是用了有序的性质),0 就可以被丢弃了(实际上是放到归并排序的临时数组里了)。
    2、此时 1 是小于 5 的,那么可以断定 1 比 arr2 里的所有数字都小,也可以把 1 和之前的 0 一样处理掉
    3、现在 arr1=[2,3,4],arr2=[5,6],2 小于 5,所以 2 比 arr2 里的所有元素都要小,丢弃,一直循环下去,4 也被丢掉,逆序对完成。

所以解法就是归并排序,只加了一行 countInversePairs += mid - i + 1

递归里面运行完两个 mergeSort 后,start ~ mid 和 mid+1 ~ end 都是有序的,且两个内部的逆序对都已经算过了,只需要算当前状态的逆序对就行了,不会重复。

代码:

class Solution
{
private:
	int countInversePairs = 0;

public:
	int inversePairs(vector<int> &nums)
	{
		if (nums.empty())
			return 0;
		mergeSort(nums, 0, nums.size() - 1);
		return countInversePairs;
	}
	// 辅函数 - 归并排序
	void mergeSort(vector<int> &nums, int left, int right)
	{
		if (left < right)
		{
			int mid = (left + right) / 2;
			mergeSort(nums, left, mid);		 // 对 nums[left, mid] 进行排序
			mergeSort(nums, mid + 1, right); // 对 nums[mid + 1, right] 进行排序
			merge(nums, left, mid, right);	 // 合并
		}
	}
	// 合并操作:将两个有序的子序列合并为一个有序序列
	void merge(vector<int> &nums, int left, int mid, int right)
	{
		// 此时 nums[left, mid] 和 nums[mid+1, right] 都有序
		vector<int> temp(right - left + 1); // 辅助数组
		int i = left, j = mid + 1;
		int k = 0; // k 为数组 temp 的下标
		while (i <= mid && j <= right)
		{
			if (nums[i] <= nums[j])
				temp[k++] = nums[i++];
			else
			{
				temp[k++] = nums[j++];
				// 因为 nums[left, mid] 是增序的,所以 nums[i, mid] 都大于 nums[j],共有 mid - i + 1 个逆序对
				countInversePairs += mid - i + 1;
			}
		}
		while (i <= mid)
			temp[k++] = nums[i++];
		while (j <= right)
			temp[k++] = nums[j++];
		// 将排好序的数组 temp 拷贝给数组 nums[left, right]
		for (int i = left, k = 0; i <= right; i++, k++)
			nums[i] = temp[k];
	}
};

复杂度分析:

时间复杂度:O(n2),其中 n 是数组 nums 的元素个数。当有 n 个元素时,需进行 logn 轮归并排序,每一轮归并其比较次数不超过 n,元素移动次数都是 n。因此。归并排序的时间复杂度为 O(nlogn)。

空间复杂度:O(n),其中 n 是数组 nums 的元素个数。归并排序时需要和待排序区间内元素个数相等的存储空间。

你可能感兴趣的:(剑指,Offer,算法,排序算法,C++,数据结构与算法,剑指Offer)