题目

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

分析

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

解法一

书中作者提到一种初始的做法是,从头扫描整个数组,如果遇到偶数,则拿出这个数,并且把整个数组的数据都向前挪动一位,再把拿出的数放到末尾。每碰到一个偶数就需要移动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. 解决 win10飞行模式 无限自动开关 无法关闭

    驱动问题,名为“Insyde Airplane Mode HID Mini-Driver”的驱动,这个驱动是专门用来快捷管理飞行模式的. 卸载完成后重启,无限开关飞行模式问题得到解决!

  2. Python:如何用一行代码获取上个月是几月

    现在转一篇志军100发于公众号 Python之禅的文章: Python:如何用一行代码获取上个月是几月 抱歉我用了个有点标题党的标题,因为担心你错过了本文,但内容绝对干货,本文介绍的关于Python时 ...

  3. 前端技术大行其道,再不拥抱TypeScript你就老了!

    本篇文章旨在介绍下TypeScript这门新兴的程序设计语言,不需要你有多么强的专业知识,只需要你有半支烟的功夫. 好了,废话不多说,赶紧进入正题.为什么你需要拥抱TypeScript?那么你首先需要 ...

  4. RocketMQ4.4.0新特性分享

    rocketmq1.架构 MQ历史 由数据结构队列发展而来 MQ使用场景 异步处理 解耦 削峰填谷 数据同步2.队列3.使用 生产 同步(sync) 默认重试2次总共3次 默认等待超时时间为3s 异步 ...

  5. 将Python 程序打包成 .exe格式入门

    PyInstaller PyInstaller 是一个十分有用的第三方库,可以用来打包 python 应用程序,打包完的程序就可以在没有安装 Python 解释器的机器上运行了. 它能够在 Windo ...

  6. java SE Development kit 8 Update 201 (64-bit) 的安装相关

    登录http://www.oracle.com,下载JDK(J2SE) JDK 1.0,1.1,1.2,1.3,1.4 1.5 (JDK5.0) à支持注解.支持泛型   1.6(JDK6.0) à ...

  7. 【深度学习篇】--Seq2Seq模型从初识到应用

    一.前述 架构: 问题: 1.压缩会损失信息 2.长度会影响准确率 解决办法: Attention机制:聚焦模式 “高分辨率”聚焦在图片的某个特定区域并以“低分辨率”,感知图像的周边区域的模式.通过大 ...

  8. .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9977862.html 写在前面 千呼万唤始出来,首先,请允许我长吸一口气!真没想到一份来自28岁老程序员 ...

  9. JSP 内置对象(上)

    JSP 内置对象是 Web 容器创建的一组对象,不使用 new 关键字就可以直接使用的对象.如上一章中使用脚本实现打印九九乘法表中的out对象 <%-- 脚本:out对象是JSPWriter类的 ...

  10. 多线程协作wait、notify、notifyAll方法简介理解使用 多线程中篇(十四)

    在锁与监视器中有对wait和notify以及notifyAll进行了简单介绍 所有对象都有一个与之关联的锁与监视器 wait和notify以及notifyAll之所以是Object的方法就是因为任何一 ...