题目

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

分析

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

解法一

书中作者提到一种初始的做法是,从头扫描整个数组,如果遇到偶数,则拿出这个数,并且把整个数组的数据都向前挪动一位,再把拿出的数放到末尾。每碰到一个偶数就需要移动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\n");
        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++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。

剑指offer:调整数组顺序使奇数位于偶数前面的更多相关文章

  1. 剑指OFFER——调整数组顺序使奇数位于偶数前面

    输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变. 剑指offer书里的版本, ...

  2. 剑指Offer 调整数组顺序使奇数位于偶数前面

    题目描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变.     思路: ...

  3. 用js刷剑指offer(调整数组顺序使奇数位于偶数前面)

    题目描述 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变. 牛客网链接 js代码 ...

  4. 剑指offer--8.调整数组顺序使奇数位于偶数前面

    习惯了简单 ------------------------------------------------- 时间限制:1秒 空间限制:32768K 热度指数:422906 本题知识点: 数组 题目 ...

  5. 剑指Offer-13.调整数组顺序使奇数位于偶数前面(C++/Java)

    题目: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变. 分析: 这道题做法有很 ...

  6. 剑指offer 调整数组顺序使得奇数位于偶数前面

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public:     void  ...

  7. 【Java】 剑指offer(21) 调整数组顺序使奇数位于偶数前面

    本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集   题目 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇 ...

  8. 剑指Offer - 九度1516 - 调整数组顺序使奇数位于偶数前面

    剑指Offer - 九度1516 - 调整数组顺序使奇数位于偶数前面2013-11-30 02:17 题目描述: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部 ...

  9. 剑指Offer:调整数组顺序使奇数位于偶数前面【21】

    剑指Offer:调整数组顺序使奇数位于偶数前面[21] 题目描述 输入一个整形数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分. 解题分析 使用插 ...

随机推荐

  1. OGG-02803 Encountered a Data Guard role transition

    告警提示其实已经很明显了OGG-02803 Encountered a Data Guard role transition. Alter Extract to SCN 15,756,246 and ...

  2. Windows Server 2016-活动目录NTP时间同步

    在实际生产域环境下,往往会有很多跟时间不同步相关的问题,简单的说几种常见的情景:本地客户端时间与域控时间不统一导致无法加域:每次客户端电脑输入密码到进入桌面环境等N久:Skype for Busine ...

  3. WEB框架-Django框架学习-关联管理器(RelatedManager)

    一.class RelatedManager "关联管理器"是在一对多或者多对多的关联上下文中使用的管理器.它存在于下面两种情况: 1.一对多 ForeignKey关系的“另一边” ...

  4. 判断JS数据类型的四种方法

    在 ECMAScript 规范中,共定义了 7 种数据类型,分为 基本类型 和 引用类型 两大类,如下所示: 基本类型:String.Number.Boolean.Symbol.Undefined.N ...

  5. 吴恩达机器学习笔记61-应用实例:图片文字识别(Application Example: Photo OCR)【完结】

    最后一章内容,主要是OCR的实例,很多都是和经验或者实际应用有关:看完了,总之,善始善终,继续加油!! 一.图像识别(店名识别)的步骤: 图像文字识别应用所作的事是,从一张给定的图片中识别文字.这比从 ...

  6. 万能的Python,还能用来制作高大上的进度条?

    对于开发或者运维来说,使用Python去完成一些跑批任务,或者做一些监控事件是非常正常的情况.那么如何有效的监控任务的进度,除了在任务中加上log外,还能不能有另一种方式来了解任务进展到哪一步了呢? ...

  7. ASP.NET Core中使用GraphQL - 第一章 Hello World

    前言 你是否已经厌倦了REST风格的API? 让我们来聊一下GraphQL. GraphQL提供了一种声明式的方式从服务器拉取数据.你可以从GraphQL官网中了解到GraphQL的所有优点.在这一系 ...

  8. Python爬虫入门教程 43-100 百思不得姐APP数据-手机APP爬虫部分

    1. Python爬虫入门教程 爬取背景 2019年1月10日深夜,打开了百思不得姐APP,想了一下是否可以爬呢?不自觉的安装到了夜神模拟器里面.这个APP还是比较有名和有意思的. 下面是百思不得姐的 ...

  9. 【Android Studio安装部署系列】九、Android Studio常用配置以及快捷键

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 概述 整理Android Studio的常用配置和快捷键. 常用配置 显示行号 临时显示 永久显示 File——Settings——Edi ...

  10. Java8内存模型—永久代(PermGen)和元空间(Metaspace)

    一.JVM 内存模型 根据 JVM 规范,JVM 内存共分为虚拟机栈.堆.方法区.程序计数器.本地方法栈五个部分. 1.虚拟机栈:每个线程有一个私有的栈,随着线程的创建而创建.栈里面存着的是一种叫“栈 ...