面试题21:调整数组顺序使奇数位于偶数前面。输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
如果不考虑时间复杂度,最简单的思路是从头扫描这个数组,每碰到一个偶数,拿出这个数字,并把位于这个数字后面的所有数字往前挪动一位,挪完之后数组的末尾就有一个空位,这时把该偶数放入这个空位。由于每碰到一个偶数就需要移动O(n)个数字,因此总的时间复杂度是O(n²)。
只完成基本功能的解法:这道题目要求把奇数放在数组的前半部分,偶数放在数组的后半部分,因此所有的奇数应该位于偶数的前面。即,我们在扫描这个数组的时候,如果发现有偶数出现在奇数前面,则交换它们的顺序,交换之后就符合要求了。因此,我们可以维护两个指针,第一个指针初始化为指向数组的第一个数字,它只向后移动,第二个指针初始化为指向数组的最后一个数字,它只向前移动,在两个指针相遇之前,第一个指针总是位于第二个指针前面。如果第一个指针指向的是偶数,第二个指针指向的是奇数,则交换这两个数字:
#include <iostream>
using namespace std;
void ReorderOddEven(int nums[], int length) {
if (nums == nullptr || length <= 0) {
return;
}
int* head = nums;
int* tail = nums + length - 1;
while (head != tail) {
if (*head & 1) { // 当head指向奇数时,检查head指向的下一个数字
++head;
} else { // 当head指向偶数时,交换head和tail指向的数字,下一步仍检查head指向的数字,且tail指向的数字已经是偶数,需自减
int temp = *tail;
*tail = *head;
*head = temp;
--tail;
}
}
}
int main() {
int nums[] = {2,2,2,2,1};
ReorderOddEven(nums, 5);
for (int i : nums) {
cout << i << endl;
}
return 0;
}
可扩展性的解法:如面试者是毕业生或初级程序员,面试官会满意以上解法,但如果面试者申请的是资深开发,就需要考虑到程序扩展性,如把负数放到非负数前面等类似问题。要解决这种类似问题,只需要修改函数中的if条件判断,而大的逻辑框架不需要改动,因此我们可以把逻辑框架抽象出来,而把判断的标准变成一个函数指针,即用一个单独的函数来判断数字是不是符合标准。这样我们可以把整个函数解耦成两部分:一是判断数字应该在前半部分还是后半部分的标准,二是拆分数组的操作:
#include <iostream>
using namespace std;
void ReorderOddEven(int nums[], int length, bool (*func)(int)) { // 函数指针参数可简化为bool func(int)
if (nums == nullptr || length <= 0) {
return;
}
int* head = nums;
int* tail = nums + length - 1;
while (head != tail) {
if (func(*head)) { // 当head指向奇数时,检查head指向的下一个数字
++head;
} else { // 当head指向偶数时,交换head和tail指向的数字,下一步仍检查head指向的数字,且tail指向的数字已经是偶数,需自减
int temp = *tail;
*tail = *head;
*head = temp;
--tail;
}
}
}
bool isEven(int num) {
return num & 1;
}
int main() {
int nums[] = {2,2,2,2,1};
ReorderOddEven(nums, 5, isEven);
for (int i : nums) {
cout << i << endl;
}
return 0;
}
这样类似问题只需要定义新的判断分组标准的函数而不需要对Reorder进行任何改动。即解耦好处是提高了代码的重用性,为功能扩展提供了便利。