这道题主要涉及动态规划,优化时能够考虑贪心算法和二分查找。
<!-- more -->java
给定一个无序的整数数组,找到其中最长上升子序列的长度。git
示例:github
输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:算法
进阶: 你能将算法的时间复杂度下降到 O(n log n) 吗?segmentfault
这也是最基础的想法,利用递归,从每个数开始,一个一个寻找,只要比选中的标准大,那么就以新的数为起点,继续找。所有找完后,找出最长的序列便可。数组
也看一下代码:优化
class Solution { public int lengthOfLIS(int[] nums) { // 递归查询 return recursiveSearch(nums, Integer.MIN_VALUE, 0); } public int recursiveSearch(int[] nums, int standard, int index) { if (nums.length == index) { return 0; } // 若是包含当前index的数字,其递增加度 int tokenLength = 0; if (nums[index] > standard) { tokenLength = 1 + recursiveSearch(nums, nums[index], index + 1); } // 若是不包含当前index的数字,其递增加度 int notTokenLength = recursiveSearch(nums, standard, index + 1); // 返回较大的那个值 return tokenLength > notTokenLength ? tokenLength : notTokenLength; } }
提交以后报超出时间限制
,这个也是预料到的,那么咱们优化一下。spa
仔细分析一下上面的暴力解法,假设 nums 是: [10,9,2,5,3,7,101,18]
,那么从 7 到 101 这个查找,在二、五、3的时候,都曾经查找过一遍。code
那么针对这种重复查找的状况,咱们能够用一个二维数组,记录一下中间结果,这样就能够达到优化的效果。好比用int[][] result
标记为记录中间结果的数组,那么result[i][j]
就表明着从 nums[i - 1] 开始,不管包含仍是不包含 nums[j] 的最大递增序列长度。这样就能保证再也不出现重复计算的状况了。递归
让咱们看看代码:
class Solution { public int lengthOfLIS(int[] nums) { // 记录已经计算过的结果 int result[][] = new int[nums.length + 1][nums.length]; for (int i = 0; i < nums.length + 1; i++) { for (int j = 0; j < nums.length; j++) { result[i][j] = -1; } } // 递归查询 return recursiveSearch(nums, -1, 0, result); } public int recursiveSearch(int[] nums, int preIndex, int index, int[][] result) { if (nums.length == index) { return 0; } // 若是已经赋值,说明计算过,所以直接返回 if (result[preIndex + 1][index] > -1) { return result[preIndex + 1][index]; } // 若是包含当前index的数字,其递增序列最大长度 int tokenLength = 0; if (preIndex < 0 || nums[index] > nums[preIndex]) { tokenLength = 1 + recursiveSearch(nums, index, index + 1, result); } // 若是不包含当前index的数字,其递增序列最大长度 int notTokenLength = recursiveSearch(nums, preIndex, index + 1, result); // 返回较大的那个值 result[preIndex + 1][index] = tokenLength > notTokenLength ? tokenLength : notTokenLength; return result[preIndex + 1][index]; } }
提交OK,可是结果感人,几乎是最慢的了,不管时间仍是空间上,都只战胜了`5%`左右的用户,那就继续优化。 ### 动态规划
假设我知道了从 nums[0] 到 nums[i] 的最大递增序列长度,那么针对 nums[i + 1],我只要去跟前面的全部数比较一下,找出前面全部数中比 nums[i + 1] 小的数字中最大的递增子序列,再加1就是 nums[i + 1] 对应的最大递增子序列。
这样我只要再记录一个最大值,就能够求出整个数组的最大递增序列了。
让咱们看看代码:
class Solution { public int lengthOfLIS(int[] nums) { if (nums.length == 0) { return 0; } // 动态规划,以前几个数字中,有几个比当前数小的,不断更新 // 存储中间结果 int[] dp = new int[nums.length]; // 最大值,由于数组中至少有一个,因此最小是1 int max = 1; // 遍历 for (int i = 0; i < dp.length; i++) { // 当前下标i的最大递增序列长度 int currentMax = 0; for (int j = 0; j < i; j++) { // 若是nums[i]比nums[j]大,那么nums[i]能够加在nums[j]后面,继续构成一个递增序列 if (nums[i] > nums[j]) { currentMax = Math.max(currentMax, dp[j]); } } // 加上当前的数 dp[i] = currentMax + 1; max = Math.max(dp[i], max); } return max; } }
提交OK,执行用时:9 ms
,只打败了75.15%
的 java 提交,看来仍是能够继续优化的。
贪心算法意味着不须要是最完美的结果,只要针对当前是有效的,就能够了。
咱们以前在构造递增序列的时候,实际上是在不断根据以前的值进行更新的,而且十分准确。但其实并不须要如此,只要保证序列中每一个数都相对较小,就能够得出最终的最大长度。
仍是以 [10,9,2,5,3,7,101,18,4,8,6,12]
举例:
2,5
、2,3
都是能够做为递增序列,但明显感受2,3
更合适,由于3更小。2,3,7
。2,3,7,101
。2,3,7,18
2,3,4,18
并非真正的结果,但首先长度上没有问题,其次若是出现新的能够排在最后的数,必定是大于4的,由于要先大于如今的最大值18。序列更新为2,3,4,18
。2,3,4,8
,这样是否是你们开始懂得了这个规律。2,3,4,6
。2,3,4,6,12
。这样也就求出了最终的结果。
结合一下题目说明
里提到的O(nlogn)
,那么就能够想到二分查找,运用到这里也就是找到当前数合适的位置。
接下来让咱们看看代码:
class Solution { public int lengthOfLIS(int[] nums) { if (nums.length == 0) { return 0; } // 贪心 + 二分查找 // 一个空数组,用来存储最长递增序列 int[] result = new int[nums.length]; result[0] = nums[0]; // 空数组的长度 int resultLength = 1; // 遍历 for (int i = 1; i < nums.length; i++) { int num = nums[i]; // 若是num比当前最大数大,则直接加在末尾 if (num > result[resultLength - 1]) { result[resultLength] = num; resultLength++; continue; } // 若是和最大数相等,直接跳过 if (num == result[resultLength - 1]) { continue; } // num比最大值小,则找出其应该存在的位置 int shouldIndex = Arrays.binarySearch(result, 0, resultLength, num); if (shouldIndex < 0) { shouldIndex = -(shouldIndex + 1); } // 更新,此时虽然得出的result不必定是真正最后的结果,但首先其resultLength不会变,以后就算resultLength变大,也是相对正确的结果 // 这里的更新,只是为了让result数组中每一个位置上的数,是一个相对小的数字 result[shouldIndex] = num; } return resultLength; } }
提交OK,执行用时:2 ms
,差很少了。
以上就是这道题目个人解答过程了,不知道你们是否理解了。这道题目用动态规划其实就已经能解决了,但为了优化,还须要用到贪心算法和二分查找。
有兴趣的话能够访问个人博客或者关注个人公众号、头条号,说不定会有意外的惊喜。
公众号:健程之道