From 32926aa18b495210e43526649d4850b2a734a8d2 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Thu, 19 Jun 2025 22:18:13 +0900 Subject: [PATCH 1/2] Solve 3_longest_substring_without_repeating_characters_medium --- .../README.md | 41 +++++++++++++++++++ .../sliding_window_with_set.py | 24 +++++++++++ .../step1.py | 32 +++++++++++++++ .../step2.py | 36 ++++++++++++++++ .../step3.py | 29 +++++++++++++ 5 files changed, 162 insertions(+) create mode 100644 3_longest_substring_without_repeating_characters_medium/README.md create mode 100644 3_longest_substring_without_repeating_characters_medium/sliding_window_with_set.py create mode 100644 3_longest_substring_without_repeating_characters_medium/step1.py create mode 100644 3_longest_substring_without_repeating_characters_medium/step2.py create mode 100644 3_longest_substring_without_repeating_characters_medium/step3.py diff --git a/3_longest_substring_without_repeating_characters_medium/README.md b/3_longest_substring_without_repeating_characters_medium/README.md new file mode 100644 index 0000000..bde864f --- /dev/null +++ b/3_longest_substring_without_repeating_characters_medium/README.md @@ -0,0 +1,41 @@ +# 問題へのリンク + +[3. Longest Substring Without Repeating Characters](https://leetcode.com/problems/longest-substring-without-repeating-characters/) + +# 言語 +Python + +# 問題の概要 +文字列 `s` が与えられたとき、重複しない文字からなる最長の部分文字列(substring)の長さを求める問題。 + +# 自分の解法 + +文字列から文字`char`を1文字ずつ取り出していく。文字`char`を走査しているとき、そこから前にたどって最長の部分文字列(で文字が重複していないもの)を管理しながら走査していく。 + +- 各文字に対して、それが出現した最後のインデックスを記録するための辞書 `char_to_last_index` を用意してウィンドウ制御を行う。 + + +- 時間計算量:`O(n)` +- 空間計算量:`O(1)` + +## step2 +- 変数名を変更:`substring_start_index` → `window_start_index` +- コメントを追加 + + +# 別解・模範解答 +ウィンドウ制御にsetを用いる方法もある。 + +https://wiki.python.org/moin/TimeComplexity より、setの要素の追加・削除は平均して`O(1)`であるため、以下のように書くことができる。 + +```python +while char in window_chars: + window_chars.remove(s[left_index]) # O(1) + left_index += 1 +``` + + +- 時間計算量:`O(n)` +- 空間計算量:`O(1)` + +# 次に解く問題の予告 diff --git a/3_longest_substring_without_repeating_characters_medium/sliding_window_with_set.py b/3_longest_substring_without_repeating_characters_medium/sliding_window_with_set.py new file mode 100644 index 0000000..017c5df --- /dev/null +++ b/3_longest_substring_without_repeating_characters_medium/sliding_window_with_set.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=3 lang=python3 +# +# [3] Longest Substring Without Repeating Characters +# + + +# @lc code=start +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + window_chars: set[str] = set() + max_length = 0 + left_index = 0 + for right_index, char in enumerate(s): + while char in window_chars: + window_chars.remove(s[left_index]) # O(1) on average + left_index += 1 + window_chars.add(char) + max_length = max(max_length, right_index - left_index + 1) + + return max_length + + +# @lc code=end diff --git a/3_longest_substring_without_repeating_characters_medium/step1.py b/3_longest_substring_without_repeating_characters_medium/step1.py new file mode 100644 index 0000000..117701e --- /dev/null +++ b/3_longest_substring_without_repeating_characters_medium/step1.py @@ -0,0 +1,32 @@ +# +# @lc app=leetcode id=3 lang=python3 +# +# [3] Longest Substring Without Repeating Characters +# + + +# @lc code=start +from collections import defaultdict + + +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + if not s: + return 0 + + # character -> last index + char_to_last_index: dict[str, int] = defaultdict(int) + substring_start_index = 0 + max_length = 0 + + for index, char in enumerate(s): + if char in char_to_last_index: + substring_start_index = max( + substring_start_index, char_to_last_index[char] + 1 + ) + char_to_last_index[char] = index + max_length = max(max_length, index - substring_start_index + 1) + return max_length + + +# @lc code=end diff --git a/3_longest_substring_without_repeating_characters_medium/step2.py b/3_longest_substring_without_repeating_characters_medium/step2.py new file mode 100644 index 0000000..accfe3e --- /dev/null +++ b/3_longest_substring_without_repeating_characters_medium/step2.py @@ -0,0 +1,36 @@ +# +# @lc app=leetcode id=3 lang=python3 +# +# [3] Longest Substring Without Repeating Characters +# + + +# @lc code=start +from collections import defaultdict + + +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + if not s: + return 0 + + # character -> last index + char_to_last_index: dict[str, int] = defaultdict(int) + window_start_index = 0 + max_length = 0 + + # consider two substrings at every iteration; + # 1. trailing substring: starts at substring_start_index, ends at index, and managed by a window + # 2. last longest substring: only length information is saved as max_length + # at the end of every iteration, we update max_length + for index, char in enumerate(s): + if char in char_to_last_index: + window_start_index = max( + window_start_index, char_to_last_index[char] + 1 + ) + char_to_last_index[char] = index + max_length = max(max_length, index - window_start_index + 1) + return max_length + + +# @lc code=end diff --git a/3_longest_substring_without_repeating_characters_medium/step3.py b/3_longest_substring_without_repeating_characters_medium/step3.py new file mode 100644 index 0000000..631a080 --- /dev/null +++ b/3_longest_substring_without_repeating_characters_medium/step3.py @@ -0,0 +1,29 @@ +# +# @lc app=leetcode id=3 lang=python3 +# +# [3] Longest Substring Without Repeating Characters +# + +# @lc code=start +from collections import defaultdict + + +class Solution: + def lengthOfLongestSubstring(self, s: str) -> int: + # character -> last index + char_to_last_index: dict[str, int] = defaultdict(int) + + # window manages the trailing substring + window_start_index = 0 + max_length = 0 + for index, char in enumerate(s): + if char in char_to_last_index: + window_start_index = max( + window_start_index, char_to_last_index[char] + 1 + ) + max_length = max(max_length, index - window_start_index + 1) + char_to_last_index[char] = index + return max_length + + +# @lc code=end From 0531d43c94070f97a21786016f46b11fde5235b0 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 15 Dec 2025 12:17:30 +0900 Subject: [PATCH 2/2] Add explanation for sliding window technique in README --- .../README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/3_longest_substring_without_repeating_characters_medium/README.md b/3_longest_substring_without_repeating_characters_medium/README.md index bde864f..13e7137 100644 --- a/3_longest_substring_without_repeating_characters_medium/README.md +++ b/3_longest_substring_without_repeating_characters_medium/README.md @@ -34,6 +34,8 @@ while char in window_chars: left_index += 1 ``` +- Sliding Windowでは、基本的に`[left, right]`の閉区間で、`right`をforループでインクリメントしていくのが王道。バグも生みにくい。 + - 時間計算量:`O(n)` - 空間計算量:`O(1)`