-
Notifications
You must be signed in to change notification settings - Fork 0
300 longest increasing subsequence medium #24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Kaichi-Irie
wants to merge
6
commits into
main
Choose a base branch
from
300_longest_increasing_subsequence_medium
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3992c74
Solve 300_longest_increasing_subsequence_medium
Kaichi-Irie 175197b
Add step3
Kaichi-Irie d3a33d7
Add implementation of bisect_right function in README
Kaichi-Irie 9e2a715
Refactor lengthOfLIS to initialize subsequence_min_tails as empty and…
Kaichi-Irie 8f0b53d
Add a link to the problem in README
Kaichi-Irie 6f49b5e
Refactor lengthOfLIS to update tails directly instead of using min fu…
Kaichi-Irie File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| # 問題へのリンク | ||
| [300. Longest Increasing Subsequence](https://leetcode.com/problems/longest-increasing-subsequence/) | ||
|
|
||
| # 言語 | ||
| 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 | ||
|
|
||
| `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: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これでも回るコードになっているので OK と思います。読むときにはこう書いてくれる保証がないので、それよりも広いものを読めるようにしておく必要はあるでしょう。私が読み取る必要があると思っているのは以下です。
|
||
| 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] = 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)`が返ることはある。 | ||
| - ちなみに`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) | ||
|
|
||
| - `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` | ||
|
|
||
| ```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)` | ||
|
|
||
| # 想定されるフォローアップ質問 | ||
| - もし `bisect_left` ではなく `bisect_right` を使った場合、結果は変わりますか?変わる場合、どのような入力で変わりますか?変わらない場合、その理由は何ですか? | ||
| - 本問では、`bisect_left` と `bisect_right` のどちらを使用しても結果は変わらない。なぜなら、求めるLISは"strictly increasing"であり、`tails`配列に同じ値が存在することはないからである。しかし、もし問題が"non-decreasing"なLISを求めるものであれば、`bisect_right`を使用することで、同じ値を持つ要素が`tails`に追加される可能性があり、結果が変わることになる。その場合は`bisect_right`を使用することで、同じ値を持つ要素がLISに含まれることを許容することになる。 | ||
| - このアルゴリズムではLISの『長さ』しか分かりませんが、実際の部分列そのものを復元するには、どのような変更が必要になりますか? | ||
| - このアルゴリズムでは、実際にはLISの「長さ」に加えて「末尾の要素」もわかる。そのため、末尾から | ||
|
|
||
|
|
||
|
|
||
| # 次に解く問題の予告 | ||
| - [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/) | ||
39 changes: 39 additions & 0 deletions
39
300_longest_increasing_subsequence_medium/min_tails_binary_search.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| 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:]: | ||
| index_to_insert = bisect_left(min_tails, num) | ||
| if index_to_insert == len(min_tails): | ||
| min_tails.append(num) | ||
| else: | ||
| min_tails[index_to_insert] = num | ||
| return len(min_tails) | ||
|
|
||
|
|
||
| # @lc code=end |
25 changes: 25 additions & 0 deletions
25
300_longest_increasing_subsequence_medium/min_tails_binary_search_bisect.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 = [] | ||
| for num in nums: | ||
| index_to_insert = bisect.bisect_left(subsequence_min_tails, num) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 一文字変数より index_to_insert の方が読みやすく感じました。 |
||
| 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 | ||
25 changes: 25 additions & 0 deletions
25
300_longest_increasing_subsequence_medium/min_tails_linear.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
37 changes: 37 additions & 0 deletions
37
300_longest_increasing_subsequence_medium/step3_binary_search.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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] = num | ||
| return len(tails) | ||
|
|
||
|
|
||
| # @lc code=end |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
max_lengths_so_far のことであれば、自分なら index_to_max_length と付けると思います。 list のインデックスから、その値を末尾としたときに最大の長さへのマッピング、というニュアンスをこめています。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
確かにわかりやすいと感じました。
max_length_by_indexやmax_length_ending_at_indexあたりもありかもしれません。リストや辞書の命名に詰まったら
*s(複数形)と、*to*や*by*(マッピング)をどちらも検討してみます。