本文为打卡刷Leetcode题系列文章, 这个系列文章的目录都是按照如下四个部分构成
- 题目链接
- 题目描述
- 代码初步
这部分写的是我在刷题过程中的思路,相信在拿到题目就立马参考大神们的思路写代码是不会有进步的,我思故我在,思考让我进步!! - 代码欣赏
这一部分po出优秀的解题答案,这里我们可以学习大神们的解题思路,进而内化成自己的。
题目链接
https://leetcode-cn.com/problems/longest-increasing-subsequence/
题目描述
代码初步
在拿到这个题目时,想到了要用到动态规划。动态规划最重要的是怎么划分状态,一开始我只是想保存并不断更新最长上升子序列的值,但没想出来要如何更新。
后面参考了leetcode讨论区大神的解题思路,才发现其实可以通过记录前面每位数上的最长上升子序列,来求得当前位上的最长上升子序列。
pol一下代码:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
dp = [1] * n
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
# 这里取max的原因是因为可能存在前面的几位最长上升子序列的长度相等,并且当前位都大于它们
dp[i] = max(dp[i], dp[j]+1)
else:
continue
return max(dp)
用到的数据结构与算法:
动态规划是数据结构与算法中必须要掌握的一门算法。但是也不要把它想得很高深。
动态规划的过程:把问题的求解过程分成N个阶段,每个阶段会生成一个状态,用数组保存这些个状态值,最后通过这个保存状态值的数组来求解的过程。
比如这一题的每个状态就是假设数组中总共有N个数,那么遍历数组,求得到第i个数字的时候包含的最长升子序列的个数,保存在另一个新数组里。
代码欣赏
- 时间复杂度改进:你能把代码的时间复杂度提升到O(NlogN),这里看到对数立马想到了二分查找。
- 代码思路: 贪心算法 + 二分查找
贪心法: 向着事情发展最有利的方向前进 - 算法流程:
- 维护一个数组dp, 使整个列表维护着一个排序列表。最长子序列就为数组dp的长度
- 从给定的原数组中依次取出值nums[i],与dp[-1]作比较,
- 此时会出现两种情况,当nums[i]比dp[-1]大时, 将nums[i]直接添加在dp末尾
- 当nums[i] 比dp[-1]小时,需要找到dp中第一个比nums[i]大的数,然后将其替换为nums[i],始终保持dp中元素最小,求得最长子序列就越长。
为什么始终保持dp中元素最小,求得最长子序列就越长?
设常量数字 N,和随机数字 x,我们可以容易推出:当 N 越小时,N<x 的几率越大。例如: N=0 肯定比 N=1000更可能满足 N<x.
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
dp = []
for n in nums:
if not dp or n>dp[-1]:
dp.append(n)
else:
l, r = 0, len(dp) - 1
# 二分法查找dp数组中第一个比nums[i]大的数,替换
while l <= r:
mid = l + (r-l)//2
if n > dp[mid]:
l = mid + 1
else:
r = mid - 1
dp[l] = n
return len(dp)