文章目录
- 题目介绍
- 题解1:往回遍历
- 题解2:二分查找
题目介绍
- 原题链接:NC163 最长上升子序列(一)
- 描述
给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列
我们定义一个序列是 严格上升 的,当且仅当该序列不存在两个下标i和j满足 i<ji<j 且 - 数据范围:
- 要求:时间复杂度, 空间复杂度
- 示例1
输入:
[6,3,1,5,2,3,7]
- 返回值:
4
- 说明:该数组最长上升子序列为 [1,2,3,7] ,长度为4
题解1:往回遍历
建议新手白脖子先看视频教程:【小鱼老师算法题】python讲解动态规划思想的最长递增子序列问题 另外我的题解是为了方便在本地IDE进行调试,所以没有新建类,而是使用exec函数
li = []
exec('li=' + input())
dp = [1] * len(li)
for i in range(1, len(li)):
for j in range(i):
if li[i] > li[j]:
dp[i] = max(dp[i], dp[j]+1)
print(max(dp) if li else 0)
- 知识点:动态规划
动态规划算法的基本思想是:将待求解的问题分解成若干个相互联系的子问题,先求解子问题,然后从这些子问题的解得到原问题的解;对于重复出现的子问题,只在第一次遇到的时候对它进行求解,并把答案保存起来,让以后再次遇到时直接引用答案,不必重新求解。动态规划算法将问题的解决方案视为一系列决策的结果 - 思路:
要找到最长的递增子序列长度,每当我们找到一个位置,它是继续递增的子序列还是不是,它选择前面哪一处接着才能达到最长的递增子序列,这类有状态转移的问题常用方法是动态规划 - 具体做法:
step 1:用dp[i]表示到元素i结尾时,最长的子序列的长度,初始化为1,因为只有数组有元素,至少有一个算是递增
step 2:第一层遍历数组每个位置,得到n个长度的子数组
step 3:第二层遍历相应子数组求对应到元素i结尾时的最长递增序列长度,期间维护最大值
step 4:对于每一个到iii结尾的子数组,如果遍历过程中遇到元素j小于结尾元素,说明以该元素结尾的子序列加上子数组末尾元素也是严格递增的,因此转移方程为 - 图示:
题解2:二分查找
dp[i]表示从左至右到原序列第i个元素的最长递增子序列的长度,从第i个元素往回遍历更新dp[i]的值。由于每个元素都需要往回遍历一次,时间复杂度是。往回遍历如何更新dp[i]的值在题解1中已有很好的介绍,这里主要写用二分法代替往回遍历的过程,时间复杂度是。
二分法的过程为:首先创建数组arr=[ele_1],ele_1是原序列第一个元素,然后从第二个元素开始从左至右遍历原序列
- 如果ele_i > max(arr),将ele_i加到arr最后
- 如果ele_i <= max(arr),用二分法找到arr中第一个比ele_i大(或相等)的元素并用ele_i替换
遍历完成后arr的长度即为最长递增子序列的长度(但arr不是最长递增子序列)。第二步替换是因为遍历到的元素可能会有比ele_i大但比替换元素小的元素,比如原序列为[2,5,8,3,4,6]。
import bisect
li = []
exec('li=' + input())
if not li:
print(0)
exit()
dp = [1] * len(li)
sorted_list = [li[0]]
for i in range(1, len(li)):
if li[i] > sorted_list[-1]:
sorted_list.append(li[i])
dp[i] = len(sorted_list)
else:
position = bisect.bisect_left(sorted_list, li[i])
sorted_list[position] = li[i]
dp[i] = position+1
print(max(dp))