前言
字符串是我们在日常开发中最常处理的数据,虽然它本身不是一种数据结构,但是由于其可以包含所有信息,所以通常作为数据的一种形式出现,由于不同语言创建和管理字符串的方式也各有差异,因此针对不同语言特征又产生了很多问题。
常见的字符串转换题目,也就是在大小写字母、数字、特殊字符这几种类型之间进行。但是在转换过程中需要处理几种特殊情况,比如当前元素能否进行转换,如果是字符串转换为数字还要考虑当前元素是不是数字,转换之后是否会溢出等。
1.转换成小写字母
力扣709题,给你一个字符串 s
,将该字符串中的大写字母转换成相同的小写字母,返回新的字符串。
分析:在计算机中,每个字符都有相应的ASCII
码。我们可以根据码表操作字符串,常见的ASCII
码范围:
0-9 48-57
A-Z 65-90
a-z 97-122
遍历整个字符串,对每一位字符串加以判断,如果字符串的编码值在65-90之间,就需要在原来了的ASCII
值上利用按位或运算| 32
就可以转换为对应小写。
代码如下:
// 使用内置函数
function toLowerCase(s) {
return s.toLowerCase();
}
// 自行实现
let toLowerCase = function (s) {
const res = [];
for (let charOfWord of s) {
if (charOfWord.charCodeAt() >= 65 && charOfWord.charCodeAt() <= 90) {
// 使用按位或位运算表示加法
charOfWord = String.fromCharCode(charOfWord.charCodeAt() | 32);
}
res.push(charOfWord);
}
return res.join("");
};
2.字符串转换为整数(atoi)
力扣8题,请你来实现一个 myAtoi(string s)
函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi
函数)。
函数 myAtoi(string s)
的算法如下:
- 读入字符串并丢弃无用的前导空格
- 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
- 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
- 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为
0
。必要时更改符号(从步骤 2 开始)。 - 如果整数数超过 32 位有符号整数范围
[−231, 231 − 1]
,需要截断这个整数,使其保持在这个范围内。具体来说,小于−231
的整数应该被固定为−231
,大于231 − 1
的整数应该被固定为231 − 1
。 - 返回整数作为最终结果。
注意:
- 本题中的空白字符只包括空格字符
' '
。 - 除前导空格或数字后的其余字符串外,请勿忽略 任何其他字符。
分析:
参考:Gatsby/力扣官方
这里用到了自动机的解法,用图来表示:
用表格来表示:
’ '(空格) | +/- | Number | 其它 | |
---|---|---|---|---|
start | start | signed | in_number | end |
signed | end | end | in_number | end |
in_number | end | end | in_number | end |
end | end | end | end | end |
代码如下:
/**
* @param {string} s
* @return {number}
*/
var myAtoi = function(s) {
// 自动机类
class Automaton {
constructor() {
// 执行阶段:默认是开始阶段
this.state = 'start';
// 正负符号:默认是正数
this.sign = 1;
// 数值,默认是0
this.answer = 0;
/*
关键点:
状态和执行的阶段的对应表
含义:[执行阶段, [空格], [正负号], [数值], [其它]]
*/
this.map = new Map([
['start',['start', 'signed', 'in_number', 'end']],
['signed', ['end', 'end', 'in_number', 'end']],
['in_number', ['end', 'end', 'in_number', 'end']],
['end', ['end', 'end', 'end', 'end']],
]);
}
// 获取状态的索引
getIndex(char) {
if (char === ' ') {
return 0;
} else if (char === '-' || char === '+') {
return 1;
} else if (isNumeric(char)) {
return 2;
} else {
return 3;
}
}
/*
关键点:
字符转换执行函数
*/
get(char) {
const MIN_VALUE = -Math.pow(-2, 31);
const MAX_VALUE = Math.pow(2, 31) - 1;
/*
易错点:
每次传入字符,都要变更自动机的执行阶段
*/
this.state = this.map.get(this.state)[this.getIndex(char)];
if (this.state === 'in_number') {
/*
小技巧:
在JS中,对字符串类型做减法操作,可以得到一个数值型(Number)的值
易错点:本处需要利用括号来提高四则运算的优先级
*/
this.answer = this.answer * 10 + (char - 0);
// 易错点:在进行负数比较时,需要将INT_MIN变为正数
this.answer = (this.sign === 1 ? Math.min(this.answer, MAX_VALUE) : Math.min(this.answer, MIN_VALUE));
} else if (this.state === 'signed') {
/*
优化点:
对于一个整数来说,非正即负,
所以正负号的判断,只需要一次。
所以可以降低其判断的优先级
*/
this.sign = (char === '+' ? 1 : -1);
}
}
}
// 判断传进来的字符串是不是数字
function isNumeric(s) {
return /^-?\d+(\.\d+)?$/.test(s);
}
// 生成自动机实例
let automaton = new Automaton();
// 遍历每个字符
for (let char of s) {
// 依次进行转换
automaton.get(char);
}
// 返回值,整数 = 正负 * 数值
return automaton.sign * automaton.answer;
};
在判断传入的字符是不是数字时,最好用正则表达式来判断,这样比较准确。
用typeof Number(char) === 'number'
和!isNaN(char)
都不太合理:
typeof Number(char) === 'number'
: 这部分判断使用了typeof
操作符,它会将Number(char)
的结果判定为'number'
。然而,Number(char)
在转换无法转换为有效数字的字符串时会返回NaN
,而typeof NaN
也是'number'
,因此这部分判断并不能准确地判断传入的字符串是否是一个有效的数字。!isNaN(char)
: 这部分判断使用了isNaN
函数,它用于检查一个值是否为NaN
。然而,isNaN
函数在判断非数字类型的值时也会返回false
,比如空字符串、布尔值、对象等。这也就意味着,如果传入的是非数字但却不是NaN
的值,这部分判断同样会得出错误的结论。