题目来源:https://leetcode-cn.com/problems/longest-increasing-subsequence/
1. 给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
解法一:动态规划
- 初始化状态数组为1,dp[i]表示前i个数字中,最长上升子序列的最大长度。(初始化为1是因为单个数字必定是上升子序列)
- 状态转移方程:对于i∈[0,n),对于j∈[0,i),即nums[j]是nums[i]左边的项,如果nums[j]<nums[i],那么上升子序列长度在dp[j]的基础上加一,最长上升子序列长度为max(dp[i], dp[j]+1),因为j有很多个,更新的dp[j]+1不一定大于前面更新的dp[i]。否则什么也不做。
class Solution(object):
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n <= 1:
return n
dp = [1 for i in range(n)]
for i in range(1,n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j]+1)
return max(dp)
解法二:二分查找
方法一最后的搜索过程复杂度为,外层O(n)是动不了的,可以考虑对内层循环优化,让内层复杂度变为O(logn)。
对于nums[i](i∈[0,len(nums))),如果nums[i]大于tails最大的元素,则直接加到tails尾部。否则,从tails中找到第一个比nums[i]大的元素,用nums[i]替换掉它。tails是一个最长上升子序列。
class Solution:
def lengthOfLIS(self, nums):
n = len(nums)
if n <= 1:
return n
tails = [nums[0]]
for i in range(n): # 外层循环复杂度依然为O(n)
if nums[i] > tails[-1]: # nums[i]大于tails最大值,则直接加到尾部
tails.append(nums[i])
continue # 没必要做下面的工作,所以结束本次循环,开始下一次
low = 0 # 二分查找tails中比nums[i]大的项的最小索引
high = len(tails) - 1
while low < high:
mid = low + (high - low) // 2
if nums[i] > tails[mid]:
low = mid + 1
else:
high = mid
tails[low] = nums[i] # 替换为nums[i]
return len(tails)
2. 给定一个无序的整数数组,输出其中一组最长上升子序列的的下标集合。
LIC的变型,先计算最长上升子序列的长度,记为max_len,从max_len开始每次递减1,并取相应的下标。
class Solution(object):
def findLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n == 0:
return 0
dp = [1 for _ in range(n)]
res = []
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
max_dp = max(dp)
while max_dp:
res.append(dp.index(max_dp)) # 取下标
max_dp -= 1
return([res::-1])
3. 给定一个未排序的整数数组,找到最长递增子序列的个数。
来源:https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/
额外维护一个数组counts,记录以每个整数结尾的最长上升子序列的个数。
class Solution(object):
def findNumberOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
if n == 0:
return 0
dp = [1 for _ in range(n)]
counts = [1 for _ in range(n)]
cnt = 0
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
if dp[j] >= dp[i]: # 正常情况下,应有dp[j] <= dp[i],如果不满足则说明dp[i]还没更新好,更新dp[i]
dp[i] = dp[j] + 1
counts[i] = counts[j] # 相当于初始化
elif dp[j] + 1 == dp[i]: # 如果dp[i]之前有连续相等的dp[j],那么从第二个开始,都会有dp[i] == dp[j] + 1,而counts[j]是相互独立的(因为分别以nums[j]结尾),所以每次都要加上counts[j]
counts[i] += counts[j]
max_len = max(dp)
for i in range(n):
if dp[i] == max_len: # 最长上升子序列可能有多个
cnt += counts[i]
return cnt
参考: