-
Notifications
You must be signed in to change notification settings - Fork 0
300. Longest Increasing Subsequence #32
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
base: main
Are you sure you want to change the base?
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,255 @@ | ||
| ## Step1 | ||
| - 有名問題 | ||
| - O(n^2) のDPが思いつく | ||
| - 今回は nums.length <= 2500 なので十分そう | ||
|
|
||
| - Follow up として O(n log n) で解けるかとある | ||
| - プログラミングコンテストチャレンジブックで二分探索による解法を見たことがあるが完全に忘れてしまった | ||
| - Step2で復習する | ||
| - セグメント木による解法もあるが、セグメント木を何も見ずに書ける自信なし、、 | ||
| - Step2でソラで書けるように練習する | ||
| - Step1ではセグメント木をライブラリとして使ってやってみる | ||
| - https://github.com/not522/ac-library-python/blob/master/atcoder/segtree.py | ||
| - ここから拾ってくる | ||
|
|
||
| - まずは、普通の bottom-up DP で | ||
| - max_lengths[i][j] := i番目(1-indexed)までの subproblem を考えたとき、増加部分列が nums[j] で終わるような最大の長さ とする | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| n = 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. ここは
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. n とか ans とか書いてしまう悪い癖が戻ってしまっていました。ご指摘ありがとうございます。 |
||
| max_lengths = [[0] * n for _ in range(n + 1)] | ||
| # initial conditions | ||
| for i in range(n): | ||
| max_lengths[i + 1][i] = 1 | ||
|
|
||
| for i in range(1, n): | ||
| for j in range(i): | ||
| if nums[j] < nums[i]: | ||
| # nums[i] を選ぶ | ||
| max_lengths[i + 1][i] = max( | ||
| max_lengths[i + 1][i], | ||
| max_lengths[i][j] + 1, | ||
| ) | ||
| # nums[i] を選ばない | ||
| max_lengths[i + 1][j] = max( | ||
| max_lengths[i + 1][j], | ||
| max_lengths[i][j], | ||
| ) | ||
| return max(max_lengths[-1]) | ||
| ``` | ||
|
|
||
| - 例によって、in-place化 | ||
| - i -> i + 1 の遷移かつ一点更新のみ | ||
| - time: O(n^2), space: O(n) | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| n = len(nums) | ||
| max_lengths = [1] * n | ||
| for i in range(1, n): | ||
| for j in range(i): | ||
| if nums[j] >= nums[i]: continue | ||
| max_lengths[i] = max(max_lengths[i], max_lengths[j] + 1) | ||
| return max(max_lengths) | ||
| ``` | ||
|
|
||
| - top-down DP | ||
| - 今回のパターンではあまり読みやすくなったような感じはしない | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| n = len(nums) | ||
|
|
||
| @cache | ||
| def max_lengths(i: int) -> int: | ||
| max_length = 1 | ||
| for j in range(i): | ||
| if nums[j] < nums[i]: | ||
| max_length = max(max_length, max_lengths(j) + 1) | ||
| return max_length | ||
|
|
||
| return max(max_lengths(i) for i in range(n)) | ||
| ``` | ||
|
|
||
| - セグメント木(コピペして使用) | ||
| - 参照) https://atcoder.github.io/ac-library/production/document_en/segtree.html | ||
| - time: O(n log n), space: O(n) | ||
| ```python | ||
| import typing | ||
|
|
||
|
|
||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| sorted_nums = list(set(nums)) | ||
| sorted_nums.sort() | ||
|
|
||
| # モノイドを定義 | ||
| def binary_operator(x: int, y: int) -> int: | ||
| return max(x, y) | ||
|
|
||
| identity_element = 0 | ||
|
|
||
| max_lengths = SegTree(binary_operator, identity_element, len(sorted_nums)) | ||
| ans = 0 | ||
| for num in nums: | ||
| # 座標圧縮 | ||
| pos = bisect_left(sorted_nums, num) | ||
| max_length_so_far = max_lengths.prod(0, pos) | ||
| max_lengths.set(pos, max_length_so_far + 1) | ||
| ans = max(ans, max_length_so_far + 1) | ||
| return ans | ||
|
|
||
|
|
||
| class SegTree: | ||
| def __init__( | ||
| self, | ||
| op: typing.Callable[[typing.Any, typing.Any], typing.Any], | ||
| e: typing.Any, | ||
| v: typing.Union[int, typing.List[typing.Any]], | ||
| ) -> None: | ||
| self._op = op | ||
| self._e = e | ||
|
|
||
| if isinstance(v, int): | ||
| v = [e] * v | ||
|
|
||
| self._n = len(v) | ||
| self._log = self._ceil_pow2(self._n) | ||
| self._size = 1 << self._log | ||
| self._d = [e] * (2 * self._size) | ||
|
|
||
| for i in range(self._n): | ||
| self._d[self._size + i] = v[i] | ||
| for i in range(self._size - 1, 0, -1): | ||
| self._update(i) | ||
|
|
||
| def set(self, p: int, x: typing.Any) -> None: | ||
| assert 0 <= p < self._n | ||
|
|
||
| p += self._size | ||
| self._d[p] = x | ||
| for i in range(1, self._log + 1): | ||
| self._update(p >> i) | ||
|
|
||
| def prod(self, left: int, right: int) -> typing.Any: | ||
| assert 0 <= left <= right <= self._n | ||
| sml = self._e | ||
| smr = self._e | ||
| left += self._size | ||
| right += self._size | ||
|
|
||
| while left < right: | ||
| if left & 1: | ||
| sml = self._op(sml, self._d[left]) | ||
| left += 1 | ||
| if right & 1: | ||
| right -= 1 | ||
| smr = self._op(self._d[right], smr) | ||
| left >>= 1 | ||
| right >>= 1 | ||
|
|
||
| return self._op(sml, smr) | ||
|
|
||
| def _update(self, k: int) -> None: | ||
| self._d[k] = self._op(self._d[2 * k], self._d[2 * k + 1]) | ||
|
|
||
| @staticmethod | ||
| def _ceil_pow2(n: int) -> int: | ||
| x = 0 | ||
| while (1 << x) < n: | ||
| x += 1 | ||
|
|
||
| return x | ||
| ``` | ||
|
|
||
| ## Step2 | ||
| - 二分探索による高速化 | ||
| - かなり頭が混乱した | ||
| - 覚えておかないと厳しい印象 | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| min_end_values_of_subsequcnce = [math.inf] * len(nums) | ||
| for num in nums: | ||
| pos = bisect_left(min_end_values_of_subsequcnce, num) | ||
| min_end_values_of_subsequcnce[pos] = num | ||
| return bisect_left(min_end_values_of_subsequcnce, math.inf) | ||
| ``` | ||
|
|
||
| - セグメント木を自力で | ||
| - なにをやっているかをちゃんと理解すれば案外スラスラ書けた気がする | ||
| - 区間更新とかもあるらしいが今はやらない | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| unique_nums = list(set(nums)) | ||
| sorted_nums = sorted(unique_nums) | ||
|
|
||
| max_lengths = SegTree() | ||
| ans = 0 | ||
| for num in nums: | ||
| pos = bisect_left(sorted_nums, num) | ||
| max_lenghth_so_far = max_lengths.get_max(0, pos) | ||
| max_lengths.set_value(pos, max_lenghth_so_far + 1) | ||
| ans = max(ans, max_lenghth_so_far + 1) | ||
| return ans | ||
|
|
||
|
|
||
| class SegTree: | ||
| def __init__(self): | ||
| # nums.length <= 2500 なので十分な長さ用意しておく | ||
| self.n = 2 ** 12 | ||
| self.seg = [0] * (2 * self.n) | ||
|
|
||
| def set_value(self, pos: int, value: int) -> None: | ||
| pos += self.n | ||
| self.seg[pos] = value | ||
| # 親を全部更新 | ||
| pos //= 2 | ||
| while pos > 0: | ||
| self.seg[pos] = max(self.seg[2 * pos], self.seg[2 * pos + 1]) | ||
| pos //= 2 | ||
|
|
||
| def get_max(self, query_left: int, query_right: int, seg_left: int = 0, seg_right: int = 2 ** 12, pos: int = 1) -> int: | ||
| # クエリ範囲とセグメント範囲が全く被っていない場合 -> 単位元を返す | ||
| if query_right <= seg_left or seg_right <= query_left: return 0 | ||
|
|
||
| # クエリ範囲がセグメント範囲を包含している場合 -> そのポジションの値を返す | ||
| if query_left <= seg_left and seg_right <= query_right: return self.seg[pos] | ||
|
|
||
| # それ以外 -> 子どもに任せる | ||
| seg_mid = (seg_left + seg_right) // 2 | ||
| return max( | ||
| self.get_max(query_left, query_right, seg_left, seg_mid, 2 * pos), | ||
| self.get_max(query_left, query_right, seg_mid, seg_right, 2 * pos + 1), | ||
| ) | ||
| ``` | ||
|
|
||
| - 過去ログを参照 | ||
| - https://github.com/shining-ai/leetcode/pull/31 | ||
| >セグメントツリーと座標圧縮を使うのは、この問題を解くにあたっては指し過ぎだと思います。勉強のためと割り切って書くのは良いと思います。 | ||
| - https://github.com/Ryotaro25/leetcode_first60/pull/34 | ||
| >ソフトウェアエンジニアの常識から微妙に外れていると思います。知っている人は多いけれども、知らなくても別に動揺されないものです。 | ||
| つまり、これを使ってできる高速化はできなくても構わないのですが、しかし、考える時にこれも知っていると、セグメントツリーを使わない方法を思いついたりするので、知っていてもいいかもしれません。 | ||
| - https://github.com/Yoshiki-Iwasa/Arai60/pull/46 | ||
| >ちなみにlisはlongest increasing subsequenceとは必ずしも一致しないですが、この命名でよいでしょうか? | ||
| たとえばnums = [10, 11, 12, 1, 2]を与えると、lisは[1, 2, 12]になると思います。実際には[1, 2, 12]という部分列を取ることはできません。 | ||
| - 命名は悩んだ。説明的にしようとするとどんどん長くなってしまう。。 | ||
| - `end_minimums_of_is`, `sub_seq_minimums`, `smallest_value_of_subseq` など候補 | ||
| - これらを考慮して `end_minimums_of_subsequence` が個人的に良さそう | ||
| - https://github.com/fhiyo/leetcode/pull/32 | ||
|
|
||
|
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. この辺類題です。一応、見ていないかもしれないので。
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. ありがとうございます。類題も解いておきます。 |
||
| ## Step3 | ||
| - 練習も兼ねて二分探索で | ||
| ```python | ||
| class Solution: | ||
| def lengthOfLIS(self, nums: List[int]) -> int: | ||
| end_minimums_of_subsequence = [math.inf] * 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. 最初は空の配列を用意して、配列の右端より大きいnumが出てきたらappendする方法でもよい気がしますね |
||
| ans = 0 | ||
| for num in nums: | ||
| pos = bisect_left(end_minimums_of_subsequence, num) | ||
| end_minimums_of_subsequence[pos] = num | ||
| ans = max(ans, pos + 1) | ||
| return ans | ||
| ``` | ||
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.
セグメント木はソフトウェアエンジニアの常識には含まれていないと思います。