剑指offer-3-面试14:调整数组顺序使奇数位于偶数前面

来源:互联网 发布:男士护肤推荐 知乎 编辑:程序博客网 时间:2024/06/05 20:39

  • 题目
  • 分析
    • 只完成基本功能的解法仅适用于初级程序员
    • 考虑可扩展性的解法能秒杀offer
  • 测试用例代码
  • 本题考点

题目

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

分析

如果不考虑时间复杂度,最简单的思路应该是从头扫描这个数组,每碰到一个偶数时,拿出这个数字,并把位于这个数字后面的所有数字往前挪动一位。挪完之后在数组的末尾有一个空位,这时把该偶数放入这个空位。由于每碰到一个偶数就需要移动O(n)个数字,因此总的时间复杂度是O(n^2)。但是,这种方法不能让面试官满意。不过如果我们在听到这个题目之后马上能说出这个解法,面试官至少会觉得我们的思维非常敏捷。

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

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

void ReorderOddEven_1(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 & 0x1) != 0)            pBegin ++;        // 向前移动pEnd,直到它指向奇数        while(pBegin < pEnd && (*pEnd & 0x1) == 0)            pEnd --;        if(pBegin < pEnd)        {            int temp = *pBegin;            *pBegin = *pEnd;            *pEnd = temp;        }    }}

考虑可扩展性的解法,能秒杀offer

如果是面试应届毕业生或者工作时间不长的程序员,面试官会满意前面的代码。但如果应聘者申请的是资深的开发职位,那面试官可能会接着问几个问题。

面试官: 如果把题目改成把数组中的数按照大小分为两部分,所有负数都在非负数的前面,该怎么做?
应聘者:可以重新定义一个函数,在新的函数里,只要修改第二个和第三个while循环中的判断条件就行了。
面试官:如果把题目再改改,变成把数组中的数分为两部分,能被3整除的数都在不能被3整除的数的前面。怎么办
应聘者:可以定义一个新的函数。在这个函数中。。。
面试官:(打断应聘者的话)难道就没有更好的办法?

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

回到面试官提出的两个问题上来。我们发现要解决这两个新的问题,其实只需要修改函数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)     {        // 向后移动pBegin        while(pBegin < pEnd && !func(*pBegin))            pBegin ++;        // 向前移动pEnd        while(pBegin < pEnd && func(*pEnd))            pEnd --;        if(pBegin < pEnd)        {            int temp = *pBegin;            *pBegin = *pEnd;            *pEnd = temp;        }    }}bool isEven(int n){    return (n & 1) == 0;}

在上面的代码中,函数Reorder根据func的标准把数组pData分成两部分:而函数isEven则是一个具体的标准,即判断一个数是不是偶数。有了这两个函数,我们可以很方便地把数组中所有奇数移到偶数的前面

void ReorderOddEven_2(int *pData, unsigned int length){    Reorder(pData, length, isEven);}

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

测试用例&代码

(1)功能测试(输入数组中的奇数、偶数交替出现,输入的数组中所有偶数都出现在奇数的前面,输入的数组中所有奇数都出现在偶数的前面)

(2)特殊输入测试(输入NULL指针、输入的数组只包含一个数字)

本题考点

(1)快速思维能力。要在短时间内按照要求把数组分隔成两部分,不是一件容易的事情,需要较快的思维能力

(2)对于一件工作几年的应聘者,面试官还将考察其对扩展性的理解,要求应聘者写出的代码具有可重用性。

0 0
原创粉丝点击