《剑指Offer》学习笔记--面试题14:调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

如果不考虑时间复杂度,最简单的思路应该是从头到尾扫描这个数组,没碰到一个偶数时,拿出这个数字,并把这个数字后面的所有数字往前移动一位。挪完之后在数组的末尾有一个空位,这时把该偶数放入这个空位。由于碰到一个偶数就需要移动O(n)个数字,因此总的时间复杂度是O(n^2)。但是,这种方法不能让面试官满意。

只完成进本功能的解法,仅适用于初级程序员

这个题目要求把奇数放在数组的前半部分,偶数放在后半部分,因此所有的奇数应该位于偶数的前面。也就是说我们在扫描这个数组的时候,如果发现有偶数出现在奇数的前面,我们可以交换它们的顺序,交换之后就符合要求了。

因此我们可以维护两个指针,第一个指针初始化时指向数组的第一个数字,它只向后移动;第二个指针初始化指向数组最后一个数字,它只向前移动。在两个指针相遇之前,第一个指针总是位于第二个指针的前面。如果第一个指针指向的数字是偶数,并且第二个指针指向的数字是奇数,我们就交换这两个数字。基于这个分析,我们可以写出如下代码:

void ReorderOddEven(int *pData, unsigned int length)
{
	if(pData == NULL || length <= 0)
		return;

	int *pBegin = pData;
	int *pEnd = pData + length - 1;

	while(pBegin < pEnd){
		//向后移动pBegin,直到它指向偶数
		while(pBegin < pEnd && *pBegin & 0x01 != 0)
			pBegin++;

		//向前移动pEnd,直到它指向奇数
		while(pBegin < pEnd && *pEnd & 0x01 == 0)
			pEnd--;

		if(pBegin < pEnd){
			int temp = *pBegin;
			*pBegin = *pEnd;
			*pEnd = temp;
		}
	}
}
考虑可扩展性的解法,能秒杀Offer

面试官:如果把题目改成数组中的数按照大小分为两部分,所有负数都在非负数的前面,该怎么做?

面试官期待我们提供的不仅仅是解决一个问题的办法,而是解决一系列同类型问题的通用办法。这就是面试官在考察我们对扩展性的理解,即希望我们能够给出一个模式,在这个模式下能够很方便地把已有的解决方案扩展到同类型的问题上去。

回到面试官新提出的两个问题上来。我们发现要解决这两个新的问题,其实只需要修改函数ReorderOddEven中的两处判断的标准,而大的逻辑框架完全不需要改动。因此我们可以把这个逻辑框架抽象出来,而把判断标准变成一个函数指针,也就是用一个单独的函数来判断数字是不是符合标准。这样我们就把整个函数解耦成两个部分:

一是判断数字应该在数组前半部分还是后半部分标准;

二是拆分数组的操作。

代码如下:

void Reorder(int *pData, unsigned int length, bool (*func)(int))
{
	if(pData == NULL || length <= 0)
		return;

	int *pBegin = pData;
	int *pEnd = pData + length - 1;
	while(pBegin < pEnd){
		while(pBegin < pEnd && !func(*pBegin))
			pBegin++;
		while(pBegin < pEnd && func(*pEnd))
			pEnd--;

		if(pBegin < pEnd){
			int temp = *pBegin;
			*pBegin = *pEnd;
			*pEnd = temp;
		}
	}
}

bool isEven(int n)
{
	return (n & 0x01) == 0;
}
在上面的代码中,函数Reorder根据func的标准把数组pData分成两部分;而函数isEven则是一个具体的标准,即判断一个数是不是偶数。有了这两个函数,我们可以很方便地把数组中的所有奇数移到偶数的前面。实现代码如下:

void RecorderOddEven(int *pData, unsigned int length)
{
	Recorder(pData, length, isEven);
}
如果把问题改成数组中的负数移到非负数的前面,或者把能被3整除的数移到不能被3整除的数的前面,都只需定义新的函数来确定分组的标准,而函数Reorder不需要做任何改动。也就是说解耦的好处就是提高了代码的重用性,为功能扩展提供了便利。







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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值