本文最后更新于:2022年4月9日 中午
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5 ,3,7,101,18 ] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101 ],它的长度是 4 。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2 ) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n ) 吗?
Solution
动态规划算法
dp[i]
表示以 nums[i]
这个数结尾的最长递增子序列的长度。
class Solution : def lengthOfLIS (self, nums: List[int ] ) -> int: dp = [1 ]*len (nums) for i in range (len (nums)): for j in range (0 , i): if nums[i]>nums[j]: dp[i]=max (dp[i],dp[j]+1 ) res = 0 for i in range (len (dp)): res=max (res,dp[i]) return res
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int lengthOfLIS (vector <int >& nums) { int n = nums.size(); if (n<2 ) return n; vector <int > dp (n, 1 ) ; for (int i=1 ; i<n; ++i){ for (int j=0 ; j<i; ++j) if (nums[i]>nums[j]) dp[i] = max(dp[i], dp[j]+1 ); } int res = 0 ; for (int i=0 ; i<n; ++i) res = max(res, dp[i]); return res; } };
贪心算法
参考 LeetCode官方
思路:如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution {public : int lengthOfLIS (vector <int >& nums) { int n = nums.size(); if (n<2 ) return n; vector <int > d (n+1 , 0 ) ; int len = 1 ; d[len] = nums[0 ]; for (int i=1 ; i<n; ++i){ if (nums[i] > d[len]) d[++len] = nums[i]; else { int l=1 , r=len, pos=0 ; while (l<=r){ int mid = (l+r)>>1 ; if (d[mid]<nums[i]){ l = mid+1 ; pos = mid; } else { r = mid-1 ; } } d[pos+1 ] = nums[i]; } } return len; } };
给定数组arr, 返回arr的最长递增子序列 。
生成长度为N的数组dp, dp[i]表示在以arr[i]这个数结尾的情况下,arr[0…i]中的最大递增序列长度。
对第一个数arr[0]来说,令dp[0] = 1,接下来,从左到右依次计算出每个数结尾的情况下的最长递增序列长度。
计算dp[i],如果最长递增子序列以arr[i]结尾,那么arr[0,…,i-1]中所有比arr[i]小的数都可以作为倒数第二个数
所以 dp[i] = max{ dp[j] + 1} (0 <=j < i, arr[j] < arr[i]), 如果arr[0,…,i-1]中所有数都不比arr[i]小,令dp[i] = 1。
根据求出的dp数组,得到最长递增子序列 。遍历dp数组,找到最大值以及位置,并开始逆序还原出决策路径。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 vector <int > generateLIS (vector <int > &arr, vector <int > &dp) { int len = 0 ; int index = 0 ; for (int i = 0 ; i < dp.size(); i++) { if (dp[i] > len) { len = dp[i]; index = i; } } vector <int > lis (len, 0 ) ; lis[--len] = arr[index]; for (int i = index; i >= 0 ; i--){ if (arr[i] < arr[index] && dp[i] == dp[index] - 1 ){ lis[--len] = arr[i]; index = i; } } return lis; }
以arr=[2,1,5,3,6,4,8,9,7]为例进行说明:
初始时,dp[0]=1, ends[0]=2, rights = 0, 有效区 ends[0…0],ends[0] = 2, 长度为1,结尾为2
arr[1] = 1, 在有效区ends[0,…0]找最左边大于或等于arr[1]的数,发现ends[0] =2 >arr[1], 表示以arr[1]结尾的最长序列只有一 个,dp[1] = 1, ends[0] = 1 (用1替换了原来的2)
arr[2] = 5, 在有效区ends[0,..0]找最左边大于或等于arr[2]的数,发现并没有,则ends的有效长度+1, end[1] = 5, 有效区扩大,dp[2] = 2. arr[0,1] = {1, 5}
参考:动态规划C++实现–最长递增子序列