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..13e7137 --- /dev/null +++ b/3_longest_substring_without_repeating_characters_medium/README.md @@ -0,0 +1,43 @@ +# 問題へのリンク + +[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 +``` + +- Sliding Windowでは、基本的に`[left, right]`の閉区間で、`right`をforループでインクリメントしていくのが王道。バグも生みにくい。 + + +- 時間計算量:`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