diff --git "a/\347\253\266\346\212\200\343\203\227\343\203\255\345\260\261\346\264\273\351\203\250PR\347\224\250/300. Longest Increasing Subsequence.md" "b/\347\253\266\346\212\200\343\203\227\343\203\255\345\260\261\346\264\273\351\203\250PR\347\224\250/300. Longest Increasing Subsequence.md" new file mode 100644 index 0000000..384cdb8 --- /dev/null +++ "b/\347\253\266\346\212\200\343\203\227\343\203\255\345\260\261\346\264\273\351\203\250PR\347\224\250/300. Longest Increasing Subsequence.md" @@ -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文の進み方のせいかこっちの方がわかりやすかった。 + +```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 + increasing_subsequence = [MAX_INT] * len(nums) + + 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) +``` + +### 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)})") + + if index == len(increasing_subsequence): + increasing_subsequence.append(num) + else: + increasing_subsequence[index] = num + + return len(increasing_subsequence) +``` + +## セグメントツリーによる解法 +一旦スキップ。 +