【剑指offer】题型总结 - 字符串

文章目录

  • 字符串
    • 05. 替换空格
      • 题意
      • 解法1 另创变量存储修改结果
        • 复杂度分析
      • 解法2 原地修改
        • 复杂度分析
    • 58 - II. 左旋转字符串
      • 题意
      • 解法 切片组合
        • 复杂度分析
    • 20. 表示数值的字符串
      • 题意
      • 解法1 暴力处理
        • 复杂度分析
      • 解法2 有限状态自动机
        • 复杂度分析
    • 67. 把字符串转换成整数
      • 题意
      • 解法 暴力遍历
        • 复杂度分析
    • 总结
      • A. `string` 的 `+` 与 `+=`(`+=` 要比 `+` 高效)
      • B. string 的常用方法
      • C. str 转 int 的越界判断
      • D. 枚举 - `enum`

字符串

05. 替换空格

题意

  • 将字符串中的空格转换为“%20”

解法1 另创变量存储修改结果

class Solution {
public:
    string replaceSpace(string s) {
        string res;
        for(int i=0; i<s.size(); i++)
        {
            if(s[i]==' ')
                res += "%20";
            else
                res += s[i];
        }
        return res;
    }
};

复杂度分析

时间复杂度 O(N):遍历字符串 s,每轮修改 res;
空间复杂度 O(N):创建一个新的字符串变量存储修改后的字符串。

解法2 原地修改

  • 首先遍历一遍字符串 s,获取空格的数量 space_cnt
  • 然后,修改字符串 s 的大小为 s.size() + space_cnt * 2
  • 最后,倒序遍历字符串 s,如果遇到的不是空格,则直接填充,如果遇到的是空格,则填充“%20”。
class Solution {
public:
    string replaceSpace(string s) {
        int space_cnt = 0;
        int old_size = s.size();
        
		// 获取空格的数量
        for(int i=0; i<s.size(); i++)
        {
            if(s[i] == ' ') space_cnt++;
        }
        
        // 修改字符串的长度
        s.resize(old_size + space_cnt * 2);
		
		// 倒序修改
        for(int i=s.size()-1, j=old_size-1; i>=0, j>=0; i--, j--)
        {
            if(s[j] == ' ')
            {
                s[i] = '0';
                s[i-1] = '2';
                s[i-2] = '%';
                i-=2;
            }
            else
            {
                s[i] = s[j];
            }
        }
        return s;
    }
};

复杂度分析

时间复杂度 O(N):遍历字符串 s;
空间复杂度 O(1):原地修改。


58 - II. 左旋转字符串

题意

  • 左旋字符串

解法 切片组合

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        string tmp1 = s.substr(0, n), tmp2 = s.substr(n, s.size()-n);
        return tmp2 + tmp1;
    }
};

复杂度分析

时间复杂度 O(N):切片复杂度为O(N);
空间复杂度 O(N):两个切片返回的临时对象长度之和为 N。


20. 表示数值的字符串

题意

  • 字符串处理

解法1 暴力处理

  • 首先获取有效字符串的起始位置(去掉首尾的连续空格),其中 ed 表示的是 有效字符串的后一位
  • 然后判断是否有 e/E
  • 如果没有 e/E,则判断 s[st, ed-1] 是不是整数或小数
  • 如果有 e/E,则分两段判断,即判断第一段 s[st, e_pos-1] 是不是整数或小数,第二段 s[e_pos+1, ed-1] 是不是整数
  • 然后是整数和小数的判断:
    • 对于整数的判断,首先判断有没有符号位,然后判断后面跟的是不是全是数字,空字符串和只含有符号位的字符串 不被认为是整数
    • 对于小数的判断,首先判断有没有符号位,然后寻找小数点的位置,分别处理小数点在首尾和中间时的情况,类似的,空字符串和只有符号位的字符串以及只有小数点的字符串 不被认为是小数。
class Solution {
public:
    bool isNum(char c)
    {
        if(c<='9' && c>='0') return true;
        return false;
    }
    bool isDecimal(string s)
    {
        int idx = 0;
        if(s[0] == '+' || s[0] == '-')
            s = s.substr(1, s.size()-1);
        if(s == "" || s == ".") return false;
        int point_pos = s.find('.');
        if(point_pos == 0)
        {
            // 开头是点
            for(int i=1; i<s.size(); i++)
            {
                if(!isNum(s[i])) return false;
            }
        }
        if(point_pos == s.size()-1)
        {
            // 结尾是点
            for(int i=0; i<s.size()-1; i++)
            {
                if(!isNum(s[i])) return false;
            }
        }
        // 点在中间
        for(int i=0; i<point_pos;i++)
        {
            if(!isNum(s[i])) return false;
        }
        for(int i=point_pos+1; i<s.size();i++)
        {
            if(!isNum(s[i])) return false;
        }
        return true;
    }
    bool isInteger(string s)
    {
        int idx = 0;
        if(s[0] == '+' || s[0] == '-')
            s = s.substr(1, s.size()-1);
        if(s == "") return false;
        while(isNum(s[idx]))
            idx++;
        if(idx==s.size()) return true;
        return false;
    }
    bool isNumber(string s) {
        int n = s.size();
        int idx = 0;
        int st = 0, ed = 0;
        int e_pos = s.find('e')!=s.npos ? s.find('e') : s.find('E');
        
        bool flag = false;

        while(s[idx] == ' ')
        {
            idx++;
        }
        
        st = idx;

        while(idx < s.size() && s[idx] != ' ')
        {
            idx++;
        }

        ed = idx;   // 有效字符串的后一位

        // 之后是不是全是空格
        for(int i=idx; i<s.size(); i++)
        {
            if(s[i] != ' ')
                return false;
        }

        // 有没有 e/E
        if(e_pos == s.npos)
        {   // 没有 e/E,只有一个整数或一个小数
            flag = isInteger(s.substr(st, ed-st)) || isDecimal(s.substr(st, ed-st));
        }
        else
        {
            flag = isInteger(s.substr(st, e_pos-st)) || isDecimal(s.substr(st, e_pos-st));
            flag = flag && (isInteger(s.substr(e_pos+1, ed-e_pos-1)));
        }
        return flag; 
    }
};

复杂度分析

时间复杂度 O(N):遍历字符串 s;
空间复杂度 O(N):有限变量。

解法2 有限状态自动机

创建状态,绘制有限状态自动机

本质上是利用 map 实现了一个状态转移矩阵。

class Solution {
public:
    enum State{
        STATE_INITIAL,
        STATE_INT_SIGN,
        STATE_INTEGER,
        STATE_POINT,
        STATE_POINT_WITHOUT_INT,
        STATE_FRACTION,
        STATE_EXP,
        STATE_EXP_SIGN,
        STATE_EXP_NUMBER,
        STATE_END
    };

    enum CharType{
        CHAR_NUMBER,
        CHAR_EXP,
        CHAR_POINT,
        CHAR_SIGN,
        CHAR_SPACE,
        CHAR_ILLEGAL
    };

    CharType toCharType(char ch)
    {
        if(ch>='0' && ch<='9') return CHAR_NUMBER;
        else if(ch=='e' || ch=='E') return CHAR_EXP;
        else if(ch=='.') return CHAR_POINT;
        else if(ch=='+' || ch=='-') return CHAR_SIGN;
        else if(ch==' ') return CHAR_SPACE;
        else return CHAR_ILLEGAL;
    }

    
    bool isNumber(string s) {
        unordered_map<State, unordered_map<CharType, State> > transfer
        {
            {
                STATE_INITIAL, {
                    {CHAR_SPACE, STATE_INITIAL},
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_SIGN, STATE_INT_SIGN},
                    {CHAR_POINT, STATE_POINT_WITHOUT_INT}
                }
            },
            {
                STATE_INT_SIGN, {
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_POINT, STATE_POINT_WITHOUT_INT}
                }
            },
            {
                STATE_INTEGER, {
                    {CHAR_NUMBER, STATE_INTEGER},
                    {CHAR_POINT, STATE_POINT},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END}
                }
            },
            {
                STATE_POINT, {
                    {CHAR_NUMBER, STATE_FRACTION},
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END}
                }
            },
            {
                STATE_FRACTION, {
                    {CHAR_EXP, STATE_EXP},
                    {CHAR_SPACE, STATE_END},
                    {CHAR_NUMBER, STATE_FRACTION}
                }
            },
            {
                STATE_POINT_WITHOUT_INT, {
                    {CHAR_NUMBER, STATE_FRACTION}
                }
            },
            {
                STATE_EXP, {
                    {CHAR_SIGN, STATE_EXP_SIGN},
                    {CHAR_NUMBER, STATE_EXP_NUMBER}
                }
            },
            {
                STATE_EXP_SIGN, {
                    {CHAR_NUMBER, STATE_EXP_NUMBER}
                }
            },
            {
                STATE_EXP_NUMBER, {
                    {CHAR_NUMBER, STATE_EXP_NUMBER},
                    {CHAR_SPACE, STATE_END}
                }
            },
            {
                STATE_END, {
                    {CHAR_SPACE, STATE_END}
                }
            }
        };

        int n = s.size();
        State st = STATE_INITIAL;

        for(int i=0;i<n;i++)
        {
            CharType tmp = toCharType(s[i]);
            if(transfer[st].find(tmp) == transfer[st].end())
            {
                return false;
            }
            else
            {
                st = transfer[st][tmp];
            }
        }
        return st == STATE_INTEGER || st == STATE_POINT || st == STATE_FRACTION || st == STATE_EXP_NUMBER || st == STATE_END;
    }
};

复杂度分析

时间复杂度 O(N):遍历字符串 s;
空间复杂度 O(1):有限状态转移矩阵。

枚举 - enum

将一组整形常量组织在一起,常用来作为数据的标识。


67. 把字符串转换成整数

题意

  • 字符串处理

解法 暴力遍历

  • 首先,如果字符串为空,返回 0;
  • 然后,寻找第一个非 ‘ ’ 的位置记为 st,若st == str.size(),则整个字符串都为 ‘ ’,返回 0;
  • 判断 str[st] 是否为非法字符(除 0-9、+、- 以外),若是,则返回 0;
  • 寻找最长有效整数的后一位,记为 ed;
  • 将 str[st,ed-1] 转换为 int 型整数,为了判断是否超出数据类型范围,在每一轮转换时都判断是否超出范围。
class Solution {
public:
    bool isNum(char c)
    {
        if(c >= '0' && c <= '9') return true;
        return false;
    }
    int strToInt(string str) {
        int n = str.size();

        if(str == "") return 0;

        int st = 0, ed = 0, idx = 0;


        while(idx<n && str[idx]==' ')
        {
            idx++;
        }
        
        if(idx == n) return 0;
        st = idx;

        
        if(!isNum(str[st]) && str[st]!='-' && str[st]!='+') return 0;
        idx++;

        while(idx<n && isNum(str[idx]))
        {
            idx++;
        }
        ed = idx;

        if(str[st] == '-')
        {

            int ans = 0;
            for(int i=st+1; i<ed; i++)
            {
                if(ans<INT_MIN/10) return -2147483648;
                if(ans==INT_MIN/10 && str[i]>'8') return -2147483648;
                ans=ans*10 - (str[i]-'0');    
            }    
            // ans*=-1;
            return ans;
        } 
        else
        {
            int ans = 0;
            if(str[st] == '+') st++;
            cout<<st<<" "<<ed<<endl;
            for(int i=st; i<ed; i++)
            {
                if(ans>INT_MAX/10) return 2147483647;
                if(ans==INT_MAX/10 && str[i]>'7') return 2147483647;
                ans=ans*10 + (str[i]-'0');   
                
            }
            return ans;
        }

    }
};

复杂度分析

时间复杂度 O(N):遍历字符串 s;
空间复杂度 O(1):有限变量。

越界判断:(参考评论区)

  • 除以10,如果除以10的结果和上一次的计算结果不同,意味发生越界。
int temp = res * 10 + (chars[i] - '0');
if (temp / 10 != res) return sign == 1? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = temp;
  • 在乘10之前先与 INT_MAX/10 或 INT_MIN/10 比较,因为每一轮 temp 都会进行乘10操作,所以可以在乘10之前进行判断。
for(int i=st+1; i<ed; i++)
{
    if(ans<INT_MIN/10) return -2147483648;
    if(ans==INT_MIN/10 && str[i]>'8') return -2147483648;
    ans=ans*10 - (str[i]-'0');    
}   

for(int i=st; i<ed; i++)
{
     if(ans>INT_MAX/10) return 2147483647;
     if(ans==INT_MAX/10 && str[i]>'7') return 2147483647;
     ans=ans*10 + (str[i]-'0');   
     
 }

总结

A. string++=+= 要比 + 高效)

  • str = str + a;:先将等号右边的两个 string 对象内容相加,并且创建一个新的 string 对象接收他们的和的赋值,再把结果赋值给左边的 str

  • str += a;:直接将等号右边的 string 对象内容追加到左边的 string 对象后。

  • + 会比 += 多创建一个对象,因此时间上和空间上的效率都会比 += 低。

B. string 的常用方法

  • 【PAT】第六章 C++标准模板库 - string

C. str 转 int 的越界判断

  • 除以10,如果除以10的结果和上一次的计算结果不同,意味发生越界。
int temp = res * 10 + (chars[i] - '0');
if (temp / 10 != res) return sign == 1? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = temp;
  • 在乘10之前先与 INT_MAX/10 或 INT_MIN/10 比较,因为每一轮 temp 都会进行乘10操作,所以可以在乘10之前进行判断。
for(int i=st+1; i<ed; i++)
{
    if(ans<INT_MIN/10) return -2147483648;
    if(ans==INT_MIN/10 && str[i]>'8') return -2147483648;
    ans=ans*10 - (str[i]-'0');    
}   

for(int i=st; i<ed; i++)
{
     if(ans>INT_MAX/10) return 2147483647;
     if(ans==INT_MAX/10 && str[i]>'7') return 2147483647;
     ans=ans*10 + (str[i]-'0');   
     
 }

D. 枚举 - enum

将一组整形常量组织在一起,常用来作为数据的标识。


你可能感兴趣的:(剑指offer,leetcode,c++,剑指offer)