Skip to content
Open
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
start_new_problem.sh
main.go
go.mod
go.sum
*.go
172 changes: 172 additions & 0 deletions 3LongestSubstringWithoutRepeatingCharacters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
問題: https://leetcode.com/problems/longest-substring-without-repeating-characters/description/

### Step 1
- O(n^2)時間で全探索する方法と、sliding windowを使う方法を思いついた
- sliding windowでやってみる
- 文字コードとか考慮しないといけなさそう
- テストケース
- s="" -> 0
- s="a" -> 1
- s="ab" -> 2
- s="abc" -> 3
- s="aaa" -> 1
- s="aba" -> 2
- s="abcbdef" -> 4
- s="abcbdefa" -> 5
- 時間計算量: O(n)
- 空間計算量: O(n)

```Go
func lengthOfLongestSubstring(s string) int {
if len(s) == 0 {
return 0
}
charToLastIndex := make(map[byte]int)

Choose a reason for hiding this comment

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

確認済みかもですが、ASCIIの特性を活かした解法もあるようです🙇‍♂️
philip82148/leetcode-swejp#3

Copy link
Owner Author

Choose a reason for hiding this comment

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

共有ありがとうございます。書いてみました。

const AsciiCodepointNum = 128

func lengthOfLongestSubstring(s string) int {
	var charLastIndex [AsciiCodepointNum]int
	for i := range charLastIndex {
		charLastIndex[i] = -1
	}
	result := 0
	substringStartIndex := 0
	for i := 0; i < len(s); i++ {
		if charLastIndex[uint8(s[i])] >= substringStartIndex {
			substringStartIndex = charLastIndex[uint8(s[i])] + 1
		}
		charLastIndex[uint8(s[i])] = i
		result = max(result, i-substringStartIndex+1)
	}
	return result
}

charToLastIndex[s[0]] = 0
result := 1
left := 0
right := 1
for right < len(s) {
rightChar := s[right]
if i, found := charToLastIndex[rightChar]; found && left <= i {
result = max(result, right-left)
charToLastIndex[rightChar] = right
left = i + 1
right++
continue
}
charToLastIndex[rightChar] = right
right++
}
return max(result, right-left)
}
```

### Step 2
#### 2a
- step1の修正
- https://github.com/olsen-blue/Arai60/pull/49/files#diff-eaf04e4839867b1c256a01b37e3fd908cd889582123148e5910d72e4e4fcb421R53
- 自分のやりたいことをより少ない行数で実装していた
Copy link

Choose a reason for hiding this comment

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

そうですね。上のコードを見て、形式的にも色々できそうですね。
たとえば、ループで次に行くときに right++ をしているので for に直りそうとか、実は、= right のところが同じとか。

- こちらの書き方の方が文字に対してrune型を使える
- runeはint32のaliasでunicodeコードポイントを表現するので、byte型を使うより多くの文字を表現できる
- 追記: 「マルチバイト文字」に対応できる
- https://github.com/rihib/leetcode/pull/7/files#r1705447541

```Go
func lengthOfLongestSubstring(s string) int {
charToLastIndex := make(map[rune]int)
result := 0
left := 0
for right, c := range s {
if i, found := charToLastIndex[c]; found && left <= i {
left = i + 1
}
charToLastIndex[c] = right
result = max(result, right-left+1)
}
return result
}
```

#### 2b
- leftを一気に飛ばさずにハッシュマップから除去しながら一つずつslideさせていく方法
- https://github.com/Yoshiki-Iwasa/Arai60/pull/42/files#diff-e7cfc7c77fde81f979e52c0e885012cb46b258eb1d473ac363c4384a73a8fa86R42
- 結構詰まった
- stringからピンポイントでrune型文字にアクセスする方法がわからなかった
- `[]rune(s)`でsをrune型スライスにしてしまう
- `for _, found := charToLastIndex[rightChar]; found;`で毎回foundを計算し直してくれると思ったらしてくれなくて永遠に left++ されてindex out of range
- よく考えたらそれもそのはずで、for文の第1項は初期化条件なので、第3項かループ内でいじらないと更新されない

```Go
func lengthOfLongestSubstring(s string) int {
sRune := []rune(s)
chars := make(map[rune]struct{})
result := 0
left := 0
for right := range sRune {
rightChar := sRune[right]
_, found := chars[rightChar]
for ; found; _, found = chars[rightChar] {
delete(chars, sRune[left])
left++
}
chars[rightChar] = struct{}{}
result = max(result, right-left+1)
}
return result
}
```

### Step 3
#### 3a
- leftを飛ばす

```Go
func lengthOfLongestSubstring(s string) int {
charToLastIndex := make(map[rune]int)
result := 0
left := 0
for right, c := range s {
Copy link

@olsen-blue olsen-blue Mar 29, 2025

Choose a reason for hiding this comment

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

私は、グリッドのインデックスにr, cを使いがちなので、一瞬 c の意味を捉えられませんでした。charと書いても良いのかもしれないですね。
同時に私も r, c ではなく row, col と書いた方が良いかもという気がしてきました...

if i, found := charToLastIndex[c]; found && left <= i {
left = i + 1
}
charToLastIndex[c] = right
result = max(result, right-left+1)
}
return result
}
```

#### 3b
- leftを飛ばさない

```Go
func lengthOfLongestSubstring(s string) int {
sRune := []rune(s)
substringChars := make(map[rune]struct{})
result := 0
left := 0
for right, c := range sRune {
_, found := substringChars[c]
for ; found; _, found = substringChars[c] {
delete(substringChars, sRune[left])
left++
}
substringChars[c] = struct{}{}
result = max(result, right-left+1)
}
return result
}
```

### CS

Choose a reason for hiding this comment

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

この辺りの学習いいですね。

- Goのbyteとrune
- byte: uint8型のalias(バイトだから分かる)
- rune: int32型のalias(いつも忘れる)
- https://go.dev/ref/spec#Rune_literals 熟読する
- unicodeコードポイントを表現する
- シングルクオーテーションで囲まれる(知らなかった...)
- \t: タブ
- \x + 2桁16進数
- \u + 4桁16進数
- \U + 8桁16進数
- \x + 3桁8進数 (ただし、0~255以外の数値はエラー)
- runeの方が表現一つのデータ?で表現できる文字が多い
- escape sequence
- \nで改行を表す、などのように\によって特別な記号を表現する方法。総称となる単語を知らなかった
- format specifiers
- フォーマット指定子
- こちらも総称を知らなかった
- Goのrune型のフォーマット指定子
- %v: コードポイントの元となる数値
- %c: 文字
- %q: '文字'
- %U: 16進数で表現されるunicodeコードポイント(ex. U+1F600)
- %#U: %Uと%qが両方出力される
- octal: 8
- UnicodeとUTF-8
- いつもわからなくなる
Comment on lines +167 to +168

Choose a reason for hiding this comment

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

永遠によくわかっていなかったです。参考になります。

- Unicode: 世界中の文字を数値に対応させたものの集合体
- UTF-8: Unicode文字を表現するための符号化方式
- ex. 'あ'という文字はUnicodeで3042番のコードポイントが割り当てられており、
これをUTF-8で表現すると E38182 になる