《剑指offer》——数组中的逆序对

60 篇文章 3 订阅

更多2019年的技术文章,欢迎关注我的微信公众号:码不停蹄的小鼠松(微信号:busy_squirrel),也可扫下方二维码关注获取最新文章哦~

T:

题目描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

  • 暴力搜索法

直接看代码,简洁明了。
code:

public int InversePairs(int [] array) {
    int nums = 0;
	
	for (int i = 1; i < array.length; i++) {
		for (int j = 0; j < i; j++) {
			if (array[j] > array[i]) {
				nums ++;
			}
		}
	}
	return nums;
}

如果只是单纯的从暴力搜索的角度去解决该问题,貌似简单了点儿,且这种情况下的耗时为 O ( n 2 ) O(n^2) O(n2)。我自己也没想出来用什么更简单高效的方法解决,之前有想到用动态规划思想,代码敲好后,突然发现思路有严重bug。。。

看到讨论版的代码,很多人说用归并排序,对于这个排序,我也忘得差不多了,重新学习!

  • more efficiency methods

先复习一下归并排序…
具体的算法思想不说了,给个链接:http://blog.csdn.net/morewindows/article/details/6678165/

这里写图片描述

code:

	package niuke.sward2offer.inversePairs;
	
	/**
	 * 归并排序,递归方式
	 * 
	 * date: 2015.11.11  22:00
	 * @author SSS
	 *
	 */
	public class Solution1 {
	
		/**
		 * 将两个有序数组合并为一个新数组,耗时O(n)
		 * 
		 * @param array		// 共用一个数组,即都在数组array上进行两个数组片段的重新排序
		 * @param aStart
		 * @param aEnd
		 * @param bStart
		 * @param bEnd
		 * @param newArray
		 */
		public void merge(int []array, int aStart, int aEnd, int bStart, int bEnd, int []newArray) {
			int i = aStart;
			int j = bStart;
			
			int count = 0;	// 新数组的下标
			while (i <= aEnd && j <= bEnd) {
				if (array[i] < array[j]) {
					newArray[count ++] = array[i ++];
				}
				else {
					newArray[count ++] = array[j ++];
				}
			}
			
			while (i <= aEnd) {
				newArray[count++] = array[i++];
			}
			while (j <= bEnd) {
				newArray[count++] = array[j++];
			}
			
			/**
			 * 因为是要在下标为aStart和bEnd之间进行排序,所以将要有序的数列按对应下标覆盖array中原有的数据。
			 */
			for (int k = 0; k < bEnd - aStart + 1; k++) {
				array[aStart + k] = newArray[k];
			}
			
		}
		
		/**
		 * 归并排序的主体部分
		 * @param array
		 * @param start
		 * @param end
		 * @param newArray
		 */
		public void mergeSort(int []array, int start, int end, int []newArray) {
			if (start < end) {
				// 将下标为start和end这一部分的子数组从中间分为左、右两个部分
				int mid = (start + end) / 2;
				mergeSort(array, start, mid, newArray);	// 认为左边有序
				mergeSort(array, mid + 1, end, newArray);	// 认为右边有序
				this.merge(array, start, mid, mid + 1, end, newArray);	
			}
		}
		
		public static void main(String []args) {
			int []array = {6, 3, 5, 9, 1, 8, 2, 7, 10, 4};
			int []newArray = new int[array.length];
			
			Solution1 solution1 = new Solution1();
			solution1.mergeSort(array, 0, array.length - 1, newArray);
			
			for (int i : newArray) {
				System.out.println(i);
			}
		}
	}

那么,能不能从这种方法的排序中得到点儿启发,从而算出原始数组中的逆序对呢?

归并排序的最底层,是从一个个的单个数字开始合并的,两个单个数字合并为一个二维有序数组,两个二维有序数组合并为一个有序的四维有序数组,……

归并排序的核心在于将两个有序数组合并为一个有序数组,代码如下:

	/**
		 * 将两个有序数组合并为一个新数组,耗时O(n)
		 * 
		 * @param array		// 共用一个数组,即都在数组array上进行两个数组片段的重新排序
		 * @param aStart
		 * @param aEnd
		 * @param bStart
		 * @param bEnd
		 * @param newArray
		 */
		public void merge(int []array, int aStart, int aEnd, int bStart, int bEnd, int []newArray) {
			int i = aStart;
			int j = bStart;
			
			int count = 0;	// 新数组的下标
			while (i <= aEnd && j <= bEnd) {
				if (array[i] < array[j]) {
					newArray[count ++] = array[i ++];
				}
				else {
					newArray[count ++] = array[j ++];
				}
			}
			
			while (i <= aEnd) {
				newArray[count++] = array[i++];
			}
			while (j <= bEnd) {
				newArray[count++] = array[j++];
			}
			
			/**
			 * 因为是要在下标为aStart和bEnd之间进行排序,所以将要有序的数列按对应下标覆盖array中原有的数据。
			 */
			for (int k = 0; k < bEnd - aStart + 1; k++) {
				array[aStart + k] = newArray[k];
			}
			
		}

比如将两个有序数组: A = [ a 1 , a 2 , a 3 , …   ] , B = [ b 1 , b 2 , b 3 , …   ] A = [a_1, a_2, a_3, \dots],B = [b_1, b_2, b_3, \dots] A=[a1,a2,a3,]B=[b1,b2,b3,]合并为一个有序数组 C = [ b 1 , a 1 , a 2 , a 3 , b 2 , …   ] C = [b_1, a_1, a_2, a_3, b_2, \dots] C=[b1,a1,a2,a3,b2,]. 另外还一个潜在的条件,在原数组中,就是数组 A A A中的元素都在 B B B中元素的左边。

假设 a 1 > b 1 a_1 > b_1 a1>b1,那么 b 1 b_1 b1要先放在数组 C C C中,既然 a 1 > b 1 a_1 > b_1 a1>b1,且数组 A A A也是有序数组,那么数组 A A A中元素 a 1 a_1 a1之后的元素(包括 a 1 a_1 a1)都要大于 b 1 b_1 b1,即都可以和 b 1 b_1 b1组成逆序对,后面的依次类推。。。

所以,在归并排序的过程中求解逆序对的方法如下:设置一个全局变量,用于统计每次调用merge(…)函数时存在的逆序对。

code:

	package niuke.sward2offer.inversePairs;
	
	/**
	 * 在归并排序上的改进,可求得数组中的逆序对。
	 * 
	 * date:2015.11.11  22:40
	 * @author SSS
	 *
	 */
	public class Solution2 {
		
		private int inversePairsCount = 0;
		
		public int InversePairs(int [] array) {
			int []newArray = new int[array.length];
	        this.mergeSort(array, 0, array.length - 1, newArray);
	        
	        return inversePairsCount;
	    }
	
		/**
		 * 将两个有序数组合并为一个新数组,耗时O(n)
		 * 
		 * @param array		// 共用一个数组,即都在数组array上进行两个数组片段的重新排序
		 * @param aStart
		 * @param aEnd
		 * @param bStart
		 * @param bEnd
		 * @param newArray
		 */
		public void merge(int []array, int aStart, int aEnd, int bStart, int bEnd, int []newArray) {
			int i = aStart;
			int j = bStart;
			
			int count = 0;	// 新数组的下标
			while (i <= aEnd && j <= bEnd) {
				if (array[i] > array[j]) {
					newArray[count ++] = array[j ++];
					inversePairsCount += aEnd - i + 1;	// 将该轮合并当中出现的逆序对统计出来
				}
				else {
					newArray[count ++] = array[i ++];
				}
			}
			
			while (i <= aEnd) {
				newArray[count++] = array[i++];
			}
			while (j <= bEnd) {
				newArray[count++] = array[j++];
			}
			
			/**
			 * 因为是要在下标为aStart和bEnd之间进行排序,所以将要有序的数列按对应下标覆盖array中原有的数据。
			 */
			for (int k = 0; k < bEnd - aStart + 1; k++) {
				array[aStart + k] = newArray[k];
			}
			
		}
		
		/**
		 * 归并排序的主体部分
		 * @param array
		 * @param start
		 * @param end
		 * @param newArray
		 */
		public void mergeSort(int []array, int start, int end, int []newArray) {
			if (start < end) {
				// 将下标为start和end这一部分的子数组从中间分为左、右两个部分
				int mid = (start + end) / 2;
				mergeSort(array, start, mid, newArray);	// 认为左边有序
				mergeSort(array, mid + 1, end, newArray);	// 认为右边有序
				this.merge(array, start, mid, mid + 1, end, newArray);	
			}
		}
		
		public static void main(String []args) {
			int []array = {5, 3, 4, 1};
			
			Solution2 solution2 = new Solution2();
			
			
			System.out.println("nums = " + solution2.InversePairs(array));
		}
	}

更多2019年的技术文章,欢迎关注我的微信公众号:码不停蹄的小鼠松(微信号:busy_squirrel),也可扫下方二维码关注获取最新文章哦~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值