本文是笔者在实际中具体遇到的场景,从中提取具体的核心的部分,使用前后指针进行性能优化的具体案例
开发需求场景
- 前段时间,笔者在做代码Review的时候,发现了一个需求的前端实现方案不太优雅
- 组员选择使用了循环加切割的方案去处理这个字符串
- 笔者最终将其改为,使用快慢指针的方式,让其变得更加优雅,性能更佳
需求描述
- 后端有一个字段返回一篇中文文章的具体内容
- 比如有一个artical文章字段的值是一个长长的字符串
- 需求抽象成:
let artical = '白日依山尽,黄河入海流。欲穷千里目,更上一层楼。'
- 前端展示的时候,需要一行一行展示(标点符号分隔,一句话就是一行)
- 比如上述的
artical
的值,最终会展示成
html
<p>白日依山尽</p>
<p>黄河入海流</p>
<p>欲穷千里目</p>
<p>更上一层楼</p>
循环加切割的方案
他的实现思路是:
先第一遍遍历,把原来的字符串"拷贝"一份,如果遇到了标点符号,统一使用横杠替换,最后再以横杠为标识,将字符串切割成数组,从而完成需求
实际上的标点符号,除了逗号、句号之外,还有问号,感叹号,省略号等,这里是为了更便于理解,抽象成为简单模型
代码
js
let artical = '白日依山尽,黄河入海流。欲穷千里目,更上一层楼。'
function toArr(str) {
let s = [',', '。']
let ns = ''
for (let i = 0; i < str.length - 1; i++) {
if (s.includes(str[i])) {
ns = ns + '-'
} else {
ns = ns + str[i]
}
}
return ns.split('-')
}
const result = toArr(artical)
console.log('result', result)
点评
- 首先,时间复杂度高,每次拼接都会复制整个已有字符串,字符串越长,复制的成本越高,累积起来甚至达到平方级的耗时。而artical字段的值,也不小,所以这种拼接方式效率不太高
- 然后,有依赖特殊字符风险,若原字符串中本身包含
-
,那么split('-')
则是会错误分割,尴尬了
快慢双指针方案
实现思路是
- 定义两个指针
l
和r
,初始都在起点索引为0的地方 - 使用while循环,让第一个指针
r
先往右跑 - 当
r
遇到标点符号的时候,截取l
到r
的区间的字符串存到数组里面 - 而后,再让
l
赶上r
,r
再往右跑,直到跑到头,跑完整个article
字符串 - 有点像,张三和李四拿着卷尺丈量操场的长度
- 李四原地不动,张三拉着卷尺到头,记录一段距离
- 李四再跑到张三的位置,张三再往前跑同时把卷尺拉到头
- 如下动画:

代码
js
let artical = '白日依山尽,黄河入海流。欲穷千里目,更上一层楼。'
function toArr(str) {
let s = [',', '。']
let arr = []
let l = 0
let r = 0
while (r <= str.length - 1) {
if (s.includes(str[r])) {
let range = str.slice(l, r)
arr.push(range)
r = r + 1
l = r
} else {
r = r + 1
}
}
return arr
}
const result = toArr(artical)
console.log('result', result)
最终使用Set集合去判断是否是标点符号,即
js
function toArr(str) {
let s = new Set([',', '。'])
let arr = []
let l = 0
let r = 0
while (r <= str.length - 1) {
if (s.has(str[r])) {
let range = str.slice(l, r)
arr.push(range)
r = r + 1
l = r
} else {
r = r + 1
}
}
return arr
}
点评
- 时间复杂度整体保持O(n)------
n
为字符串长度【从头到尾跑一遍就行了】 - 空间复杂度没大的开销
- Set的语义更贴合 "判断元素是否在一个集合中" 的场景,代码清晰------Set天然用于去重和成员判断
- 数组includes的效率会随标点符号数量增多而下降
A good memory is better than a bad pen. Record it down... 最后,再来回顾一下双指针的相关知识
双指针知识回顾
- 双指针是相对于单指针而言的,我们常见的for循环和forEach只有一个索引变量i的,就是单指针,有两个变量,比如i或者j的,就是双指针
- 双指针的本质是多定义一个索引变量j,用空间换时间提升效率的方式
- 双指针分为快慢指针和对撞指针
- 快慢指针------两个指针同侧出发移动,一前一后,一快一慢
- 对撞指针------两个指针从两端向中间移动
快慢指针应用场景
- 链表操作(判断循环、找中间节点)
- 数组去重、
字符串处理(本文就是一个具体实际应用)
- DOM 树遍历等
对撞指针应用场景
- 回文字符串验证
- 有序数组的两数之和
- 数组元素交换等