概要
問題
文字列
s
が与えられたとき、重複文字を含まない最長の連続部分文字列の長さを求めます。
例
s = "abcabcbb"→ 出力:3("abc")s = "bbbbb"→ 出力:1("b")s = "pwwkew"→ 出力:3("wke")
制約
- 入力長:
0 ≤ n ≤ 5×10⁴ - 文字種: 英字・数字・記号・空白(ASCII + 非ASCII混在可能)
- 部分文字列(substring)であり、部分列(subsequence)ではない
アルゴリズム要点
- スライディングウィンドウ: 右端を進めつつ、左端を必要に応じて前進
-
ASCII高速化:
array('I', 128)で固定・高速アクセス -
非ASCII対応:
dictでメモリ削減(BMP 65536配列を回避) - Time: O(n) — 各文字を高々1回走査
- Space: O(1) 相当(ASCII固定128、非ASCIIは出現数に比例)
ステップバイステップ解説
Python実装(LeetCode形式)
以下は、メモリ削減版の実装です。ASCIIは固定配列、非ASCIIは辞書で管理します。
from __future__ import annotations
from array import array
from typing import Dict, Final
class Solution:
"""
Longest Substring Without Repeating Characters
メモリ削減版:
- ASCII (0..127) は array('I', 128) の軽量表(未出現=0, 出現は index+1)
- 非ASCII (>=128) は dict に格納(BMP 65536表は使わない)
→ 65536 要素配列(約256KB) の確保を完全に回避してピークメモリを下げる
"""
_MAX_LEN: Final[int] = 5 * 10**4
def lengthOfLongestSubstring(self, s: str) -> int:
"""
Args:
s: 入力文字列
Returns:
重複のない最長連続部分文字列の長さ
Raises:
TypeError: s が str でない場合
ValueError: 入力長が仕様上限を超える場合
Complexity:
Time: O(n)
Space: O(1) 相当(ASCIIは固定128、非ASCIIは出現数に比例)
"""
# 入力検証
if not isinstance(s, str):
raise TypeError("Input must be a string")
n: int = len(s)
if n > self._MAX_LEN:
raise ValueError("Input length exceeds allowed maximum")
# 基底条件: 空文字列
if n == 0:
return 0
# ASCII 用(0..127)だけ固定確保:極小&高速
last_ascii: array = array("I", [0]) * 128
# 非ASCII は dict にのみ格納(BMP 65536表は作らない)
last_other: Dict[int, int] = {}
left: int = 0 # ウィンドウ左端
best: int = 0 # 最大長
for i, ch in enumerate(s):
code: int = ord(ch)
# 分岐: ASCIIか非ASCIIか
if code < 128:
prev: int = last_ascii[code]
# 重複検出: 前回出現がウィンドウ内なら左端を前進
if prev > left:
left = prev
# 現在位置を記録(1-indexed: 0 は未出現)
last_ascii[code] = i + 1
else:
prev = last_other.get(code, 0)
if prev > left:
left = prev
last_other[code] = i + 1
# 現在ウィンドウの長さを計算
curr_len: int = i - left + 1
if curr_len > best:
best = curr_len
return best
視覚的図解
アルゴリズムフローチャート
フローの説明:
1. 入力検証(型チェック・長さチェック)→ エラーなら例外を発生
2. 空文字列なら0を返して終了
3. 初期化:left=0(ウィンドウ左端)、best=0(最大長)
4. 各文字について:
・ASCII(code<128)なら配列から、非ASCIIなら辞書から前回出現位置を取得
・前回位置がウィンドウ内(prev>left)なら、leftを前進して重複を排除
・現在位置を記録し、ウィンドウ長を計算してbestを更新
5. 全文字を処理したら、bestを返して終了
計算量
時間計算量: O(n)
- 各文字を高々1回走査
- ASCIIは配列で O(1) アクセス、非ASCIIは辞書で平均 O(1)
- 左端
leftは単調増加(最大 n まで)
空間計算量: O(1) 相当
- ASCII部分:
array('I', 128)→ 512バイト固定 -
非ASCII部分:
dictは出現種類数に比例(実際は小規模)
実装比較
| 実装 | Time | Space (ASCII) | Space (非ASCII) | メモリ順位 |
|---|---|---|---|---|
| 修正前(BMP 65536配列) | O(n) | 512B | 約256KB(BMP表) | 中程度 |
| 修正後(dict) | O(n) | 512B | 出現種類数に比例 | 改善 |