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
96 changes: 96 additions & 0 deletions Python3/300. Longest Increasing Subsequence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
## Step 1. Initial Solution

- 一つずつ見ていく方法
- O(n^2)でやる方法はなんとなく想像がつくが、時間がかかるので他の方法を検討
- 前の最大値から伸ばしていく方法なら少し短そう
- 再帰でできるか?
- 特に分かりやすいやり方は思いつかないので一旦放置
- 他にも考えてみたが上手く答えに至らなかったので一つずつ見ていく方法(加算方式)で実装

```python
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
longest_subsequence_to_index: dict[int, int] = {}
Copy link

Choose a reason for hiding this comment

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

キーは配列のインデックスで、値はその要素を最後に使って作れる最大の長さだと思います。 longest_subsequence_to_index という変数名ですと、中に含まれている値と矛盾するように思います。 index_to_max_length はいかがでしょうか?

max_length = 0
for i in range(len(nums)):
longest_subsequence_to_index[i] = 1
for j in range(i):

Choose a reason for hiding this comment

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

iとjの関係性がわかる変数名だとよりわかりやすいかと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

i -> end_index
j -> check_index
とかですかね

new_length = longest_subsequence_to_index[j] + 1
if nums[j] < nums[i] and new_length > longest_subsequence_to_index[i]:
longest_subsequence_to_index[i] = new_length
if max_length < longest_subsequence_to_index[i]:
max_length = longest_subsequence_to_index[i]
return max_length

```

- 反省
- dictじゃなくてlistで良い
- 変数名長いから躊躇ったが、max(longest…[i], longest…[j]+1)が良いかも
- 最後はmax(list)でも可

### Complexity Analysis

- 時間計算量:O(n^2)
- 1 + 2 + … + n
- 空間計算量:O(n)

## Step 2. Alternatives

- LeetCodeにある解法の一つを見てみると、subsequenceの末尾を見て、越えていたらリストを伸ばすという方法で最大の長さを探す方法がある
- 複数のsubsequenceを管理する方法もあるが、新しいsubsequenceを探していく時も同じリストを塗り替えていくのでも大丈夫
- ある長さに至るまでのsubsequenceの最小値をその長さのインデックスに管理するイメージ(日本語難しい)
- 時間計算量はO(n log n)になる

```python
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
subsequence_lengths_to_nums: list[int] = []

Choose a reason for hiding this comment

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

to_numsは、何かマッピングを行なっているわけではなければ不要かと感じました。

Copy link

Choose a reason for hiding this comment

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

部分配列の長さから、その部分配列の最後の num のうち最小のものへのマッピングとみなせるため、 subsequence_lengths_to_nums で良いと思います。自分なら、 length_to_min_last_num と付けると思います。

for num in nums:
if not subsequence_lengths_to_nums or subsequence_lengths_to_nums[-1] < num:

Choose a reason for hiding this comment

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

ここ少し、if分岐がパズルに感じたため、
subsequence_lengths_to_numsの初期値をnums[0]を入れてしまって、特別扱いしても良いかな、と思いました。好みだと思います。

Copy link

Choose a reason for hiding this comment

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

subsequence_length = bisect_left(subsequence_lengths_to_nums, num)
if subsequence_length == len(subsequence_lengths_to_nums):
  subsequence_lengths_to_nums.append(num)
else:
  subsequence_lengths_to_nums[subsequence_length] = num

としたほうがシンプルだと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かにそれで行けますね、思いつきませんでした!

subsequence_lengths_to_nums.append(num)
continue
subsequence_length = bisect_left(subsequence_lengths_to_nums, num)
subsequence_lengths_to_nums[subsequence_length] = num
return len(subsequence_lengths_to_nums)
```

- 他の人の解法もみてみる
- 割と同じような流れに帰着しているがシンプルに書けている
- bisect_leftの返り値がlen(list)でなければ挿入できる?
- 0 → 0が返ってきて0<0がFalseなのでappend
- 最大値 → len(list)が返ってくるのでappend
- https://github.com/olsen-blue/Arai60/pull/31/files#diff-b7fbb0dce1473afc0264185268f1a1ef6d682a3a8c997d43bc8bdd636a66ce4aR36
- SegmentTreeを使うことも常識外ではあるが連想できるらしい
- 詳しい実装は控えるが、ツリー構造でまとまりごとに演算(この場合は最小値)を計算して結果を保持しておく方法
- 更新と演算結果の探索がO(log n)で終わるのがポイント
- https://qiita.com/ZOI_dayo/items/f53122c831be78c695bc
- 座標圧縮なるものもあるらしいが一旦放置

## Step 3. Final Solution

- bisect_leftを自分で実装

```python
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
def get_subseq_len_to_num(sorted_list: list[int], num: int) -> int:
begin = 0
end = len(sorted_list)
while begin != end:
middle = (begin + end) // 2
if num <= sorted_list[middle]:

Choose a reason for hiding this comment

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

middleが主役の分岐だと思うので、if sorted_list[middle] >= numの方が自然なように感じました。

Copy link

Choose a reason for hiding this comment

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

主役 (比較される側) を左辺に持ってくる派と、数直線上に一直線に並ぶよう < または <= で原則統一する派があるように思います。

end = middle
else:
begin = middle + 1
return begin

subseq_lens_to_nums: list[int] = []

Choose a reason for hiding this comment

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

この変数名の意図を汲み取るのが難しかったです。

for num in nums:
insert_pos = get_subseq_len_to_num(subseq_lens_to_nums, num)
if insert_pos < len(subseq_lens_to_nums):

Choose a reason for hiding this comment

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

収まっている or 収まっていないで、それぞれ1つ処理があるだけの単純な構造なので、if-elseでも理解しやすいかと思いました。

subseq_lens_to_nums[insert_pos] = num
continue
subseq_lens_to_nums.append(num)
return len(subseq_lens_to_nums)
```