zoukankan      html  css  js  c++  java
  • 剑指offer:调整数组顺序使奇数位于偶数前面

    题目

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

    分析

    事实上,这个题比较简单,很多种方式都可以实现,但是其时间复杂度或空间复杂度不尽相同。

    解法一

    书中作者提到一种初始的做法是,从头扫描整个数组,如果遇到偶数,则拿出这个数,并且把整个数组的数据都向前挪动一位,再把拿出的数放到末尾。每碰到一个偶数就需要移动O(N)次,这样总的时间复杂度为O(n^2),空间复杂度为O(1)

    这种方式很简单,如果已经很清楚是怎么回事,可以跳过例子说明,继续阅读下一个解法。但是可以尝试自己写一下代码,发现有些细节部分并不是那么容易写出来

    举个例子,假设有数据1,2,3,4,5,6:

    从左往右扫描,找到第一个偶数2,并临时保存:

          
    1 2 3 4 5 6
             
      取出        

    将2后面的所有数往前移动一个位置,并将2放到最后一个位置:

          
    1 3 4 5 6 2
            移动后

    继续扫描当前位置,发现3为奇数,继续,发现4为偶数,将从3之后位置的数开始,到倒数第二个位置,所有数往前移动一个位置,并将4放到最后:

          
    1 3 5 6 4 2
          移动后  

    继续扫描当前位置数5,6,至此,偶数有2两个,当前指向位置为,所在下标为4,总数 - 位置 <= 偶数 ,结束。

          
    1 3 5 6 4 2
             

    根据该思路,C语言代码实现如下:

    //reorder.c
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <string.h>
    void reorder(int arr[],int len)
    {
        if(NULL == arr || 0 == len)
            return;
        /*统计偶数数量,减少移动次数*/
        int evenNum = 0;
        int loop = 0;
        int temp;
        int inLoop = 0;
        while(loop < len)
        {
            /*如果是偶数,则需要开始移动*/
            temp = arr[loop];
            /*如果已经达到了*/
            if(0 == (temp & 1) && (len - loop  > evenNum))
            {

                /*从当前位置开始移动,直到遇到剩下的数量是偶数的个数*/
                for(inLoop = loop + 1;inLoop < len - evenNum;inLoop++)
                {
                    arr[inLoop-1] = arr[inLoop];
                }
                /*把空出来的位填充上*/
                arr[len  - evenNum - 1] = temp;
                evenNum++;

                /*交换后继续*/
                continue;
            }
            /*继续循环下一个*/
            else        
            {        
                loop++;
            }


        }
    }

    解法二

    很多人其实最先想到的解法可能是,创建一个新的数组,从头扫描,遇到偶数放后边,遇到奇数放前边。扫描结束后,再将数组内容拷贝到原数组,这样整个时间复杂度为(n),而空间复杂度也为O(n),这样的方法实现简单,也不容易出错。C语言代码实现如下:

    //reorder1.c
    void reorder(int arr[],int len)
    {
        if(NULL == arr || 0 == len)
            return;
        /*记录奇偶的数量*/
        int oddNum = 0;
        int evenNum = 0;
        int loop = 0;
        /*创建一个新的数组*/
        int *temp = malloc(len * sizeof(int));
        if(NULL == temp)
        {
            printf("malloc memory failed ");
            return;
        }
        /*拷贝数组,并遍历*/
        memcpy(temp,arr,sizeof(int)*len);
        for(;loop < len;loop++)
        {
            /*偶数放到数组末尾*/
            if(0 == (temp[loop] & 1))
            {
                arr[len-1-evenNum] = temp[loop];
                evenNum++;
            }
            /*奇数放到数组末尾*/
            else
            {
                arr[oddNum] = temp[loop];
                oddNum++;
            }
        }
        free(temp);
    }

    解法三

    还记得我们之前介绍过的《快速排序优化详解》吗?快速排序中,有一个分区操作,是将整个数组大于等于基准的部分放右边,而小于等于基准的部分放左边,即根据基准,将数组一分为二。其实在这里,同样可以参考这个思路,只不过跟基准比大小,变成了判断是奇还是偶。
    这里简单描述一下该思路,更多细节可以参考《快速排序优化详解》中如何将元素移动到基准两侧一节:

    • 定义下标i和j,分别从开头和结尾开始扫描
    • 当i遇到偶数时,停止扫描
    • 当j遇到奇数时,停止扫描
    • 此时交换i和j位置的值
    • 继续前面的操作,直到i和j交错或相等

    举个例子,假设有数据1,2,3,4,5,6,7,8:

            
    1 2 3 4 5 6 7 8
               
    i             j

    i和j继续扫描,i遇到2停止,j遇到5停止,交换两处的值:

            
    1 7 3 4 5 6 2 8
               
      i         j  

    i和j继续扫描,i遇到4停止,j遇到5停止,交换两处的值:

            
    1 7 3 5 4 6 2 8
               
          i j      

    继续扫描,此时,i和j交错,扫描结束:

            
    1 7 3 5 4 6 2 8
               
          j i      

    基于该思路的算法时间复杂度为O(n),空间复杂度为O(1),C语言代码实现如下:

    //reorder2.c
    void reorder(int arr[],int len)
    {
        if(NULL == arr || 0 == len)
            return;
        int i = 0;
        int j = len - 1;
        int temp;
        for(;;)
        {
            /*i j分别向右和向左移动,i遇到偶数停止,j遇到奇数停止?*/
            while(1 == (arr[i] & 1))
            {
                i++;
            }
            while(0 == (arr[j] & 1))
            {
                j--;
            }
            if(i < j)
            {
                temp = arr[i];
                arr[i] = arr[j];
                arr[j] = temp;
            }
            /*交错时停止*/
            else
            {
                break;
            }

        }
    }

    运行效率比较

    编译后,对一百万数据进行操作,运行时间结果如下。
    解法一:

    time ./reorder 1000000

    并没有耐心等到结果出来。
    解法二:

    $ time ./reorder1 100000000
    reorder for 100000000 numbers
    before reorder:too much,will not print
    after  reorder:too much,will not print

    real    0m2.425s
    user    0m2.141s
    sys    0m0.284s

    对1亿数据进行操作,耗时很短,只是内存占用较多。
    解法三:

    $ time ./reorder2 100000000
    reorder for 100000000 numbers
    before reorder:too much,will not print
    after  reorder:too much,will not print

    real    0m2.146s
    user    0m2.018s
    sys    0m0.128s

    耗时很短,内存占用少。

    微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。

    扩展

    在本题中,只是对整数是奇还是偶进行分开,那么如果是别的条件呢?例如是否为素数,是否为正数等等。我们可以让调用者传入一个条件函数,让它决定到底是放在后半部分,还是前半部分。这是不是很向库函数qsort需要传入一个比较函数的做法?这部分内容可以参考《函数指针》,根据这个思路,我们修改解法三的代码:


    这个时候通过传入函数指针,可以对任意条件进行处理了。

    总结

    我们发现,一些基本算法的思想,通常可以用到其他问题上,而不同的思路,带来的效率可能天差地别。

    练习

    尝试自己实现上面的算法。如果需要保证奇数或偶数之间的相对位置不变,该如何修改?

    备注

    完整代码以及模拟一亿数据处理请访问:剑指offer:调整数组顺序使奇数位于偶数前面

    微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。

  • 相关阅读:
    zoj3430Detect the Virus(ac自动机)
    zoj3494BCD Code(ac自动机+数位dp)
    hdu3247Resource Archiver(ac自动机+spfa)
    hdu3341Lost's revenge(ac自动机+dp)
    hdu4511小明系列故事——女友的考验(ac自动机+最短路)
    hdu4758Walk Through Squares(ac自动机+dp)
    数论一
    母函数专题
    KMP专题
    3级算法题
  • 原文地址:https://www.cnblogs.com/bianchengzhuji/p/10458251.html
Copyright © 2011-2022 走看看