中级农民
- 积分
- 106
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2018-2-24
- 最后登录
- 1970-1-1
|
## 300. Longest Increasing Subsequence
Given an unsorted array of integers, find the length of longest increasing subsequence.
**Example:**
```
Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.
```
**Note:**
- There may be more than one LIS combination, it is only necessary for you to return the length.
- Your algorithm should run in O(*n2*) complexity.
**Follow up:** Could you improve it to O(*n* log *n*) time complexity?
### Solution:
$O(n^2)$解法
教训是不要把`__main__`给提交了!
dp[i] 以nums[i] 为最末元素的最长子序列的长度
```python
class Solution:
def lengthOfLIS(self, nums) -> int:
n = len(nums)
if n<=1:
return n
dp =[1 for _ in range(n)]
ret = 0
for i in range(1, n):
for j in range(0, i):
dp[i] = max(dp[i], dp[j]+1 if nums[i]>nums[j] else 1)
ret = max(ret, dp[i])
return ret
```
$O(n\log n)$
这解法我是肯定想不出了,精巧orz
> 依然是着眼于一个上升子序列的结尾元素。思路是这样的:
>
> 如果已经得到的上升子序列的结尾的数越小,遍历的时候后面接上一个数,就会有更大的可能性构成一个更长的上升子序列。
>
> 说明:
>
> 1、在最开始,我们强调了子序列的定义,必须保持子序列中的元素在原始数组中的相对顺序。因此,通过从左向右遍历得到一个上升子序列,这个方法是合理的;
>
> 2、既然结尾越小越好,可以如下定义状态。为了与之前的状态区分,这里将状态数组命名为 tail。
>
> 第 1 步:定义新的状态。
> $tail[i]$ 表示长度为 i + 1 的所有最长上升子序列的结尾的最小值。
>
> 说明:
>
> 1、状态定义其实也描述了状态转移方程;
>
> 2、以题目中的示例为例 [10, 9, 2, 5, 3, 7, 101, 18] 中,容易发现长度为 2 的所有上升子序列中结尾最小的是子序列 [2, 3] ,因此 tail[1] = 3。
>
> 第 2 步:思考状态转移方程。状态转移方程其实也包含在状态的定义中。从直觉上看,数组 tail 也是一个严格上升数组。
> 下面证明:即对于任意的索引 i < j ,都有 tail[i] < tail[j]。
>
> 使用反证法:
>
> 假设对于任意的索引 i < j ,存在某个 tail[i] >= tail[j]。
>
> 对于此处的 $tail[i]$ 而言,对应一个上升子序列$[a_0,a_1,\ldots, a_i]$,依据定义 $tail[i] = a_i$;
> 对于此处的 $tail[j]$ 而言,对应一个上升子序列 $[b_0, b_1, ..., b_i, ... , b_j]$,依据定义 $tail[j] = b_j$ ;
>
> 由于 $tail[i] >= tail[j]$ ,等价于 $a_i \ge b_j$ ,而在上升子序列 $[b_0, b_1, ..., b_i, ... , b_j]$ 中,$b_i$严格小于 $b_j$ ,故有 $a_i \ge b_j > b_i$ ,则上升子序列 $[b_0, b_1, ..., b_i]$是一个长度也为 i + 1 但是结尾更小的数组,与 $a_i$的最小性矛盾。因此原命题成立。(证完)
>
> 因此,我们只需要维护有序数组 tail,它的长度就是最长上升子序列的长度。
>
> 下面说明如何在遍历中维护有序数组 tail 的定义:
>
> 算法的执行流程:
>
> 1、设置一个有序数组 tail,初始时为空;
>
> 注意:有序数组 tail 虽然有“上升”的性质,但它不是每时每刻都表示问题中的“最长上升子序列”(下文还会强调),不能命名为 LIS,有序数组 tail 只是用于求解 LIS 问题的辅助数组。
>
> 2、在遍历数组 nums 的过程中,每来一个新数 num,如果这个数严格大于有序数组 tail 的最后一个元素,就把 num 放在有序数组 tail 的后面,否则进入第 3 点;
>
> 注意:这里的大于是“严格”大于,不包括等于的情况。
>
> 3、在有序数组 tail 中查找第 1 个等于大于 num 的那个数,试图让它变小;
>
> 如果有序数组 tail 中存在等于 num 的元素,什么都不做,因为以 num 结尾的最短的“上升子序列”已经存在;
> 如果有序数组 tail 中存在大于 num 的元素,找到第 1 个,让它变小,这样我们就找到了一个“结尾更小”的“相同长度”的上升子序列。
> 说明:我们再看一下数组 tail[i] 的定义:长度为 i + 1 的所有最长上升子序列的结尾的最小值。因此,在遍历的过程中,我们试图让一个大的值变小是合理的。
>
> 这一步可以认为是“贪心算法”,总是做出在当前看来最好的选择,当前“最好的选择”是:试图让第 1 个严格大于 nums[i] 的数变小,变成 nums[i],这一步操作是“无后效性”的。
>
> 这一步是在有序数组中的操作,因此可以使用“二分查找法”。
>
> 4、遍历新的数 num ,先尝试上述第 2 点,第 2 点行不通则执行第 3 点,直到遍历完整个数组 nums,最终有序数组 tail 的长度,就是所求的“最长上升子序列”的长度。
>
>
https://leetcode-cn.com/problems ... -tan-xin-suan-fa-p/
```python
def lengthOfLIS(self, nums):
n = len(nums)
if not nums:
return 0
tails = [nums[0]]
for num in nums[1:]:
if num>tails[-1]:
tails.append(num)
continue
l, r = 0, len(tails)-1
while(l<r):
mid = l+(r-l)//2
if tails[mid]<num:
l = mid+1
else:
r = mid
tails[l]=num
return len(tails)
```
|
|