【算法】动态规划专题② ——LIS(最长递增子序列) python

目录

  • 前置知识
  • 问题描述
  • DP解法
  • 小试牛刀
  • 举一反三
  • 实战演练
  • 总结


前置知识


【算法】动态规划专题① ——线性DP python


问题描述


题目是说:
给定一个整数数组,找到其中最长的严格递增子序列的长度。(子序列不要求连续)

比如说,像数组 [10,9,2,5,3,7,101,18],最长递增子序列是 [2,5,7,101],所以长度是4。

那要怎么做呢?


DP解法


对于每个元素,遍历它前面的所有元素,如果前面的元素比它小,那么就可以在这个元素的基础上形成更长的子序列。
再维护一个dp数组,其中dp[ i i i]表示以第 i i i个元素结尾的最长子序列的长度

那具体步骤是怎么样的呢?

初始化dp[ i i i]=1,然后,
对于每个 i i i,从 j j j=0到 i i i-1遍历,
如果nums[ i i i] > nums[ j j j],那么dp[ i i i] = max(dp[ i i i], dp[ j j j] + 1)。

这样处理完所有元素之后,最大的dp值就是结果。


比如上面的例子,数组是[10,9,2,5,3,7,101,18],对应的dp数组应该怎么算?

初始dp都是1。第一个元素10,前面没有元素,dp[0]=1。第二个元素9,比10小,所以不能接在后面,所以dp[1]=1。第三个元素2,同样比前面两个都小,所以dp[2]=1。第四个元素5,前面有元素2,所以dp[3] = dp[2]+1=2。接下来第五个元素3,比它前面的一些元素大吗?比如,3比2大,所以dp[4] = dp[2]+1=2。第六个元素7,前面比它小的有2、5、3,所以这时候要看这些位置上的dp值加1的最大值。比如,2对应的dp是1,5对应的dp是2,3对应的dp是2。所以最大的加1是3,所以dp[5]=3。第七个元素101,前面所有比它小的元素对应的dp值的最大值是3,所以dp[6]=4。最后一个元素18,前面比它小的元素中最大的dp值可能是3(比如7的位置),所以dp[7] =4。那么最大的dp是4,即答案为4。


示例:

a = [10, 9, 2, 5, 3, 7, 101, 18]
n = len(a)
dp = [0] * n
for i in range(n):
    dp[i] = 1  # 单独一个元素本身就是一个长度为1的子序列
    for j in range(i):
        if a[j] < a[i]:
            dp[i] = max(dp[i], dp[j] + 1)
print(max(dp))  # 4(2,3,7,101)


小试牛刀


蓝桥勇士 https://www.lanqiao.cn/problems/2049/learning/?page=1&first_category_id=1&problem_id=2049


题目描述

小明是蓝桥王国的勇士,他晋升为蓝桥骑士,于是他决定不断突破自我。
这天蓝桥首席骑士长给他安排了N个对手,他们的战力值分别为a1,a2,an,且按顺序阻挡在小明的前方。对于这些对手小明可以选择挑战,也可以选择避战。
作为热血豪放的勇士,小明从不走回头路,且只愿意挑战战力值越来越高的对手。
请你算算小明最多会挑战多少名对手。

输入描述:
输入第一行包含一个整数N,表示对手的个数。
第二行包含N个整数a1,a2,······,an,分别表示对手的战力值。
1≤N≤1e3,1≤a≤1e9。

输出描述:
输出一行整数表示答案。

示例输入:

6
1 4 3 2 5 6

示例输出:

4

code实现:

n = int(input())
a = list(map(int, input().split()))
dp = [0] * n
for i in range(n):
    dp[i] = 1 
    for j in range(i):
        if a[j] < a[i]:
            dp[i] = max(dp[i], dp[j] + 1)
ans = max(dp)
print(ans)



举一反三


既然有最长递增子序列,那是不是也得有最长下降子序列
这又该怎么求呢?

类似地:

a = [10, 9, 2, 5, 3, 7, 101, 18]
n = len(a)
dp = [0] * n
for i in range(n - 1, -1, -1):
    dp[i] = 1
    for j in range(i + 1, n):
        if a[i] > a[j]:
            dp[i] = max(dp[i], dp[j] + 1)
print(max(dp))  # 4(10,9,5,3)


好了,实际运用的时候到了

实战演练


合唱队形 https://www.lanqiao.cn/problems/742/learning/?page=1&first_category_id=1&problem_id=742

题目描述

N位同学站成一排,音乐老师要请其中的(N一K)位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,······K,他们的身高分别为T1,T2,······,TK,则他们的身高满足
T1<···Ti+1>···>Tk(1≤i≤K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

输入描述

输入两行
第一行是一个整数N(2≤N≤100),表示同学的总数。
第二行有n个整数,用空格分隔,第i个整数Ti(130≤T≤230)是第i位同学的身高(厘米)。

输出描述

输出一个整数,就是最少需要几位同学出列。

示例输入:

8
186 186 150 200 160 130 197 220 

示例输出:

4

思路分析:

求出最长递增子序列和最长下降子序列相加的最大值 -1 即可


题解code:

n = int(input())
a = list(map(int, input().split()))

dp1 = [0] * n
dp2 = [0] * n
for i in range(n):
    dp1[i] = 1
    for j in range(i):
        if a[i] > a[j]:
            dp1[i] = max(dp1[i], dp1[j] + 1)

for i in range(n - 1, -1, -1):
    dp2[i] = 1
    for j in range(i + 1, n):
        if a[i] > a[j]:
            dp2[i] = max(dp2[i], dp2[j] + 1)

res = max(dp1[x] + dp2[x] - 1 for x in range(n))
print(n - res)


总结


动态规划解法以清晰的逻辑展现 LIS (最长递增子序列)问题的递推本质,是算法学习的经典案例。
当然,此解法时间复杂度是O(n^2),当n较大时,比如1e4或1e5,会超时。
那有没有更优的解法?比如O(n log n)的解法?
答案是有的。二分 + 贪心 的做法,
不过在此就不多赘述了,感兴趣的可以自己思考一下。


END
如果有更多问题或需要进一步的帮助,可以在评论区留言讨论哦!
如果喜欢的话,请给博主点个关注 谢谢

你可能感兴趣的:(算法,动态规划,python)