本文是筆者在實際中具體遇到的場景,從中提取具體的核心的部分,使用前後指針進行性能優化的具體案例
開發需求場景
- 前段時間,筆者在做代碼Review的時候,發現了一個需求的前端實現方案不太優雅
- 組員選擇使用了循環加切割的方案去處理這個字符串
- 筆者最終將其改為,使用快慢指針的方式,讓其變得更加優雅,性能更佳
需求描述
- 後端有一個字段返回一篇中文文章的具體內容
- 比如有一個artical文章字段的值是一個長長的字符串
- 需求抽象成:
let artical = '白日依山盡,黃河入海流。欲窮千里目,更上一層樓。' - 前端展示的時候,需要一行一行展示(標點符號分隔,一句話就是一行)
- 比如上述的
artical的值,最終會展示成
<p>白日依山盡</p>
<p>黃河入海流</p>
<p>欲窮千里目</p>
<p>更上一層樓</p>
循環加切割的方案
他的實現思路是:
先第一遍遍歷,把原來的字符串“拷貝”一份,如果遇到了標點符號,統一使用橫槓替換,最後再以橫槓為標識,將字符串切割成數組,從而完成需求
實際上的標點符號,除了逗號、句號之外,還有問號,感嘆號,省略號等,這裏是為了更便於理解,抽象成為簡單模型
代碼
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字符串 - 有點像,張三和李四拿着捲尺丈量操場的長度
- 李四原地不動,張三拉着捲尺到頭,記錄一段距離
- 李四再跑到張三的位置,張三再往前跑同時把卷尺拉到頭
- 如下動畫:
代碼
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集合去判斷是否是標點符號,即
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 樹遍歷等
對撞指針應用場景
- 迴文字符串驗證
- 有序數組的兩數之和
- 數組元素交換等