Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions Python3/3. Longest Substring Without Repeating Characters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
## Step 1. Initial Solution

- 文字列が与えられて重複しない部分文字列の最長長を調べる
- 文字列は英字, 数字, 記号, スペースもあり
- 前から見て行って重複しない間は長さを見る
- 重複したら重複以降を保持して続ける
- そんなに苦戦せずに書けた

```python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
max_length = 0
substring = ""
for char in s:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

charはC、C++、Javaなどの予約語(基本データ型)charと被るので他言語に慣れた読み手に配慮して自分は避けるようにしています。

cかchがよくみます。ちなみにchrはPythonの組み込み関数にあるので避けます。

duplicate_place = substring.find(char)
if duplicate_place == -1:
substring += char

substring_length = len(substring)
if substring_length > max_length:
max_length = substring_length
else:
substring = substring[duplicate_place + 1:] + char
return max_length
```

### Complexity Analysis

- 時間計算量:O(N^2)
- 空間計算量:O(N)

## Step 2. Alternatives

- もう少し効率の良いやり方はありそう
- 各charの位置を辞書で保存すれば探す処理はO(1)で済むが更新に時間がかかる
- https://github.com/tokuhirat/LeetCode/pull/48/files#diff-59fd806da6e42daeb12a7afabcd326ca6953f65144e488ba169167138a8c2598R2-R4
- 開始位置ごとに最後までiteration
- 中身は少し違うが計算量は同じ
- end_indexが動くのは少し分かりにくかった
- https://github.com/hayashi-ay/leetcode/pull/47/files#diff-eaf04e4839867b1c256a01b37e3fd908cd889582123148e5910d72e4e4fcb421R26
- 確かに更新する必要はないか。現在地との差分を考えるこれでO(N)で計算できる

```python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
substring_start_position = -2
max_length = 0
char_to_position = defaultdict(lambda: -1)
for index, char in enumerate(s):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

position と index が同じ意味で使われている点に違和感を感じました。どちらかに統一するとよいと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

どちらが良いか迷って混在させてしまってました。修正するようにします

duplicate_position = char_to_position[char]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicate_position ですと、 duplicate が動詞のため、関数名のように感じられました。また、直訳すると「重複した位置」となり、表現したいこことずれているように感じされました。自分なら、 last_position と名付けると思います。

char_to_position[char] = index
if duplicate_position >= substring_start_position:
substring_start_position = duplicate_position + 1
max_length = max(max_length, \
index - substring_start_position + 1)
Comment on lines +54 to +55

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
max_length = max(max_length, \
index - substring_start_position + 1)
max_length = max(
max_length,
index - substring_start_position + 1,
)

こちらのスタイルの方がよく見る気がします。
ちなみにですがpythonではカッコ(()、{}、[])の中では自由に改行できます。

return max_length
```

- https://github.com/olsen-blue/Arai60/pull/49/files#r2005295464
- defaultdictを使わずにdict.get(x ,-1)としても良い
- この方が読んでいる側からすると脳内メモリを解放されて楽に読める
- setでも試す

```python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
chars_in_window = set()
left_index = 0
max_length = 0
for right_index, char in enumerate(s):
while char in chars_in_window:
chars_in_window.remove(s[left_index])
left_index += 1
chars_in_window.add(char)
length = right_index - left_index + 1
max_length = max(max_length, length)
return max_length
```


## Step 3. Final Solution

- setで書く方法が同時に覚えておく必要のある事が少ないので嬉しい

```python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
max_length = 0
substr_start = 0
chars_in_substr = set()
for substr_end, char in enumerate(s):
Comment on lines +89 to +91

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

start, endはsliding windowを閉区間で表した時の両端点として使われていますが、Python documentationなどでは一貫して半開区間[start, end)の両端点として使われています。

例:https://docs.python.org/3/library/stdtypes.html#str.find

str.find(sub[, start[, end]])

Return the lowest index in the string where substring sub is found within the slice s[start:end]. Optional arguments start and end are interpreted as in slice notation. Return -1 if sub is not found. For example: ...

ちなみに、start, stopも同様です。
なので自分は閉区間の両端点を誤解なく表現したいときはfirst, lastやhead, tail、もしくはleft, rightを使うようにしています。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かにその観点は抜けていたので参考にします

while char in chars_in_substr:
chars_in_substr.remove(s[substr_start])
substr_start += 1
chars_in_substr.add(char)
max_length = max(max_length, substr_end - substr_start + 1)
return max_length
```

- string.findを使う方法も悪くはない

```python
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
max_length = 0
substring_start = 0
for substring_end, char in enumerate(s):
duplicate_position = s.find(char, substring_start, substring_end)
if duplicate_position != -1:
substring_start = duplicate_position + 1
max_length = max(max_length, substring_end - substring_start + 1)
return max_length
Comment on lines +103 to +112

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

substring_startの左隣を変数に置くと更新式で+1の修正が不要になり、またsubstringの距離も半開区間の両端点の差になってわかりやすくなるかもです。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        max_length = 0
        passed_tail = -1
        for substring_tail, ch in enumerate(s):
            last_found = s.find(ch, passed_tail + 1, substring_tail)
            if last_found != -1:
                passed_tail = last_found
            max_length = max(max_length, substring_tail - passed_tail)
        return max_length

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

上の

if last_found != -1:
    passed_tail = last_found

のところは、

passed_tail = max(passed_tail, last_found)

とも書けますね。これは趣味の範囲だと思います。

```