首先搞清楚【子串】和【子序列】这两个名词的区别:
【子串】:一定是连续的
【子序列】:不一定是连续的
方法一:动态规划法
一般我们设计动态规划算法都需要一个dp数组,假设dp[0,...,i-1]都已经被算出来了,然后我们需要通过这些结果算出dp[i].
这里定义dp[i]:以第i个数字nums[i]结尾的最长上升子序列的长度,注意nums[i]必须被选取。
举个栗子,
dp[5],即数组nums中以7结尾的最长上升子序列的长度,
数组中以7结尾的最长上升子序列有[2,5,7],[2,3,7],长度是3,所以dp[5]=3.
dp[6],即数组nums中以21结尾的最长上升子序列的长度,
数组中以21结尾的最长上升子序列有[2,5,7,21],[2,3,7,21],长度是4,所以dp[6]=4.
据此我们可以知道最终要求的结果,也就是子序列的最大长度应该是dp数组中的最大值。
分析到这里,整个解题思路已经有了,接下来就是要设计算法逻辑来计算出整个dp数组的值。
在定义dp数组是将数组的所有元素初值置为1,含义是每个元素都至少可以单独成为子序列,此时长度都为1
接下来总结本题的核心:状态转移方程
设0 <= j < i,考虑每轮计算新dp[i]时,遍历[0,i)列表区间,做以下判断:
case1. 当nums[i] > nums[j] 时:nums[i]可以在nums[j]之后,此情况下最长上升子序列长度为dp[j] + 1;
case2,当nums[i] <= nums[j]时,nums[i]无法接在nums[j]之后,此情况上升子序列不成立,跳过。
当所有case1下计算出dp[j] + 1的最大值,直到i的最长上升子序列长度(即dp[i])。实现方式为遍历j时,每轮执行dp[i] = max(dp[i], dp[j] + 1)
状态转移方程:dp[i] = max(dp[i], dp[j] + 1) for j in [0, i).
还是画图来理解状态转移方程会好理解很多。
初始状态
i = 0时的情况
step1: j = 0, dp[0] = 1
i=1时的情况
step1:j = 0
i=2时的情况:
step1: j = 0
step2:j = 1
所以最终dp[2] = 1
i = 3时的情况
step1:j = 0
step2: j = 1
step3: j = 2
后面的操作与前面的类似,暂不赘述。
代码:
#coding:utf-8
# 最长上升子序列
# 动态规划法
# 时间复杂度:O(n^2)
# 空间复杂度:O(n)
class Solution:
def lengthOfLIS(self, nums):
"""
:param nums: List[int]
:return: int
"""
if not nums:
return 0
length = len(nums)
dp = [1] * length
for i in range(length):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
Reference