一、题目
- 思路1:动态规划
- dp[i]定义为第i个元素结尾的最长递增子序列
- 在[0,i)中找到j,满足nums[j]<nums[i],那么dp[i]=Math.max(dp[i],dp[j]+1)
- 遍历dp[]数组,返回其中的最大值
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
// 初始化:每个元素都是长度为1的子序列
for(int i=0;i<nums.length;i++){
dp[i]=1;
}
for(int i=0;i<nums.length;i++){
// 寻找dp[j]
for(int j=i;j>=0;j--){
if(nums[j]<nums[i]){
// 更新dp[i]
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
}
int ans = Integer.MIN_VALUE;
for(int i=0;i<dp.length;i++){
ans = Math.max(ans,dp[i]);
}
return ans;
}
- 时间复杂度:O(n²),两次循环遍历
- 空间复杂度:O(n)
- 思路2:贪心算法+二分法
dp[i]:长度为i的子序列结尾字符的最小值。因为结尾字符越小,就越有可能跟后面的字符拼接为一个更长的子序列。而每次遇到一个新的元素,都尝试去更新dp[]数组,使得其中的元素更小。(贪心算法:每次取局部最优)
由dp[i]的定义可知,对于dp[],满足i<j,即有dp[i]<dp[j],所以最长递增子序列就是dp[]的长度
证明:对于dp数组中任意的i<j,都有dp[i]<dp[j]
假设dp[i] (a1,a2,...ai)>=dp[j] (b1,b2,...bi,bi+1,...bj),也就是说ai>=bj,由于[bi,bj]是一个单调递增的序列,bi<bj,那么ai>=bj>bi,所以ai>bi,存在一个更小的长度为i的结尾元素,不符合dp数组的定义。因此,dp数组是一个单调递增的序列。
二分法:因为dp数组是有序的,所以寻找需要更新的元素位置时,可以使用二分法快速定位。
public int lengthOfLIS(int[] nums) {
// dp[i]:长度为i+1的子序列的结尾元素的最小值
int[] dp = new int[nums.length];
// 初始化
dp[0]=nums[0];
// 记录dp数组的长度
int len = 0;
// 每次遇到一个新的元素
for(int i=1;i<nums.length;i++){
// 如果比dp[len]大,直接插入到dp数组的末尾
if(nums[i]>dp[len]){
len++;
dp[len]=nums[i];
}
// 使用二分法找到dp[]中第一个大于nums[i]的元素,使其变小
else{
int left = 0;
int right = len;
while(left<right){
int mid = (right+left)/2;
if(nums[i]>dp[mid]){
left = mid+1;
}else{
right = mid;
}
}
// 把nums[i]插入
dp[left] = nums[i];
}
}
// 需要返回长度
len++;
return len;
}
- 时间复杂度:O(nlogn),一次外层循环时间复杂度为n,内层while循环使用二分法时间复杂度为logn。
- 空间复杂度:O(n)
而这种思路跟耐心排序非常相似,可参考文章blog.csdn.net/stronglyh/a…