LeetCode0314——最长上升子序列(LIS)

首先搞清楚【子串】和【子序列】这两个名词的区别:

【子串】:一定是连续的

【子序列】:不一定是连续的

 

方法一:动态规划法

一般我们设计动态规划算法都需要一个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

1.题解1

2.题解2

3.题解3

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值