-
Notifications
You must be signed in to change notification settings - Fork 0
Create 300. Longest Increasing Subsequence.md #50
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| 参照: | ||
| https://github.com/TORUS0818/leetcode/pull/33#discussion_r1826882598 | ||
| https://github.com/kazukiii/leetcode/pull/32#discussion_r1790191654 | ||
| https://github.com/seal-azarashi/leetcode/pull/28#pullrequestreview-2344638688 | ||
| https://github.com/Ryotaro25/leetcode_first60/pull/34#discussion_r1743810106 | ||
| https://github.com/Yoshiki-Iwasa/Arai60/pull/46#discussion_r1716197814 | ||
| https://github.com/Exzrgs/LeetCode/pull/18 | ||
| https://github.com/rossy0213/leetcode/pull/15#discussion_r1611781523 | ||
| https://github.com/goto-untrapped/Arai60/pull/18 | ||
| https://github.com/shining-ai/leetcode/pull/31 | ||
| https://github.com/hayashi-ay/leetcode/pull/27/commits/b53ce7bfa1c3cf30970c94356aab268597e70fea | ||
| https://discord.com/channels/1084280443945353267/1200089668901937312/1209827519352668170 | ||
|
|
||
| 外部参照: | ||
| https://ei1333.github.io/luzhiled/snippets/dp/longest-increasing-subsequence.html | ||
|
|
||
|
|
||
| 類題: | ||
| 1235. Maximum Profit in Job Scheduling | ||
| https://leetcode.com/problems/maximum-profit-in-job-scheduling/description/ | ||
| 962. Maximum Width Ramp | ||
| https://leetcode.com/problems/maximum-width-ramp/description/ | ||
|
|
||
|
|
||
| ## DPによる解法 | ||
| ### 1回目 (14m52s) | ||
| 時間計算量: O(N**2) | ||
| 空間計算量: O(N) | ||
|
|
||
| * 最初全探索を考えたが、subsequence(連続とは限らない)ため、再帰的に解く必要があり計算量が膨大(O(2^n))になる。 | ||
| * DPとして解く際、値に何を入れるか考え、言語化すると"ある地点leftまでにできる最大の増加部分列の長さ"。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| num_subsequences = [1] * len(nums) | ||
| for left in range(len(nums) - 1, -1, -1): | ||
| for right in range(left, len(nums)): | ||
| if nums[left] < nums[right]: | ||
| num_subsequences[left] = max(num_subsequences[left], num_subsequences[right] + 1) | ||
|
|
||
| return max(num_subsequences) | ||
| ``` | ||
|
|
||
| ### 2回目 | ||
| * rightのループを始める時に、right == leftの場合の比較は不必要。 | ||
| * num_subsequencesが最適解かはあまり自信がない。できれば"ある地点leftまでにできる最大の増加部分列の長さ"の要素を入れても良いと思ったが、長くなりすぎる。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| num_subsequences = [1] * len(nums) | ||
|
|
||
| for left in range(len(nums) - 1, -1, -1): | ||
| for right in range(left + 1, len(nums)): | ||
| if nums[left] < nums[right]: | ||
| num_subsequences[left] = max(num_subsequences[left], num_subsequences[right] + 1) | ||
|
|
||
| return max(num_subsequences) | ||
| ``` | ||
|
|
||
|
|
||
| ### 3回目 | ||
| * 好みの問題であるが、for文の向きを逆にしても良いので3回目はそっちで書いてみる。 | ||
| * 「数直線で考えたとき、右側が大きくなるよう、 nums[left] < nums[right] としたい」 by nodchipさん | ||
| (https://github.com/shining-ai/leetcode/pull/31/commits/c838d53b1643a92161bbc54f80fe6d5b3c5f6edf) | ||
| * 個人的にはfor文の進み方のせいかこっちの方がわかりやすかった。 | ||
|
Comment on lines
+64
to
+67
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. 私も同意です。無意識のうちに頭の中に右側が大きくなる数直線を連想することが多いので、right, leftは小さい値からスタートするこちらの方が直感的だなと感じました。
Owner
Author
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. ありがとうございます、どちらでも良い場合は順方向がやはりイメージしやすいですね。 |
||
|
|
||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| num_subsequences = [1] * len(nums) | ||
|
|
||
| for right in range(1, len(nums)): | ||
| for left in range(right): | ||
| if nums[left] < nums[right]: | ||
| num_subsequences[right] = max(num_subsequences[right], num_subsequences[left] + 1) | ||
|
|
||
| return max(num_subsequences) | ||
| ``` | ||
|
|
||
|
|
||
| ## 二分探索(bisect_left)による解法 | ||
| 時間計算量: O(NlogN) | ||
| 空間計算量: O(N) | ||
|
|
||
| ロジック: | ||
| MAX_INT = 10 ** 4 + 1とした時、numsと同等の長さの配列を下記の様に作成する。 | ||
| increasing_subsequence = [10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1 ...] | ||
| ここに、各値を左から順にnums = [0,1,0,3,2,3]をbisect_leftで見つかったindexに代入していく。 | ||
| n = 0 | ||
| increasing_subsequence = [0, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1] | ||
|
|
||
| n = 1 | ||
| increasing_subsequence = [0, 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1] | ||
|
|
||
| n = 0 | ||
| increasing_subsequence = [0, 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1] | ||
|
|
||
| n = 3 | ||
| increasing_subsequence = [0, 1, 3, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1] | ||
|
|
||
| n = 2 | ||
| increasing_subsequence = [0, 1, 2, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1] | ||
|
|
||
| n = 3 | ||
| increasing_subsequence = [0, 1, 2, 3, 10 ** 4 + 1, 10 ** 4 + 1, 10 ** 4 + 1] | ||
|
|
||
| 最終的に、再度increasing_subsequenceに対してMAX_INTを代入した場合の位置(3とMAX_INTの境目)が | ||
| できるsubsequenceの最大の長さ。 | ||
|
|
||
| 最悪時間計算量は、O(NlogN)。二分探索(O(logN))をN回行う。 | ||
|
|
||
| ### 1回目 | ||
| ```python | ||
| from bisect import bisect_left | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| MAX_INT = 10 ** 4 + 1 | ||
|
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. MAX_INT は、普通は int の最大値、Python2 では sys.maxint 3 では sys.maxsize C では INT_MAX のことだと考えるでしょう。違うものが入っているのは好ましくないです。 入力の制約を満たさない入力、異常な入力への対処
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. 数字の場合は、だんだんいろいろな事情で大きな数字が入るようになってきて、気がついたら、ここにその数が流れてきて、10001 を超えていたという事が起きるでしょう。で、事故を起こして、顧客に代表が謝罪をしているときに、2年前にこのコードを書いたときには、10000までしか来ないって言われていたので、このコードは悪くありません、となるかということですね。 つまり、そういう可能性を見ながらコードを書けるかなのです。そういう風に話が変わったときにどれくらい柔軟であるべきかなども俎上に載せて計算して書いています。 もちろん、この入力が「年齢」ならば大きな問題はないでしょう。そこらへんは状況次第です。 ただ、たとえば、64ビット符号付き整数の最大値を限界にしておいたら、事故を起こしたときに、他のところも事故を起こしている可能性が高いので、テストなどに引っかかってここが原因になりにくいですね。
Owner
Author
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. @oda さん |
||
| increasing_subsequence = [MAX_INT] * len(nums) | ||
|
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. アルゴリズムの形は理解しているもの、それがなぜ正しく動いているのか、仕組みを理解していないような印象を受けました。念のため、なぜ正しく動いているか、仕組みを説明してみていただけますか?特に、リストのインデックスと、その位置の値がどのような関係性を持つかに着目して説明するとよいと思います。 また、仕組みを理解しているのであれば、 increasing_subsequence に代入される各要素が、 increasing_subsequence という変数名で表されるものとは異なるということが分かると思います。適切な名前を付け直してみていただけますか?
Owner
Author
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. ありがとうございます。
ここまでを言語化して、 間違っている部分もあるかもしれませんが、ご一読いただけますと幸いです。 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. 前半部分のなぜ正しく動くかの部分は正しく理解できているように思いました。 後半部分の変数名については、悪くないように思いました。インデックスと、その値の対応関係が、より明確に変数名に表現されているとよいと感じました。例えば、リスト自体を 1-based に変更したうえで、 length_to_min_last_values としてしまうなどを考えました。
Owner
Author
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. ありがとうございます、よかったです。 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. 空のリストから始め、 bisect_left() の結果が len(increasing_subsequence) だった場合、末尾に要素を追加するという方法もあります。最後は |
||
|
|
||
| for n in nums: | ||
| index = bisect_left(increasing_subsequence, n) | ||
| increasing_subsequence[index] = n | ||
|
|
||
| return bisect_left(increasing_subsequence, MAX_INT) | ||
| ``` | ||
|
|
||
| ### 2回目 | ||
| * bisectを自前実装 | ||
| * increasing_subsequenceはより良い名前がありそう。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| def bisect_left_original(arr: List[int], val: int) -> int: | ||
| left, right = 0, len(arr) | ||
| while left < right: | ||
| mid = (left + right) // 2 | ||
| if arr[mid] < val: | ||
| left = mid + 1 | ||
| else: | ||
| right = mid | ||
| return left | ||
|
|
||
| MAX_INT = 10**4 + 1 | ||
| increasing_subsequence = [MAX_INT] * len(nums) | ||
| for num in nums: | ||
| insert_index = bisect_left_original(increasing_subsequence, num) | ||
| increasing_subsequence[insert_index] = num | ||
|
|
||
| return bisect_left_original(increasing_subsequence, MAX_INT) | ||
|
Comment on lines
+134
to
+152
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. bisect_leftの実装は良いと思いました。以前整理したことがあるので、こちら参考になるかもしれません。 increasing_subsequenceは、[]で初期値にしておいて、appendしながら構築すると、最後の答えの値を len(increasing_subsequence)で取得できるので、こちらの方がシンプルかもしれません。ご検討ください。
Owner
Author
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. ありがとうございます〜。全然、同じことでもその分皆さんが気になる点かと思うので、参考になります。復習時にこちらの方法で書いてみます。 |
||
| ``` | ||
|
|
||
| ### 3回目 | ||
| * 変更なし | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| def bisect_left_original(arr: List[int], val: int) -> int: | ||
| left, right = 0, len(arr) | ||
| while left < right: | ||
| mid = (left + right) // 2 | ||
| if arr[mid] < val: | ||
| left = mid + 1 | ||
| else: | ||
| right = mid | ||
| return left | ||
|
|
||
| MAX_INT = 10 ** 4 + 1 | ||
| increasing_subsequence = [MAX_INT] * len(nums) | ||
| for num in nums: | ||
| index = bisect_left_original(increasing_subsequence, num) | ||
| increasing_subsequence[index] = num | ||
|
|
||
| return bisect_left_original(increasing_subsequence, MAX_INT) | ||
| ``` | ||
|
|
||
| ### 4回目 | ||
| * icreasing_subsequenceを[]で初期化 | ||
| * この場合は変数名は`icreasing_subsequence`で良いと思った。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| def bisect_left_original(val: int, arr: List[int]) -> int: | ||
| if not arr: return 0 | ||
|
|
||
| left, right = 0, len(arr) | ||
| while left < right: | ||
| mid = (left + right) // 2 | ||
| if arr[mid] < val: | ||
| left = mid + 1 | ||
| else: | ||
| right = mid | ||
| return left | ||
|
|
||
| increasing_subsequence = [] | ||
| for num in nums: | ||
| index = bisect_left_original(num, increasing_subsequence) | ||
| if index > len(increasing_subsequence): | ||
| raise ValueError(f"Index should not exceed length of increasing_subsequence: {index} (max allowed: {len(increasing_subsequence)})") | ||
|
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 < 0 の場合はなぜ処理しないのかというツッコミどころもあると思いました。 |
||
|
|
||
| if index == len(increasing_subsequence): | ||
| increasing_subsequence.append(num) | ||
| else: | ||
| increasing_subsequence[index] = num | ||
|
|
||
| return len(increasing_subsequence) | ||
| ``` | ||
|
|
||
| ## セグメントツリーによる解法 | ||
| 一旦スキップ。 | ||
|
|
||
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.
num_subsequences という変数名は、サブシーケンスの数というニュアンスに感じました。 最大の増加部分列の長さという意味に近づけるため、 max_subsequence_lengths または max_lengthsはいかがでしょうか?
Uh oh!
There was an error while loading. Please reload this page.
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.
@nodchip さん
ありがとうございます。max_subsequence_lengthsが個人的に良いと思いました。また、numは少しニュアンスが異なるのも同意です。