LeetCode刷题 --动态规划练习题 --300 最长上升子序列
LeetCode(传送门)最近搞了一个每日刷题的活动,还挺有意思的。前两天的题比较简单,今天的题目比较有意思,记录一下。
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
先来说说笔者自己的思路吧。不难发现这样一个规律,在最长上升子序列中取一个位置截断,剩下的部分在原数组中按同样位置截断的后部分中仍旧是最长上升子序列。可能描述的不是很清楚,按上面题目中最长子序列为2,3,7,101.那么[3,7,101]依旧是原数组从相同位置(2)截断后的后半部分[5,3,7,101,18]的最长上升子序列。那么问题就有些类似与经典的爬楼梯问题,f(n) = f(n-1) + f(n-2), 到达n级的方法等于到达n的前一级的方法加上到达n的前二级的方法的总和。对应到本体中,递归终止的条件是子数组长度小于等于2。如果长度为0或1,那么最长上升子序列的长度为0或1,如果长度为2,则判断两个结点的大小,如果顺序递增则为2,否则是1.
但其实这种解法有种不好处理的地方,就是从前向后遍历数组时,如何判断下一个数组元素是否应该加入到最长子序列中。因为并不能单纯判断大小来决定是否加入,比如数组[1,6,2,3,4]。对于元素6,大于元素1,但最长子序列其实不能包含6. 也许有更好的条件来判断吧。这种思路抛砖引玉了。接下来看看经典的动态规划的方式,上代码:
1 public class Solution { 2 public int LengthOfLIS(int[] nums) 3 { 4 if (nums.Length == 0) 5 { 6 return 0; 7 } 8 9 int[] temp = new int[nums.Length]; 10 temp[0] = 1; 11 int maxResult = 1; 12 13 for (int i = 1; i < nums.Length; i++) 14 { 15 temp[i] = 1; 16 for (int j = i; j >= 0; j--) 17 { 18 if (nums[i] > nums[j]) 19 { 20 temp[i] = Math.Max(temp[i], temp[j] + 1); 21 } 22 } 23 24 maxResult = Math.Max(maxResult, temp[i]); 25 } 26 27 return maxResult; 28 } 29 }
所谓动态规划,其实是根据已有的,以计算出的值来快速解决或推断中新的值。换句话来说就是如果要求得fun(i)的值,那么f(0)~f(i-1)的值已经是求得了。对应的具体解决思路是,构建一个长度等同与数组nums长度的数组,该数组存放的是到达这个下标位置的最长子序列的长度(最长子序列的长度最小为1)。依次便利nums中的元素,对每个元素都进行这样一次检查:检查当前元素nums[i]和它索引下标之前的所有元素的的大小,如果当前元素nums[i]比较大,说明当前元素nums[i]应该加入到最长子序列中。那么对应的temp[i](到达该位置的最长子序列的长度)就是前面所有temp值的最大值加1.因此结果也就是temp中的最大值了。官方解放传送门。
这种解法需要借助一个等长的数组,空间复杂度为O(n),时间复杂度为O(n*n)。官方还提供了一种贪心+二分法的解法,时间复杂度更低。但不是很好理解,不过还是推荐大家看一下。