zoukankan      html  css  js  c++  java
  • 21 调整数组的顺序使奇数位于偶数的前面 (第3章 高质量的代码-代码的完整性)

    题目描述:(题目一)

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

    测试用例:  

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

    特殊输入测试:(输入为nullptr;输入的数组只包含一个数字)

    解题思路:

    1) 初级程序员:维护两个指针,一个指向数组的第一个数字,一个指向数组的最后一个数字;两者向中间移动,第一个指针寻找偶数,第二个指针寻找奇数,将两者内容交换。(快速排序算法)

    class Solution {
    public:
        void reOrderArray(vector<int> &array) {
            if(array.empty())
                return;
            int front = 0;
            int back = array.size()-1;
            while(front<back){
                //寻找偶数
                while(front<back && (array[front]&0x1)!=0)//不是偶数时,寻找下一个   error:(array[front]&0x1!=0)  因为!=优先级高于&
                    ++front;
    
                //寻找奇数
                while(front<back && (array[back]&0x1)!=1) //不是奇数时,寻找下一个
                    --back;
                    
                if(front<back){
                    //交换
                    array[front] = array[front] + array[back];
                    array[back] = array[front] - array[back];
                    array[front] = array[front] - array[back];
                    //++front;
                    //--back;
                }
            }
        }
    };  

     注意:!=优先级高于& 因此判断是要对(array[front]&0x1)加括号,否则在寻找奇数时,会先计算0x1  !=1 永远不成立,因此永远不会进入到循环内

    2)高级程序员:写成一些列同类型的通用写法

    -- 题目改成把数组中的数按照大小分为两部分,所有负数都在非负数的前面。

    -- 能被3整除的数都在不能被3整除的数的前面。

    上述问题都只要修改第二个while跟第三个while的的判断条件即可

    通用的解决方法:把判断的标准变成一个函数指针,也就是用一个单独的函数来判断数字是否符合标准。

    class Solution {
    public:
        void reOrderArray(vector<int> &array, bool (*func)(int)) {  
            if(array.empty())
                return;
            int front = 0;
            int back = array.size()-1;
            while(front<back){
                //寻找偶数
                while(front<back && !func(array[front]) )//不是偶数时,寻找下一个   error:(array[front]&0x1!=0)  因为!=优先级高于&
                    ++front;
    
                //寻找奇数
                while(front<back && func(array[front]) ) //不是奇数时,寻找下一个
                    --back;
                    
                if(front<back){
                    //交换
                    array[front] = array[front] + array[back];
                    array[back] = array[front] - array[back];
                    array[front] = array[front] - array[back];
                    //++front;
                    //--back;
                }
            }
        }
        bool isEven(int n){
            return (n&1)==0; //偶数返回true
        }
        
    };  
    
    //调用函数方法:
    reOrderArray( array, isEven);  //第二个参数,可以输入别的判断函数             


    题目描述:(题目二)

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

    测试用例: 

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

    特殊输入测试:(输入为nullptr;输入的数组只包含一个数字)

    解题思路:

    1)使用vector内置的函数erase与push_back,从左到右遍历,当遇到偶数时,将其在原位置删除并在vector的后面追加。

    class Solution {
    public:
        void reOrderArray(vector<int> &array) {
            //if(array.empty())
                //return;
            vector<int>::iterator even = array.begin();
            int size = array.size();
            while (size)
            {
                if ((*even &01) == 0)
                {
                    int tmp = *even;
                    even = array.erase(even);   //使用erase函数
                    array.push_back(tmp);
                }else{
                    even++;
                }
                size--;
            }
        }
    };  

     注意:该方法的效率主要取决于erase的效率。

    erase()函数删除当期位置,然后将后面的数据前移   效率较低。

    2)类似冒泡算法,前偶后奇数就交换

    class Solution {
    public:
        void reOrderArray(vector<int> &array) {
            for (int i = 0; i < array.size();i++) //每一次i循环,可以确定i位置的奇数,离i最近的奇数会传递到i位置
            {
                for (int j = array.size() - 1; j>i;j--)//j每次都从尾部遍历到i后面
                {
                    if ((array[j] & 0x1) == 1 && (array[j - 1] & 0x1) == 0) //前偶后奇交换
                    {
                        //交换两个值swap(array[j], array[j-1]);
                        array[j] = array[j] + array[j-1];
                        array[j-1] = array[j] - array[j-1];
                        array[j] = array[j] -  array[j-1];
                    }
                }
            }
        }
    };
    

    3)使用stable_partition函数:

    //实现1 判断函数定义在类外部
    bool isOk(int n){ return ((n & 1) == 1); //奇数返回真 } class Solution { public: void reOrderArray(vector<int> &array) { stable_partition(array.begin(),array.end(),isOk); } };
    //实现2 判断函数定义在类内部
    class Solution { public: void reOrderArray(vector<int> &array) { stable_partition(array.begin(),array.end(),isOk); } static bool isOk(int n){ //定义在类内,要加static,否则类内会报错。与*this指针有关 return ((n & 1) == 1); //奇数返回真 } };
    //实现3 lambda表达式
    class Solution { public: void reOrderArray(vector<int> &array) { stable_partition(array.begin(), array.end(), [](int i){return (i & 1) == 1;}); //奇数返回真 } };  

    注:

    [1] stable_partiton(first,last,compare);//first为容器的首迭代器,last为容器的末迭代器,compare为比较函数(可略写)。1 2 3 4 5 序列,用partition函数 那么输出不一定是:1 3 5 2 4 也有可能是:3 5 1 4 2。如果使用stable_partition函数(稳定整理算法),那么输出一定就是:1 3 5 2 4 

    [2]  stable_partition函数源码其实是开辟了新的空间,然后把符合条件的放在新空间的前面,其他的放在后面。  代码中看起来时在原array上进行的操作

    [3] 可以向一个算法传递任何类别的可调用对象。可调用对象有函数,函数指针,其实还有重载了函数调用运算符的类,和lambda表达式。 

     -- lambda表达式 : 用于定义并创建匿名的函数对象,以简化编程工作。

     -- 语法形式:

    [capture list] (params list) mutable exception-> return type { function body }
    
    各项具体含义如下
    1. capture list:捕获外部变量列表
    2. params list:形参列表
    3. mutable指示符:用来说用是否可以修改捕获的变量
    4. exception:异常设定
    5. return type:返回类型
    6. function body:函数体
    
    可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种
    //省略 mutable指示符、exception:异常设定
    1. [capture list] (params list) -> return type {function body}
    格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值
    //省略返回类型及mutable指示符、exception:异常设定
    2. [capture list] (params list) {function body}
    格式2省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型: (1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定; (2):如果function body中没有return语句,则返回值为void类型。
    3. [capture list] {function body}
    格式3中省略了参数列表,类似普通函数中的无参函数。
    
    示例:
    [] (int x, int y) { return x + y; } // 隐式返回类型
    [] (int& x) { ++x;  } // 没有 return 语句 -> Lambda 函数的返回类型是 'void'
    [] () { ++global_x;  } // 没有参数,仅访问某个全局变量
    [] { ++global_x; } // 与上一个相同,省略了 (操作符重载函数参数)
    //显示指定返回类型
    [] (int x, int y) -> int { int z = x + y; return z; }
    

    4)新建数组,遍历两遍,先存奇数,再存偶数

    class Solution {
    public:
        void reOrderArray(vector<int> &array) {
            vector<int> res;
            //存奇数
            for(int i = 0; i < array.size(); i++)
            {
                if((array[i] & 1) == 1)
                    res.push_back(array[i]);
            }
            //存偶数
            for(int i = 0; i < array.size(); i++)
            {
                if(array[i] % 2 == 0)
                    res.push_back(array[i]);
            }
            //array.swap(res); //swap的效率?
            array = res; //或者用 swap        
        }  

    注:以后判断奇偶性,不要用  array[i] % 2 == 1 因为不仅效率低,而且当array[i] 为负数,比如-1%2=-1 并不等于1。

    用空间换时间,存偶数与存奇数分别是O(n),最后array=res;的操作,时间复杂度是O(1)么?  

    5)插入排序算法

    相对位置不变--->保持稳定性;奇数位于前面,偶数位于后面 --->存在判断,挪动元素位置;

    这些都和内部排序算法相似,考虑到具有稳定性的排序算法不多,例如插入排序,归并排序等;这里采用插入排序的思想实现。
    //java 
    public class Solution {
        public void reOrderArray(int [] array) {
            //相对位置不变,稳定性
            //插入排序的思想
            int m = array.length;
            int k = 0;//记录已经摆好位置的奇数的个数
            for (int i = 0; i < m; i++) {
                //将找到的奇数移动到已确定的k的后面,k++,可以确定k+1个
                if (array[i] % 2 == 1) { 
                    int j = i;
                    while (j > k) {//j >= k+1
                        int tmp = array[j];
                        array[j] = array[j-1];
                        array[j-1] = tmp;
                        j--;
                    }
                    k++;
                }
            }
        }
    }  

    6)归并排序 (快速排序是不稳定的,这里不能使用)归并时间复杂度是nlogn,需要额外的O(N)空间

    //java  该算法过于麻烦   待考虑
    public class Solution {
        public void reOrderArray(int [] array) {   
            //用用归并排序的思想,因为归并排序是稳定的
            int length = array.length;
            if(length == 0){
                return;
            }
            int[] des = new int[length];
            MergeMethod(array, des, 0,length - 1);
        }
    
        public void MergeMethod(int[] array, int [] des, int start, int end){
            if(start < end){
                int mid = (start + end) / 2;
                MergeMethod(array, des, start, mid);  //前半部分
                MergeMethod(array, des, mid + 1, end);  //后半部分
                Merge(array, des, start, mid, end);
            }
        }
        
        public void Merge(int[] array, int[] des, int start, int mid, int end){
            int i = start;
            int j = mid + 1;
            int k = start;
            while(i <= mid && array[i] % 2 == 1){
                des[k++] = array[i++];
            }
            while(j <= end && array[j] % 2 == 1){
                des[k++] = array[j++];
            }
            while(i <= mid){
                des[k++] = array[i++];
            }
            while(j <= end){
                des[k++] = array[j++];
            }
            
            for(int m = start; m <= end; m++){
                array[m] = des[m];
            }
        }
    }
    

    7)新开数组空间换时间的解法:

        a.遍历数组,如果是奇数从头部放入到原数组中,并记录指针
        b.如果是偶数,放入到新数组中,并记录指针
        c.将新数组的元素安顺序,从最后一个奇数后边插入到原数组中

      

  • 相关阅读:
    Delphi中字符串默认以#开头。 dotNET界面
    生成飞库jar手机电子小说ByC# dotNET界面
    CS0016: Could not write to output file 'c:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/Temporary ASP.NET Files/ '拒绝访问' dotNET界面
    SVN备份和还原操作特指Window下使用Visual SVN dotNET界面
    生成飞库jar手机电子小说ByC#[2] dotNET界面
    写了个类似按键精灵的找图类。方便大家做UI测试的时候可以用 dotNET界面
    各种中间件
    聊聊位运算吧
    聊聊设计模式
    腾讯云容器服务(TKE集群)新版本config取不到token问题
  • 原文地址:https://www.cnblogs.com/GuoXinxin/p/10441992.html
Copyright © 2011-2022 走看看