From 9b420272f7b53443794ed887e2fbb25a0195ad40 Mon Sep 17 00:00:00 2001 From: Ryohei Sato <130881456+Satorien@users.noreply.github.com> Date: Wed, 18 Jun 2025 09:36:27 +0900 Subject: [PATCH] Create 300. Longest Increasing Subsequence.md --- .../300. Longest Increasing Subsequence.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 Python3/300. Longest Increasing Subsequence.md diff --git a/Python3/300. Longest Increasing Subsequence.md b/Python3/300. Longest Increasing Subsequence.md new file mode 100644 index 0000000..e3aa38d --- /dev/null +++ b/Python3/300. Longest Increasing Subsequence.md @@ -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] = {} + max_length = 0 + for i in range(len(nums)): + longest_subsequence_to_index[i] = 1 + for j in range(i): + 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] = [] + for num in nums: + if not subsequence_lengths_to_nums or subsequence_lengths_to_nums[-1] < num: + 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]: + end = middle + else: + begin = middle + 1 + return begin + + subseq_lens_to_nums: list[int] = [] + for num in nums: + insert_pos = get_subseq_len_to_num(subseq_lens_to_nums, num) + if insert_pos < len(subseq_lens_to_nums): + subseq_lens_to_nums[insert_pos] = num + continue + subseq_lens_to_nums.append(num) + return len(subseq_lens_to_nums) +```