300-Longest-Increasing-Subsequence
Last updated
Was this helpful?
Last updated
Was this helpful?
给定一个无序的整数数组,找到其中最长上升子序列的长度。
测试用例:
示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明: 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
不知道是否还记得我总结的关于字符串的dp问题。这道题的灵感来自于对字符串的每个分割点进行切分。
初阶做法: 我们随意选定一个下标i
,想求以nums[i]
结尾的最长上升子序列,那么我们就需要在i
之前找到位置j
,使以nums[j]
为结尾的最长的子序列最长,并且nums[i]
能够拼接在nums[j]
后面。那么问题就简单了,定义一个dp数组,dp[i]
表示以nums[i]
结尾的最长上升子序列。这仅仅是初阶做法。
高级做法:
我们想要实现O(nlogn)的复杂度,很容易就想到二分可能是一种方法。那么就得在如何找我们想要的nums[j]
上面下功夫。这里要用到耐心排序的方法。我这里也是拿来主义,别人讲的比较好:
为了简单起见,后文跳过所有数学证明,通过一个简化的例子来理解一下算法思路。
首先,给你一排扑克牌,我们像遍历数组那样从左到右一张一张处理这些扑克牌,最终要把这些牌分成若干堆。并且每一堆都是有序的。
处理这些扑克牌要遵循以下规则:
只能把点数小的牌压到点数比它大的牌上;如果当前牌点数较大没有可以放置的堆,则新建一个堆,把这张牌放进去;如果当前牌有多个堆可供选择,则选择最左边的那一堆放置。
比如说上述的扑克牌最终会被分成这样 5 堆(我们认为纸牌 A 的牌面是最大的,纸牌 2 的牌面是最小的)。
为什么遇到多个可选择堆的时候要放到最左边的堆上呢?因为这样可以保证牌堆顶的牌有序(2, 4, 7, 8, Q),证明略。
按照上述规则执行,可以算出最长递增子序列,牌的堆数就是最长递增子序列的长度,证明略。
我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是有序吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。
对于耐心排序找堆的过程,我们的目标是找到一个堆的堆顶x
跟待插入的数据y
差距最小,对于每个堆,都有堆顶x
>=y
第一次听说耐心排序,查了一下,说它是插入排序的改进。好的,又学会一种新的算法了。:)
普通做法:
进阶做法:
找到一个位置i,推算出如何从位置j计算出位置i,嗯,学会了。
还有耐心排序。
作者:labuladong 链接: