题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
基本实现
如果不考虑时间复杂度,最简单的思路应该是从头扫描这个数组,每碰到一个偶数时,拿出这个数字,并把位于这个数字后面的所有的数字往前面挪动一位。挪完之后在数组的末尾有一个空位,这时把该偶数放入这个空位。由于没碰到一个偶数就需要移动O(n)个数字,因此总的时间复杂度是O(n2).但是,这种方法不能让面试官满意。不过如果我们在听到题目之后马上能够说出这个解法,面试官至少会觉得我们的思维非常灵敏。
只完成基本功能的解法,仅适用于初级程序员
这个题目要求把奇数放在数组的前半部分,偶数放在数组的后半部分,因此所有的奇数应该位于偶数的前面,也就是说我们在扫描这个数组的时候,如果发现有偶数在奇数的前面,我们可以交换他们的数序,交换之后就符合要求了。
因此我们可以维护两个指针,第一个指针初始化时指向数组的第一个数字,它只向后移动;第二个指针初始化时指向数组的最后一个数字,它指向前移动。在两个指针相遇之前,第一个指针总是位于第二个指针的前面。如果第一个指针的数字是偶数,并且第二个指针指向的数字是奇数,我们就交换两个数字。
考虑可扩展性的解法,秒杀Offer
如果是面试应届毕业生或者工作时间不长的程序员,面试官会满意前面的代码,但如果应聘者申请的是资深的开发职位,那面试官可能会接着问几个问题。
面试官:如果把题目改成数组中的数按照大小分为两部分,所有的负数在所有的非负数的前面,该怎么做?
如果再把题目改改,变成 把数组中的数分成两部分,能被3整除的数都在不能被3整除的数的前面,怎么办?
这就是面试官在考察我们对可扩展性的理解,即希望我们能够给出一个模式,在这个模式下能够很方面第把已有的解决方案扩展到同类型的问题上去。
代码如下:
public class Offer21 {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
change(arr, OPER_TYPE.ODDEVEN);
for (int i : arr) {
System.out.print(" " + i);
}
}
public static void change(int[] arr, OPER_TYPE type) {
if (arr.length == 0 || arr == null) {
return;
}
int left = 0;
int right = arr.length - 1;
while (left < right) {
// func(type, arr[left])
while (left < right && (arr[left] & 1) == 1) {
left++;
}
// !func(type, arr[right])
while (right > left && (arr[right] & 1) == 0) {
right--;
}
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
}
在上面的代码中,虽然功能已经完成了,但是按照面试官所说的,一会改成负数正数交换,一会3的整除数换,那我们写代码的还不是疯了,天天改方法核心源码,所以我们想,核心源码不动(因为这道题除了判断条件改变外,其他都可以不改动),所以我进行了改进:
public class Offer21 {
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
change(arr, OPER_TYPE.ODDEVEN);
for (int i : arr) {
System.out.print(" " + i);
}
System.out.println();
int[] arr1 = { 3, 5, 6, 9, 7, 12 };
change(arr1, OPER_TYPE.THREE);
for (int i : arr1) {
System.out.print(" " + i);
}
System.out.println();
int[] arr2 = { -3, 5, -6, 9, -7, 12 };
change(arr2, OPER_TYPE.PLUSORMINUS);
for (int i : arr2) {
System.out.print(" " + i);
}
}
public static void change(int[] arr, OPER_TYPE type) {
if (arr.length == 0 || arr == null) {
return;
}
int left = 0;
int right = arr.length - 1;
while (left < right) {
//
while (left < right && func(type, arr[left])) {
left++;
}
//
while (right > left && !func(type, arr[right])) {
right--;
}
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
}
private static boolean func(OPER_TYPE type, int num) {
if (type == null) {
return true;
}
switch (type) {
case ODDEVEN:
return funcODDEVEN(num);
case PLUSORMINUS:
return PLUSORMINUS(num);
case THREE:
return THREE(num);
default:
return true;
}
}
private static boolean THREE(int num) {
return num % 3 == 0 ? true : false;
}
private static boolean PLUSORMINUS(int num) {
return num < 0 ? true : false;
}
private static boolean funcODDEVEN(int num) {
return (num & 1) == 1 ? true : false;
}
}
// 定义枚举的意义:让功能选择更加严格化
enum OPER_TYPE {
ODDEVEN("奇偶"), PLUSORMINUS("正负"), THREE("3整除");
private String type;
OPER_TYPE(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
在上面的代码中,func方法已经很好地解决了面试官所关心的问题,我们只需要传一个功能选项(枚举),就可以让方法知道当前我们的意愿,并调用那些封装好的方法,这样就做到了程序的可扩展性!