题目:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
直接上代码,代码中有分析:
第一个方法动态规划实现代码复杂度较低,leetcode跑分地
第二种结合2分法效率较高
public class 最长上升子序列 {
/**
* 动态规划实现:
* 第一步:定义dp数组dp[i] 代表扫描到第i个点的时候,加入i的最长上升子序列(这里注意是i一定要加入的时候的最长子序列),
* 加入i以后通过循环找 0到i-1的节点 中小于nums[i]的最大上升子序列就是当i加入后的dp[i]
* 结果:遍历n个节点取最大的上升子序列dp
* 时间复杂度:O(n平方)
* <p>
* <p>
* 另外还有O(n log n)的时间复杂度就和动态规划没有关系了,是另外一种解法:
* 每次dp[i]只存当遍历到i的时候通过二分查找nums[i]的值在dp中的位置。替换(注意是替换,长度是不变的)
* 为什么要这样?因为这样保证dp中的元素是有序的可以使用二分查找,另外通过替换最小值,可以使后续节点更好的加入dp中举个例子:
* <p>
* nums = 11,12,13,1,2,3,4,5
* 第1步dp=11
* 第2步dp=11,12
* 第3步dp =11,12,13
* 第4步dp = 1,12,13
* 第5步dp =1,2,13
* 第6步dp=1,2,3
* 第7步dp=1,2,3,4
*
* @param nums
* @return
*/
public static int lengthOfLIS(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
int ans = 1;
int n = nums.length;
//第一步定义dp方程
int dp[] = new int[n];
dp[0] = 1;
//第二步刻画状态转移方程
for (int i = 1; i < n; i++) {
int temp = -Integer.MAX_VALUE;
for (int j = i - 1; j >= 0; j--) {
if (nums[j] < nums[i]) {
temp = Math.max(dp[j], temp);
}
}
dp[i] = temp == -Integer.MAX_VALUE ? 1 : temp + 1;
}
//打印答案
for (int l = 0; l < n; l++) {
ans = Math.max(ans, dp[l]);
}
return ans;
}
//二分法
public static int lengthOfLIS2(int[] nums) {
int[] dp = new int[nums.length];
int len = 0;
for (int num : nums) {
int i = Arrays.binarySearch(dp, 0, len, num);
if (i < 0) {
i = -(i + 1);
}
dp[i] = num;
if (i == len) {
len++;
}
}
return len;
}
public static void main(String[] args) {
int[] nums = {-2, -1};
System.out.println(lengthOfLIS(nums));
}