用 JavaScript 解 LeetCode — Longest Substring Without Repeating Characters

Mindy Huang
5 min readAug 15, 2020

--

題目

這次的 LeetCode 題目是「找出沒有重複字母的最長子字串」,難度為 medium。

官方範例

  • 輸入值: “abcabcbb”
  • 輸出值: 3
  • 說明: 最長的「子字串」為 “abc”, 長度是 3

關鍵點

  1. 如何有效找出所有可能的「子字串」
  2. 計算「最大子字串」的長度
  3. 判斷是否有「重複字母」

解題思路

1. Sliding Window 滑動視窗演算法

我使用 Sliding Window 演算法,來搜索出所有「符合條件的子字串」。

window (視窗) 在此是指 Input 字串中的一段範圍 (子字串)

通常用 start index 和 end index 兩個索引值,來記錄視窗的邊界

目的:盡可能擴展視窗的大小,使「字母不重複」的「子字串」,範圍越長越好。

我們先從「第一個字母」開始 (start index = 0; end index = 0)

i. 擴展視窗的「右邊界」 (end index++)

當右邊界擴展到出現「重複字母」時,停下來,計算先前那段「沒有重複字母」的視窗長度,儲存起來

ii.接著,將視窗「左邊界」向右收縮 (start index++),直到裡面沒有重複字母。

iii. 重複 i. ii. 步驟,直到視窗無法再向右滑動 (end index 已到底),表示我們已搜索完所有「字母不重複的子字串」。

2. 計算「最大子字串」的長度

  • 定義一個變數 maxLength
  • 把所有「字母不重複的子字串」的長度 (前面步驟 i 的計算結果) ,和 maxLength 比較
  • 取兩者較大的那個,更新至 maxLength

3. 使用 Set 儲存不重複的字母

  • Set (不重複集合) 可以用來儲存「當前子字串」包含的所有字母。
  • 假設有一個curSet 的集合,儲存了當前視窗內所有字母。
  • 當我們拿到一個新的字母 char,想判斷它是否為重複字母,用 curSet.has(char) 就能辦到。

舉例說明

Input String: pwwkew 為例,假設 startIdxendIdx 分別為視窗的兩側邊界

endIdx = 0

‘p’wwkew
目前子字串:p,視窗邊界 [startIdx, endIdx] = [0, 0]

endIdx = 1

p‘w’wkew
目前子字串:pw,視窗邊界 [startIdx, endIdx] = [0, 1]

endIdx = 2

pw’w’kew
目前子字串:pww ->有重複字元, [startIdx, endIdx] = [0, 2]

  • 更新 maxLength
    將先前「合格子字串」(pw) 的長度記錄下來:maxLength = 2
  • 視窗左邊界向右收縮
    從視窗的最前面開始刪除字母,直到沒有重複字母為止:
    - 刪除 p
    目前子字串:ww,視窗邊界 [startIdx, endIdx] = [1, 2]
    - 刪除 w
    目前子字串:w (沒有重複字母),視窗邊界 [startIdx, endIdx] = [2, 2]

endIdx = 3

pww’k’ew
目前子字串:wk, [startIdx, endIdx] = [2, 3]

endIdx = 4

pwwk’e’w
目前子字串:wke, [startIdx, endIdx] = [2, 4]

endIdx = 5

pwwke’w’
目前子字串:wkew -> 有重複字母, [startIdx, endIdx] = [2, 5]。

  • 更新 maxLength
    將先前「合格子字串」(wke) 的長度記錄下來:3。我們發現他比maxLength 還大。因此更新 maxLength 為 3
  • 視窗左邊界向右收縮
    從子字串 wkew 的最前面開始刪除字母,直到字串沒有重複字母為止:
    - 刪除 w
    目前子字串:kew -> 沒有重複字母了,更新視窗邊界 [startIdx, endIdx] = [3, 5]

此時視窗已滑動至最右側 (endIdx = 5),搜索完畢

  • 得出「字母不重複的子字串」,最長為 3 (maxLength)

程式碼

function lengthOfLongestSubstring(s) {
var startIdx = 0
var endIdx = 0
var maxLength = 0

var curSet = new Set()

for (let endIdx = 0; endIdx < s.length; endIdx++) {
if (curSet.has(s[endIdx])) {
var prevEndIdx = Math.max(endIdx - 1, 0)
maxLength = Math.max(prevEndIdx - startIdx + 1, maxLength)

while (s[startIdx] !== s[endIdx]) {
// 視窗左邊界向右收縮,更新 curSet
curSet.delete(s[startIdx++])
}

startIdx++ // 左邊界已指到重複字母的位置,往右再收縮一格
} else {
curSet.add(s[endIdx])
}

if (endIdx === (s.length - 1)) {
maxLength = Math.max(endIdx - startIdx + 1, maxLength)
}
}

return maxLength
};

Reference

https://leetcode.com/problems/longest-substring-without-repeating-characters/solution/

https://iter01.com/155481.html

https://ithelp.ithome.com.tw/articles/10185006

--

--