From 3992c74be9fea883b470f8262f1fe8734c9852bb Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sun, 14 Sep 2025 16:01:57 +0900 Subject: [PATCH 1/6] Solve 300_longest_increasing_subsequence_medium --- .../README.md | 128 ++++++++++++++++++ .../min_tails_binary_search.py | 39 ++++++ .../min_tails_binary_search_bisect.py | 25 ++++ .../min_tails_linear.py | 25 ++++ .../step1.py | 24 ++++ .../step2.py | 24 ++++ .../step3.py | 11 ++ 7 files changed, 276 insertions(+) create mode 100644 300_longest_increasing_subsequence_medium/README.md create mode 100644 300_longest_increasing_subsequence_medium/min_tails_binary_search.py create mode 100644 300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py create mode 100644 300_longest_increasing_subsequence_medium/min_tails_linear.py create mode 100644 300_longest_increasing_subsequence_medium/step1.py create mode 100644 300_longest_increasing_subsequence_medium/step2.py create mode 100644 300_longest_increasing_subsequence_medium/step3.py diff --git a/300_longest_increasing_subsequence_medium/README.md b/300_longest_increasing_subsequence_medium/README.md new file mode 100644 index 0000000..7459348 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/README.md @@ -0,0 +1,128 @@ +# 問題へのリンク + + +# 言語 +Python + +# 問題の概要 + + +# 自分の解法 + +## step1 + +```python +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + + max_lengths_so_far = [1] * len(nums) + + for i in range(1, len(nums)): + length = 0 + for j in range(i): + if nums[j] < nums[i]: + length = max(length, max_lengths_so_far[j]) + max_lengths_so_far[i] = length + 1 + return max(max_lengths_so_far) +``` + +- 変数名がうまくつけられなかった +- 時間計算量も最適でない + +`nums`の要素数を`n`とすると、 +- 時間計算量:`O(n^2)` +- 空間計算量:`O(n)` + +## step2 + +```python +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + + # max_length[i] is the maximum length of increasing subsequences that ends at nums[i] + max_lengths = [1] * len(nums) + for i in range(len(nums)): + max_previous_length = 0 + for j in range(i): + if nums[j] < nums[i]: + max_previous_length = max(max_previous_length, max_lengths[j]) + max_lengths[i] = max_previous_length + 1 + return max(max_lengths) +``` + +## step3 + +## step4 (FB) + + + +# 別解・模範解答 + +`min_tails_linear.py` + +```python +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + # min_tails[i] is the minimum number of the tails of increasing subsequences with length (i+1) + if not nums: + return 0 + min_tails = [nums[0]] + for num in nums[1:]: + if min_tails[-1] < num: + min_tails.append(num) + continue + for i, tail in enumerate(min_tails): + if num <= tail: + min_tails[i] = num + break + return len(min_tails) +``` + +- `min_tails[i]`は長さ`i + 1`の増加部分列の最小の末尾要素を表す +- 非常にエレガントだが、なぜこれで正しいのか直感的に理解するのは難しい +- 時間計算量:`O(n^2)` +- 空間計算量:`O(n)` + - 最悪、`nums`が昇順にソートされている場合、`tails`の長さは`n`になる + + +`mins_tails`は常に昇順にソートされているので、`num`が`min_tails`のどこに入るかを二分探索で探せる時間計算量は`O(log n)`になる + + + +`min_tails_binary_search_bisect.py` +```python +import bisect + + +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + min_tails = [nums[0]] + for num in nums[1:]: + j = bisect.bisect_left(min_tails, num) + if j == len(min_tails): + min_tails.append(num) + else: + min_tails[j] = num + return len(min_tails) +``` + + +- 時間計算量:`O(log n)` +- 空間計算量:`O(n)` + +# 想定されるフォローアップ質問 + +## CS 基礎 + +## システム設計 + +## その他 + +# 次に解く問題の予告 +- Permutations diff --git a/300_longest_increasing_subsequence_medium/min_tails_binary_search.py b/300_longest_increasing_subsequence_medium/min_tails_binary_search.py new file mode 100644 index 0000000..53a3b33 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/min_tails_binary_search.py @@ -0,0 +1,39 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start + + +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + def bisect_left(array: list, x: int) -> int: + if not array: + return 0 + if array[-1] < x: + return len(array) + left = -1 + right = len(array) - 1 + while right - left > 1: + mid = (right + left) // 2 + if array[mid] >= x: + right = mid + else: + left = mid + return right + + if not nums: + return 0 + min_tails = [nums[0]] + for num in nums[1:]: + j = bisect_left(min_tails, num) + if j == len(min_tails): + min_tails.append(num) + else: + min_tails[j] = num + return len(min_tails) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py b/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py new file mode 100644 index 0000000..48886f7 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py @@ -0,0 +1,25 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +import bisect + + +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + subsequence_min_tails = [nums[0]] + for num in nums[1:]: + index_to_insert = bisect.bisect_left(subsequence_min_tails, num) + if index_to_insert == len(subsequence_min_tails): + subsequence_min_tails.append(num) + else: + subsequence_min_tails[index_to_insert] = num + return len(subsequence_min_tails) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/min_tails_linear.py b/300_longest_increasing_subsequence_medium/min_tails_linear.py new file mode 100644 index 0000000..6e2780a --- /dev/null +++ b/300_longest_increasing_subsequence_medium/min_tails_linear.py @@ -0,0 +1,25 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + # min_tails[i] is the minimum number of the tails of increasing subsequences with length (i+1) + if not nums: + return 0 + min_tails = [nums[0]] + for num in nums[1:]: + if min_tails[-1] < num: + min_tails.append(num) + continue + for i, tail in enumerate(min_tails): + if num <= tail: + min_tails[i] = num + break + return len(min_tails) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/step1.py b/300_longest_increasing_subsequence_medium/step1.py new file mode 100644 index 0000000..fb4d604 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/step1.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + + max_lengths_so_far = [1] * len(nums) + + for i in range(1, len(nums)): + length = 0 + for j in range(i): + if nums[j] < nums[i]: + length = max(length, max_lengths_so_far[j]) + max_lengths_so_far[i] = length + 1 + return max(max_lengths_so_far) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/step2.py b/300_longest_increasing_subsequence_medium/step2.py new file mode 100644 index 0000000..286e7e2 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/step2.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + + # max_length[i] is the maximum length of increasing subsequences that ends at nums[i] + max_lengths = [1] * len(nums) + for i in range(len(nums)): + max_previous_length = 0 + for j in range(i): + if nums[j] < nums[i]: + max_previous_length = max(max_previous_length, max_lengths[j]) + max_lengths[i] = max_previous_length + 1 + return max(max_lengths) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/step3.py b/300_longest_increasing_subsequence_medium/step3.py new file mode 100644 index 0000000..5385f01 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/step3.py @@ -0,0 +1,11 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + return 0 +# @lc code=end From 175197b14600264bb8db99cca1c582ef7a4fc75b Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 15 Sep 2025 14:32:17 +0900 Subject: [PATCH 2/6] Add step3 --- .../README.md | 61 +++++++++++++++++-- .../step3.py | 11 ---- .../step3_binary_search.py | 37 +++++++++++ .../step3_bisect.py | 25 ++++++++ .../step3_bruteforce.py | 21 +++++++ 5 files changed, 139 insertions(+), 16 deletions(-) delete mode 100644 300_longest_increasing_subsequence_medium/step3.py create mode 100644 300_longest_increasing_subsequence_medium/step3_binary_search.py create mode 100644 300_longest_increasing_subsequence_medium/step3_bisect.py create mode 100644 300_longest_increasing_subsequence_medium/step3_bruteforce.py diff --git a/300_longest_increasing_subsequence_medium/README.md b/300_longest_increasing_subsequence_medium/README.md index 7459348..bd57c15 100644 --- a/300_longest_increasing_subsequence_medium/README.md +++ b/300_longest_increasing_subsequence_medium/README.md @@ -56,6 +56,55 @@ class Solution: ## step3 +`step3_bruteforce.py` +```python +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + left_max_lengths = [1] * len(nums) + for i in range(len(nums)): + for j in range(i): + if nums[j] < nums[i]: + left_max_lengths[i] = max( + left_max_lengths[i], left_max_lengths[j] + 1 + ) + + return max(left_max_lengths) +``` +- こっちはすらすら書ける + + +`step3_binary_search.py` +```python +class Solution: + def bisect_left(self, nums: list[int], target: int) -> int: + if not nums: + return 0 + left = -1 + right = len(nums) + while right - left > 1: + mid = (right + left) // 2 + if target <= nums[mid]: + right = mid + else: + left = mid + return right + + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + tails = [] + for num in nums: + index = self.bisect_left(tails, num) + if index == len(tails): + tails.append(num) + continue + tails[index] = min(tails[index], num) + return len(tails) +``` + +- `tails`は常に昇順にソートされているので、`num`が`tails`のどこに入るかを二分探索。これは覚えていないと書けない +- `left=-1`, `right=len(nums)`から始めると、`mid=-1`や`mid=len(nums)`になるのではないかと不安に思っていたが、`while right - left > 1`の条件でループするので、ループの中では`right - left`は常に2以上になるので、`left < mid < right`が保証される。返り値は`right`なので、`len(nums)`が返ることはある。 + ## step4 (FB) @@ -117,12 +166,14 @@ class Solution: - 空間計算量:`O(n)` # 想定されるフォローアップ質問 +- もし `bisect_left` ではなく `bisect_right` を使った場合、結果は変わりますか?変わる場合、どのような入力で変わりますか?変わらない場合、その理由は何ですか? + - 本問では、`bisect_left` と `bisect_right` のどちらを使用しても結果は変わらない。なぜなら、求めるLISは"strictly increasing"であり、`tails`配列に同じ値が存在することはないからである。しかし、もし問題が"non-decreasing"なLISを求めるものであれば、`bisect_right`を使用することで、同じ値を持つ要素が`tails`に追加される可能性があり、結果が変わることになる。その場合は`bisect_right`を使用することで、同じ値を持つ要素がLISに含まれることを許容することになる。 +- このアルゴリズムではLISの『長さ』しか分かりませんが、実際の部分列そのものを復元するには、どのような変更が必要になりますか? + - このアルゴリズムでは、実際にはLISの「長さ」に加えて「末尾の要素」もわかる。そのため、末尾から -## CS 基礎 - -## システム設計 -## その他 # 次に解く問題の予告 -- Permutations +- [Subarray Sum Equals K - LeetCode](https://leetcode.com/problems/subarray-sum-equals-k/description/) +- [String to Integer (atoi) - LeetCode](https://leetcode.com/problems/string-to-integer-atoi/description/) +- [Number of Islands - LeetCode](https://leetcode.com/problems/number-of-islands/description/) diff --git a/300_longest_increasing_subsequence_medium/step3.py b/300_longest_increasing_subsequence_medium/step3.py deleted file mode 100644 index 5385f01..0000000 --- a/300_longest_increasing_subsequence_medium/step3.py +++ /dev/null @@ -1,11 +0,0 @@ -# -# @lc app=leetcode id=300 lang=python3 -# -# [300] Longest Increasing Subsequence -# - -# @lc code=start -class Solution: - def lengthOfLIS(self, nums: list[int]) -> int: - return 0 -# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/step3_binary_search.py b/300_longest_increasing_subsequence_medium/step3_binary_search.py new file mode 100644 index 0000000..5975a98 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/step3_binary_search.py @@ -0,0 +1,37 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start + + +class Solution: + def bisect_left(self, nums: list[int], target: int) -> int: + if not nums: + return 0 + left = -1 + right = len(nums) + while right - left > 1: + mid = (right + left) // 2 + if target <= nums[mid]: + right = mid + else: + left = mid + return right + + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + tails = [] + for num in nums: + index = self.bisect_left(tails, num) + if index == len(tails): + tails.append(num) + continue + tails[index] = min(tails[index], num) + return len(tails) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/step3_bisect.py b/300_longest_increasing_subsequence_medium/step3_bisect.py new file mode 100644 index 0000000..1769c35 --- /dev/null +++ b/300_longest_increasing_subsequence_medium/step3_bisect.py @@ -0,0 +1,25 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +import bisect + + +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + if not nums: + return 0 + tails = [] + for num in nums: + index = bisect.bisect_left(tails, num) + if index == len(tails): + tails.append(num) + continue + tails[index] = min(tails[index], num) + return len(tails) + + +# @lc code=end diff --git a/300_longest_increasing_subsequence_medium/step3_bruteforce.py b/300_longest_increasing_subsequence_medium/step3_bruteforce.py new file mode 100644 index 0000000..5dd732c --- /dev/null +++ b/300_longest_increasing_subsequence_medium/step3_bruteforce.py @@ -0,0 +1,21 @@ +# +# @lc app=leetcode id=300 lang=python3 +# +# [300] Longest Increasing Subsequence +# + +# @lc code=start +class Solution: + def lengthOfLIS(self, nums: list[int]) -> int: + left_max_lengths = [1] * len(nums) + for i in range(len(nums)): + for j in range(i): + if nums[j] < nums[i]: + left_max_lengths[i] = max( + left_max_lengths[i], left_max_lengths[j] + 1 + ) + + return max(left_max_lengths) + + +# @lc code=end From d3a33d7a61205e039891b7b3bab43007ac5e5e72 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 15 Sep 2025 14:36:02 +0900 Subject: [PATCH 3/6] Add implementation of bisect_right function in README --- .../README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/300_longest_increasing_subsequence_medium/README.md b/300_longest_increasing_subsequence_medium/README.md index bd57c15..5e531d8 100644 --- a/300_longest_increasing_subsequence_medium/README.md +++ b/300_longest_increasing_subsequence_medium/README.md @@ -104,6 +104,20 @@ class Solution: - `tails`は常に昇順にソートされているので、`num`が`tails`のどこに入るかを二分探索。これは覚えていないと書けない - `left=-1`, `right=len(nums)`から始めると、`mid=-1`や`mid=len(nums)`になるのではないかと不安に思っていたが、`while right - left > 1`の条件でループするので、ループの中では`right - left`は常に2以上になるので、`left < mid < right`が保証される。返り値は`right`なので、`len(nums)`が返ることはある。 +- ちなみに`bisect_right`の実装は + +```python +def bisect_right(nums: list[int], target: int) -> int: + left = -1 + right = len(nums) + while right - left > 1: + mid = (right + left) // 2 + if target < nums[mid]: + right = mid + else: + left = mid + return right +``` ## step4 (FB) From 9e2a7151fd0814c32f7772e592ed0327d54eddcf Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 15 Sep 2025 14:36:15 +0900 Subject: [PATCH 4/6] Refactor lengthOfLIS to initialize subsequence_min_tails as empty and iterate over all nums --- .../min_tails_binary_search_bisect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py b/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py index 48886f7..303de0f 100644 --- a/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py +++ b/300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py @@ -12,8 +12,8 @@ class Solution: def lengthOfLIS(self, nums: list[int]) -> int: if not nums: return 0 - subsequence_min_tails = [nums[0]] - for num in nums[1:]: + subsequence_min_tails = [] + for num in nums: index_to_insert = bisect.bisect_left(subsequence_min_tails, num) if index_to_insert == len(subsequence_min_tails): subsequence_min_tails.append(num) From 8f0b53d627aa527f4bd340dfc70e32de2a711ab1 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 15 Sep 2025 14:37:32 +0900 Subject: [PATCH 5/6] Add a link to the problem in README --- 300_longest_increasing_subsequence_medium/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/300_longest_increasing_subsequence_medium/README.md b/300_longest_increasing_subsequence_medium/README.md index 5e531d8..816ffe6 100644 --- a/300_longest_increasing_subsequence_medium/README.md +++ b/300_longest_increasing_subsequence_medium/README.md @@ -1,12 +1,9 @@ # 問題へのリンク - +[300. Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/) # 言語 Python -# 問題の概要 - - # 自分の解法 ## step1 From 6f49b5e05a018ed7fcf5631dabebbabe596f4c30 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Wed, 17 Sep 2025 21:22:02 +0900 Subject: [PATCH 6/6] Refactor lengthOfLIS to update tails directly instead of using min function --- .../README.md | 23 ++++++++++++++++++- .../min_tails_binary_search.py | 8 +++---- .../step3_binary_search.py | 2 +- .../step3_bisect.py | 2 +- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/300_longest_increasing_subsequence_medium/README.md b/300_longest_increasing_subsequence_medium/README.md index 816ffe6..2e7d058 100644 --- a/300_longest_increasing_subsequence_medium/README.md +++ b/300_longest_increasing_subsequence_medium/README.md @@ -95,7 +95,7 @@ class Solution: if index == len(tails): tails.append(num) continue - tails[index] = min(tails[index], num) + tails[index] = num return len(tails) ``` @@ -118,8 +118,29 @@ def bisect_right(nums: list[int], target: int) -> int: ## step4 (FB) +- `max_lengths_so_far`は`index_to_max_length`や`max_length_by_index`、`max_length_ending_at_index`などの方が良い +```python +for i in range(len(nums)): + for j in range(i): + if nums[j] < nums[i]: + left_max_lengths[i] = max( + left_max_lengths[i], left_max_lengths[j] + 1 + ) +``` +は + +```python +for i in range(len(nums)): + left_max_length[i] = max( + [left_max_length[j] + 1 for j in range(i) if nums[j] < nums[i]], + default = 1 + ) +``` +とも書ける。余分にメモリは確保してしまうが、オーダーには影響しない程度。むしろ、二重ループに強弱が出てわかりやすいと感じた。つまり、内部の`j`のループは`i`のためにmaxを取るために回しているもので従属的である、と伝わりやすいと感じた。逆に多重ループが完全に独立している(直積である)ときには、`itertools.product`を使えば良い。 +- `max`の`default`引数は空のイテレータを渡した時の返り値を設定できる。`max([])`をそのまま実行すると、`ValueError: max() iterable argument is empty`が出る。 + # 別解・模範解答 `min_tails_linear.py` diff --git a/300_longest_increasing_subsequence_medium/min_tails_binary_search.py b/300_longest_increasing_subsequence_medium/min_tails_binary_search.py index 53a3b33..9d4c852 100644 --- a/300_longest_increasing_subsequence_medium/min_tails_binary_search.py +++ b/300_longest_increasing_subsequence_medium/min_tails_binary_search.py @@ -15,7 +15,7 @@ def bisect_left(array: list, x: int) -> int: if array[-1] < x: return len(array) left = -1 - right = len(array) - 1 + right = len(array) while right - left > 1: mid = (right + left) // 2 if array[mid] >= x: @@ -28,11 +28,11 @@ def bisect_left(array: list, x: int) -> int: return 0 min_tails = [nums[0]] for num in nums[1:]: - j = bisect_left(min_tails, num) - if j == len(min_tails): + index_to_insert = bisect_left(min_tails, num) + if index_to_insert == len(min_tails): min_tails.append(num) else: - min_tails[j] = num + min_tails[index_to_insert] = num return len(min_tails) diff --git a/300_longest_increasing_subsequence_medium/step3_binary_search.py b/300_longest_increasing_subsequence_medium/step3_binary_search.py index 5975a98..82f784f 100644 --- a/300_longest_increasing_subsequence_medium/step3_binary_search.py +++ b/300_longest_increasing_subsequence_medium/step3_binary_search.py @@ -30,7 +30,7 @@ def lengthOfLIS(self, nums: list[int]) -> int: if index == len(tails): tails.append(num) continue - tails[index] = min(tails[index], num) + tails[index] = num return len(tails) diff --git a/300_longest_increasing_subsequence_medium/step3_bisect.py b/300_longest_increasing_subsequence_medium/step3_bisect.py index 1769c35..024a633 100644 --- a/300_longest_increasing_subsequence_medium/step3_bisect.py +++ b/300_longest_increasing_subsequence_medium/step3_bisect.py @@ -18,7 +18,7 @@ def lengthOfLIS(self, nums: list[int]) -> int: if index == len(tails): tails.append(num) continue - tails[index] = min(tails[index], num) + tails[index] = num return len(tails)