• 数字(int)转字符串和字符串转数字(int)


    室友去面试,问了一个字符串转成数字的算法题,室友没搞出来,我心想,这个不是很简单的吗?于是动手在纸上画了画代码。画完后,总感觉哪里不对,最后一个个挖掘,才发现,尼玛,这到处都是坑啊~~~特此记录一下中坑心路。 ##1. 数字转字符串 首先看一下数字转成字符串。输入一个整型数字,写一个函数,返回整型数字对应的字符串形式。如:

    输入:345
    输出:"345"
    

    这个问题第一思路应该是:对整型数字每次求最高位数字,如3,将其转换为对应字符 '3' ,然后将此整型值取下面的数,直到整型值为0,输出字符串。这个问题是,怎么求最高位的数字,可以用一个循环将number累除10,直到number小于10,即为最高位。解法如下:

    • 代码A
    char * int2Str(int nb){
        char * str= new char[12];//整型最长11位
        str[11]='';
        unsigned int nindex=0;
        if(nb<0){       //负数时的情况
            str[0]='-';
            nb*=-1;     //转换为正数
            ++nindex;   //第0位记录了符号'-',所以下移一位开始记录数字
                        //如果是正数和0的情况,直接从第0位开始记录数字
        }else if(0==nb){    //0时的情况
            str[0]='0';
            str[1]='';
            return str;
        }
        int tmpNum,len;
        while(nb!=0){
            len=1;
            tmpNum=nb;
            while(tmpNum>=10){ 
                len*=10;        //记录最高位的位度
                tmpNum/=10;     //循环累除10求最高位数字
            }
            str[nindex++]=tmpNum+'0';
            nb=nb-tmpNum*len;           //nb 减去最高位
            if(nb==0){  //判断最后一位是否为 0 的情况
                str[nindex++]='0';
                break;
            }
        }
        str[nindex]=''; //添加结束符号
        return str;
    }
    
    

    在代码A中考虑了所有可能出现的情况,如:整数nb为0或负数时的情况,整数nb的最后一位为0时的情况(仅限此种思路的情况)。可以看到,算法主要的计算部分在两个while循环中,假设nb共有M位。则第一个循环需要循环M次,第二个循环总循环次数为:((M-1)+(M-2)+...+3+2+1=M(M-1)/2) ,总共的循环次数为$(M-1)M/2$,时间复杂度为:(O(M^2))。当然,由于整型数字,最长才只有10位(不包含正负号),所以常数时间内即可解决。但有没有其他解法了呢?

    如果我们从低位开始转换进字符数组里,而不是从高位开始,那不就能省掉第二个while循环了嘛!问题是,我们一开始并不知道整数nb有多少位!所以我们转换的低位放到字符数组str的哪里呢?这里有两种思路:

    • 先求出整型数字nb的位数M,然后将转换后的字符直接存进str对应的位置。如代码B
    • 将nb从低位开始转换的字符依次从前向后存入str,即str中存入的其实是要输出字符串的逆串,然后再将字符串给正过来,即求逆串。如代码C
    • 代码B
    char * int2StrB(int nb){
        char * str= new char[12];//整型最长带上符号共11位
        str[11]='';
        unsigned int nindex=0;
        if(nb<0){
            str[0]='-';
            nb*=-1;     //转换为正数
            nindex=1;
        }else if(0==nb){
            str[0]='0';
            str[1]='';
            return str;
        }
        unsigned int len=0; //记录nb的位数
        int tmpNb=nb;
        while(0!=tmpNb){
            ++len;
            tmpNb/=10;
        }
        if(nindex==0)--len; //在str中定位最后一位数字应该在的位置
        str[len+1]='';    //设置结束符号
        while(0!=nb){
            str[len--]=nb%10+'0';
            nb/=10;
        }
        return str;
    }
    

    代码B中,首先一个循环求出整数nb(如果nb为负数,此时已经转换为对应的正数)总共位数M,需要循环M次,时间复杂度为$O(M)$;第二个循环依然是依次遍历整数nb的每一位,时间复杂度依然为$O(M)$,所以总时间复杂度为:(O(M)+O(M)=O(M))。当然,其实M是有最大数限制的。

    • 代码C
    char * int2StrC(int nb){
        char * str= new char[12];//整型最长带上符号共11位
        str[11]='';
        unsigned int nindex=0;
        if(nb<0){
            str[0]='-';
            nb*=-1;     //转换为正数
            nindex=1;
        }else if(0==nb){
            str[0]='0';
            str[1]='';
            return str;
        }
        unsigned int nstar=nindex; //记录要逆序的初始位置
        while(0!=nb){
            str[nindex++]=nb%10+'0';
            nb/=10;
        }
        str[nindex]='';
        //字符串逆序
        --nindex;
        while(nstar<nindex){
            char tmp=str[nstar];
            str[nstar]=str[nindex];
            str[nindex]=tmp;
            ++nstar;
            --nindex;
        }
        return str;
    }
    
    

    代码C中也有两个循环,第一个循环完成将整数nb从低位到高位逆序转换进str中,需要时间复杂度为$O(M)$;第二个循环将对应数字的部分进行逆序,时间复杂度为$O(M/2)=O(M)$,所以总时间复杂度也为:(O(M))

    至于B和C哪个更好,我是建议用B的,简洁明了。至于谁更快写,肯定都比A快,其次由于M是一常数,自然(M+M)>(M+M/2)的,但是在C中比B中的循环多出三条赋值操作,因为M不会大于11,所以,这个谁更好,就难说了~~(不知道这个分析的是否有问题~~~)

    ###总结一下主要的坑:

    • 负数时返回的字符串第一位要有 '-' 号,正数从人的角度上考虑不该加 '+'
    • 转换的字符串在结尾要有 '',否则可能出错

    ##字符串转数字 这个其实就是实现一下C库函数的atoi函数。当然我们有个简单的处理,就是使用C++的stringstream类,代码如下:

    • 代码D
    #include <sstream>
    
    typedef long long dlong;
    
    enum Status{kInvalid=0,kValid};
    int g_status=kValid;    //合法输入
    
    int    str2IntA(const char* str){
        g_status=kInvalid;
        if(str==nullptr || *str=='')return 0; //指针不为空,字符串不为空
        if(*str!='+' && *str!='-' && (*str<'0' && *str>'9'))return 0;
        if(*str=='+' || *str=='-'){     //只有 "+" 和 "-" 或 '+'/'-'后跟的不是数字
            if(*(str+1)=='' || (*(str+1)<'0' && *(str+1)>'9'))return 0;
        }
        stringstream stream(str);
        dlong num=INT_MAX+10; //大于整型最大数,判断溢出
        stream>>num;
        if(num>INT_MAX || num<INT_MIN)return 0;
        g_status=kValid;
        return (int)num;
    }
    

    代码D中,首先我们要处理的一个问题就是,当输入的字符串非法时,返回什么?返回0吗?但返回0怎么区分这个0不是合法输入返回的呢,所以要引入一个全局变量,当输入非法的时候将全局变量置为非法,否则置为合法。注释中已经说明了可能的非法输入情况,这里就不在多说。但是stringstream流也能将下面的输入正确输出:

    输入: "234dsdf"
    输出:234
    

    而我们知道,这其实输入一个非法的输入。当然我们可以更改代码进行遍历去判断这个,但其实如果在面试中,我觉得考官应该不是希望我们使用这个库函数的。而应该是去重新实现C版本的那个atoi函数。所以上代码E:

    • 代码E
    typedef long long dlong;
    
    enum Status{kInvalid=0,kValid};
    int g_status=kValid;    //合法输入
    
    int    str2IntB(const char* str){
        g_status=kInvalid;
        if(str==nullptr || *str=='')return 0; //指针不为空,字符串不为空
        char const* pstr=str;
        int flag=1;                 //判断正负数,默认为正数
        dlong num=0;            //防溢出
        if(*pstr=='-')flag=-1;      //负数
        else if(*pstr=='+') flag=1; //正数
        else if(*pstr<'0' && *pstr>'9')return 0;//非法输入
        else num=(*pstr-'0')*flag;  //上来就是数字,为正数
        ++pstr;
        if(*pstr!=''){        //防止字符串为"+",或 "-"的情况
            while(*pstr!=''){ //循环求数字
                if(*pstr>='0' && *pstr<='9'){
                    num=num*10+(*pstr-'0')*flag;
                    if((flag==1 && num>INT_MAX) ||
                       (flag==-1 && num<INT_MIN))   //防溢出
                        return 0;//溢出
                    ++pstr;
                }else return 0; //非法输入
            }
            if(*pstr=='') g_status=kValid;    //直达字符串末尾,说明输入合法
        }
        return (int)num;    //将 dlong 型强制转换为 int
    }
    
    

    至于思路,就是先去除非法输入部分,然后一个数字一个数字的转换为对应字符。注释里说的都已经很清楚了,所以就不多说了。

    ###总结一下主要的坑:

    • 怎么返回非法输入的结果,并且怎么进行区分
    • 转换为整型后,可能会溢出,怎么进行溢出判断
    • 非法输入:
      • 空指针,空字符串
      • 只有一个正负号,正负号不是出现在第一个位置
      • 字符串中含有非正负号和数字的其他字符

    ##3. 最后上代码文件 下载全部代码

    ##附录

  • 相关阅读:
    小白的springboot之路(三)、集成mybatis与MySQL
    小白的springboot之路(四)、mybatis-generator自动生成mapper和model、dao
    springboot打印SQL及多环境配置
    小白的springboot之路(二)、集成swagger
    小白的springboot之路(一)、环境搭建、第一个实例
    npm切换成淘宝镜像源
    无废话设计模式(0)--设计模式的六大原则
    java之--加密、解密算法
    【Java】Java中的final关键字和static
    Add an Action that Displays a Pop-up Window 添加显示弹出窗口按钮
  • 原文地址:https://www.cnblogs.com/lomper/p/4421154.html
走看看 - 开发者的网上家园