小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。
题目链接
描述
有一个密钥字符串 S ,只包含字母,数字以及 '-'(破折号)。其中, N 个 '-' 将字符串分成了 N+1 组。
给你一个数字 K,请你重新格式化字符串,使每个分组恰好包含 K 个字符。特别地,第一个分组包含的字符个数必须小于等于 K,但至少要包含 1 个字符。两个分组之间需要用 '-'(破折号)隔开,并且将所有的小写字母转换为大写字母。
给定非空字符串 S 和数字 K,按照上面描述的规则进行格式化。
测试用例
输入:S = "5F3Z-2e-9-w", K = 4
输出:"5F3Z-2E9W"
解释:字符串 S 被分成了两个部分,每部分 4 个字符;
注意,两个额外的破折号需要删掉。
分析
有一个字符串,包含大小写和数字,并且会被 -
不规则的隔开,需要我们去掉全部的 -
,然后再按长度 k
隔开,即除第一组数据的长度 ≤K
,其余组的数据长度均为 k
,当然,题目要求我们最后返回全部大写的数据
解法 1
- 用正则去掉
-
- 字符串转化为数组
- 从后往前,计算数组的下标位置,插入
-
var licenseKeyFormatting = function(s, k) {
s = s.toUpperCase().replace(/-/g,'').split('');
for(let i=s.length - k;i>0;i-=k){
s.splice(i,0,'-');
}
return s.join('')
};
// 272 ms 44.5 MB
缺点分析:
因为代码里使用了 Array.splice()
,这个方法会返回删除数组,并修改原数组,占用了大量的耗时以及内存
解法 2
采用栈的结构,一进一出,判断插入 -
即在 解法1 的基础上,替换了中间的数组插入操作,挨个将数据入栈,同时判断并插入 -
var licenseKeyFormatting = function(s, k) {
s = s.toUpperCase().replace(/-/g, '').split('');
let i = 0;
let arr = [];
while (s.length > 0) {
if (i++ == k) {
i = 1;
arr.unshift('-');
}
arr.unshift(s.pop());
}
return arr.join('')
};
// 532 ms 45.3 MB
这个解法有几个优化的点:
- 不预处理字符串
- 按位从后往前读取字符串(而不是现在的弹出操作),入栈到新变量
- 在上一步的基础上,需要判断插入
-
,并需要忽略掉原字符串中的-
解法 3
不借助额外结构,先处理为数组后,直接使用 reduceRight() 合并字符,判断插入 -
var licenseKeyFormatting = function(s, k) {
s = s.toUpperCase().replace(/-/g, '').split('');
let i = 0;
return s.reduceRight((a, b) => {
if (i++ == k) {
i = 1;
return b + '-' + a;
}
return b + a;
}, '')
};
// 64 ms 43.4 MB
很明显,这种解法节省了不少空间和时间,相比上2种,性能飞跃了不少,但这3种解法,都是先预处理了字符串
解法 4
不切割字符串,直接倒序遍历按位读取并拼接,省去了转化为数组操作所需要的内存
var licenseKeyFormatting = function(s, k) {
// s = s.toUpperCase().replace(/-/g, '').split('');
let m = 0;
let str = '';
for (let i = s.length - 1; i >= 0; i--) {
let cs = s.charAt(i);
if (cs == '-') continue;
if (m++ == k) {
m = 1;
str = cs + '-' + str;
} else {
str = cs + str;
}
}
return str.toUpperCase();
};
// 72 ms 42.7 MB
// 80 ms 42.4 MB
按理来说,这种解法已经是最优,空间占用已经最少了,但耗时还是比 解法3 略微多一点点
每一份努力,都是我们跳槽的资本,欢迎 hxd 一起来交流、相互督促、共同成长~