數字(int)轉字符串和字符串轉數字(int)

室友去面試,問了一個字符串轉成數字的算法題,室友沒搞出來,我心想,這個不是很簡單的嗎?因而動手在紙上畫了畫代碼。畫完後,總感受哪裏不對,最後一個個挖掘,才發現,尼瑪,這處處都是坑啊~~~特此記錄一下中坑心路。git

1. 數字轉字符串

首先看一下數字轉成字符串。輸入一個整型數字,寫一個函數,返回整型數字對應的字符串形式。如:github

輸入:345
輸出:"345"

這個問題第一思路應該是:對整型數字每次求最高位數字,如3,將其轉換爲對應字符 '3' ,而後將此整型值取下面的數,直到整型值爲0,輸出字符串。這個問題是,怎麼求最高位的數字,能夠用一個循環將number累除10,直到number小於10,即爲最高位。解法以下:面試

  • 代碼A
char * int2Str(int nb){
    char * str= new char[12];//整型最長11位
    str[11]='\0';
    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]='\0';
        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]='\0'; //添加結束符號
    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]='\0';
    unsigned int nindex=0;
    if(nb<0){
        str[0]='-';
        nb*=-1;     //轉換爲正數
        nindex=1;
    }else if(0==nb){
        str[0]='0';
        str[1]='\0';
        return str;
    }
    unsigned int len=0; //記錄nb的位數
    int tmpNb=nb;
    while(0!=tmpNb){
        ++len;
        tmpNb/=10;
    }
    if(nindex==0)--len; //在str中定位最後一位數字應該在的位置
    str[len+1]='\0';    //設置結束符號
    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]='\0';
    unsigned int nindex=0;
    if(nb<0){
        str[0]='-';
        nb*=-1;     //轉換爲正數
        nindex=1;
    }else if(0==nb){
        str[0]='0';
        str[1]='\0';
        return str;
    }
    unsigned int nstar=nindex; //記錄要逆序的初始位置
    while(0!=nb){
        str[nindex++]=nb%10+'0';
        nb/=10;
    }
    str[nindex]='\0';
    //字符串逆序
    --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,因此,這個誰更好,就難說了~~(不知道這個分析的是否有問題~~~)spa

總結一下主要的坑:

  • 負數時返回的字符串第一位要有 '-' 號,正數從人的角度上考慮不應加 '+'
  • 轉換的字符串在結尾要有 '\0',不然可能出錯

字符串轉數字

這個其實就是實現一下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=='\0')return 0; //指針不爲空,字符串不爲空
    if(*str!='+' && *str!='-' && (*str<'0' && *str>'9'))return 0;
    if(*str=='+' || *str=='-'){     //只有 "+" 和 "-" 或 '+'/'-'後跟的不是數字
        if(*(str+1)=='\0' || (*(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流也能將下面的輸入正確輸出:code

輸入: "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=='\0')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!='\0'){        //防止字符串爲"+",或 "-"的狀況
        while(*pstr!='\0'){ //循環求數字
            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=='\0') g_status=kValid;    //直達字符串末尾,說明輸入合法
    }
    return (int)num;    //將 dlong 型強制轉換爲 int
}

至於思路,就是先去除非法輸入部分,而後一個數字一個數字的轉換爲對應字符。註釋裏說的都已經很清楚了,因此就很少說了。

總結一下主要的坑:

  • 怎麼返回非法輸入的結果,而且怎麼進行區分
  • 轉換爲整型後,可能會溢出,怎麼進行溢出判斷
  • 非法輸入:
    • 空指針,空字符串
    • 只有一個正負號,正負號不是出如今第一個位置
    • 字符串中含有非正負號和數字的其餘字符

3. 最後上代碼文件

下載所有代碼

附錄

相關文章
相關標籤/搜索