文章目录
字符串
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 有限状态自动机
本质上是利用 unordered_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;
}
};
一种更好理解(冗余)的状态机构建法:
class Solution {
private:
enum State {
STATE_INIT,
STATE_HEAD_SPACE,
STATE_SIGN,
STATE_NUMBER,
STATE_POINT, // STATE_POINT_AFTER_NUMBER
STATE_POINT_WITHOUT_NUMBER, // 如果小数点前没有数字,则小数点后一定要有数字,所以小数点要分两类讨论
STATE_EXP,
STATE_NUMBER_AFTER_POINT,
STATE_SIGN_AFTER_EXP,
STATE_NUMBER_AFTER_EXP,
STATE_END_SPACE,
};
enum CharToType {
CHAR_SPACE,
CHAR_SIGN,
CHAR_NUMBER,
CHAR_POINT,
CHAR_EXP,
CHAR_ILLEGAL
};
CharToType char2Type(char c)
{
if(c >= '0' && c <= '9') return CHAR_NUMBER;
if(c == ' ') return CHAR_SPACE;
if(c == '+' || c == '-') return CHAR_SIGN;
if(c == 'E' || c == 'e') return CHAR_EXP;
if(c == '.') return CHAR_POINT;
return CHAR_ILLEGAL;
}
public:
bool validNumber(string s) {
unordered_map<State, unordered_map<CharToType, State>> transfer{
{
STATE_INIT, {
{CHAR_SPACE, STATE_HEAD_SPACE},
{CHAR_SIGN, STATE_SIGN},
{CHAR_NUMBER, STATE_NUMBER},
{CHAR_POINT, STATE_POINT_WITHOUT_NUMBER}
}
},
{
STATE_HEAD_SPACE, {
{CHAR_SPACE, STATE_HEAD_SPACE},
{CHAR_SIGN, STATE_SIGN},
{CHAR_NUMBER, STATE_NUMBER},
{CHAR_SPACE, STATE_HEAD_SPACE},
{CHAR_POINT, STATE_POINT_WITHOUT_NUMBER}
}
},
{
STATE_SIGN, {
{CHAR_POINT, STATE_POINT_WITHOUT_NUMBER},
{CHAR_NUMBER, STATE_NUMBER},
}
},
{
STATE_NUMBER, {
{CHAR_NUMBER, STATE_NUMBER},
{CHAR_EXP, STATE_EXP},
{CHAR_SPACE, STATE_END_SPACE},
{CHAR_POINT, STATE_POINT}
}
},
{
STATE_POINT, {
{CHAR_SPACE, STATE_END_SPACE},
{CHAR_EXP, STATE_EXP},
{CHAR_SIGN, STATE_SIGN_AFTER_EXP},
{CHAR_NUMBER, STATE_NUMBER_AFTER_POINT}
}
},
{
STATE_POINT_WITHOUT_NUMBER, {
{CHAR_NUMBER, STATE_NUMBER_AFTER_POINT}
}
},
{
STATE_EXP, {
{CHAR_SIGN, STATE_SIGN_AFTER_EXP},
{CHAR_NUMBER, STATE_NUMBER_AFTER_EXP}
}
},
{ STATE_NUMBER_AFTER_POINT, {
{CHAR_SPACE, STATE_END_SPACE},
{CHAR_NUMBER, STATE_NUMBER_AFTER_POINT},
{CHAR_EXP, STATE_EXP}
}
},
{
STATE_NUMBER_AFTER_EXP, {
{CHAR_NUMBER, STATE_NUMBER_AFTER_EXP},
{CHAR_SPACE, STATE_END_SPACE}
}
},
{
STATE_SIGN_AFTER_EXP, {
{CHAR_NUMBER, STATE_NUMBER_AFTER_EXP}
}
},
{
STATE_END_SPACE, {
{CHAR_SPACE, STATE_END_SPACE}
}
}
};
State stat= STATE_INIT;
for(char c : s)
{
auto tmp = transfer[stat];
CharToType cur = char2Type(c);
if(tmp.find(cur) == tmp.end()) return false;
stat = tmp[cur];
}
if(stat == STATE_END_SPACE || stat == STATE_POINT || stat == STATE_NUMBER_AFTER_POINT || stat == STATE_NUMBER || stat == STATE_NUMBER_AFTER_EXP) return true;
return false;
}
};
复杂度分析
时间复杂度 O(N):遍历字符串 s;
空间复杂度 O(1):有限状态转移矩阵。
枚举 - enum
将一组整形常量组织在一起,常用来作为数据的标识。
67. 把字符串转换成整数
题意
- 字符串处理
解法1 暴力遍历
- 首先,如果字符串为空,返回 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 myAtoi(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');
}
return ans;
}
else
{
int ans = 0;
if(str[st] == '+') st++;
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
)
O(N)
O(N),遍历字符串
s
s
s;
空间复杂度:
O
(
1
)
O(1)
O(1),有限变量。
越界判断:(参考评论区)
方法一:
- 在乘 10 之前先与
INT_MAX / 10
或INT_MIN / 10
比较,因为每一轮temp
都会进行乘 10 操作,所以 每一轮都有可能发生溢出,如果不借用long long
数据类型的话,就 要在每一轮乘 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');
}
方法二:
- 除以 10,如果除以 10 的结果和上一次的计算结果不同,意味发生越界。
int temp = res * 10 + (chars[i] - '0');
if (temp / 10 != res) return sign == 1? Integer.MAX_VALUE : Integer.MIN_VALUE;
res = temp;
解法2 有限状态自动机
class Solution {
private:
enum state{
INIT,
HEAD_SPACE,
SIGN,
NUMBER,
};
public:
int myAtoi(string str) {
string valid;
state stat = INIT;
for(char c : str)
{
if(stat == INIT)
{
if(c == ' ') stat = HEAD_SPACE;
else if(c == '+' || c == '-')
{
stat = SIGN;
valid += c;
}
else if(c - '0' >= 0 && c - '0' <= 9)
{
stat = NUMBER;
valid += c;
}
else break;
}
else if(stat == HEAD_SPACE)
{
if(c == ' ') stat = HEAD_SPACE;
else if(c == '+' || c == '-')
{
stat = SIGN;
valid += c;
}
else if(c - '0' >= 0 && c - '0' <= 9)
{
stat = SIGN;
valid += c;
}
else break;
}
else if(stat == SIGN)
{
if(c - '0' >= 0 && c - '0' <= 9)
{
stat = NUMBER;
valid += c;
}
else break;
}
else if(stat == NUMBER)
{
if(c - '0' >= 0 && c - '0' <= 9)
{
stat = NUMBER;
valid += c;
}
else break;
}
}
if(valid.size() == 0) return 0;
bool isNeg = valid[0] == '-' ? true : false;
string neg_stand = to_string(1 >> 30), pos_stand = to_string((1 >> 30) - 1);
if(isNeg)
{
// 负数,数字部分是否大于 2^31
long long res = 0;
for(int i = 1; i < valid.size(); i++)
{
res = res * 10 - (valid[i] - '0');
if(res <= INT_MIN) return INT_MIN; // 每轮都要判断
}
if(res <= INT_MIN) return INT_MIN;
else return res;
}
else
{
// 正数,数字部分是否大于 2^31 - 1
valid = valid[0] == '+' ? valid.substr(1) : valid;
long long res = 0;
for(int i = 0; i < valid.size(); i++)
{
res = res * 10 + (valid[i] - '0');
if(res >= INT_MAX) return INT_MAX; // 每轮都要判断
}
if(res >= INT_MAX) return INT_MAX;
else return res;
}
return 0;
}
};
复杂度分析
时间复杂度:
O
(
N
)
O(N)
O(N),遍历字符串
s
s
s;
空间复杂度:
O
(
1
)
O(1)
O(1),有限变量。
总结
A. string
的 +
与 +=
(+=
要比 +
高效)
-
str = str + a;
:先将等号右边的两个string
对象内容相加,并且创建一个新的string
对象接收他们的和的赋值,再把结果赋值给左边的str
。 -
str += a;
:直接将等号右边的string
对象内容追加到左边的string
对象后。 -
+
会比+=
多创建一个对象,因此时间上和空间上的效率都会比+=
低。
B. 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
将一组整形常量组织在一起,常用来作为数据的标识。