给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
方法一:动态规划
思路与算法:
定义 dp[i]dp[i] 为考虑前 ii 个元素,以第 ii 个数字结尾的最长上升子序列的长度,注意 \textit{nums}[i]nums[i] 必须被选取。
我们从小到大计算 dp[]dp[] 数组的值,在计算 dp[i]dp[i] 之前,我们已经计算出 dp[0 \ldots i-1]dp[0…i−1] 的值,则状态转移方程为:
dp[i]=max(dp[j])+1,其中0≤j<i且num[j]<num[i]
即考虑往 dp[0 … i-1]dp[0…i−1] 中最长的上升子序列后面再加一个nums[i]。由于 dp[j]dp[j] 代表 \nums[0…j] 中以 nums[j] 结尾的最长上升子序列,所以如果能从 dp[j]dp[j] 这个状态转移过来,那么 nums[i] 必然要大于 nums[j],才能将nums[i] 放在 nums[j] 后面以形成更长的上升子序列。
最后,整个数组的最长上升子序列即所有 dp[i]dp[i] 中的最大值。
官方的解释总是这么恶心 但是把题领悟了之后再去看官方解释也能看得明白 直接上代码
public class 最长上升子序列 {
public int lengthOfLIS(int[] nums){
if(nums.length==0){
return 0;//长度为0即没有
}
int[] dp=new int[nums.length];//创建dp数组与nums数组相同大小
dp[0]=1;
int maxans=1;
for(int i=1;i<dp.length;i++){//这里的i从1开始 因为是从第二个元素与之前元素相比较
int maxval=0;//这里的maxval用来记录每次外循环结束后的最大长度子序列 即i之前的最大长度上升子序列
for(int j=0;j<i;j++){// j 就是从0开始遍历与i比较
if(nums[i]>nums[j]){//如果nums[i]>nums[j]
maxval=Math.max(dp[j],maxval);//这里更新maxval的值 取最大
}
}
dp[i]=maxval+1;
maxans=Math.max(maxans,dp[i]);
}
return maxans;
}
}
1.maxval的作用是找到0…i-1元素中 比num[i]大的元素的最长上升子序列的长度
2.不管里层循环如何, 最后dp[i]都等于maxval+1 ,因为当存在比num[i]大的元素时我们就得获取这些元素的最大上升子序列值 , 当不存在时,那么就是0+1=1
3.最后更新maxans的值,看maxans与dp[i]的值大小 ,最终maxans的值就是最长上升子序列的长度
方法二:贪心+二分算法
public class 最长上升子序列2 {
public static int lengthOfLIS(int[] nums){
int len = 1, n = (int)nums.length;
if (n == 0) return 0;
int[] d=new int[n+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; // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 pos 设为 0
while (l <= r) {
int mid = (l + r) >> 1;
if (d[mid] < nums[i]) {
pos = mid;
l = mid + 1;
}
else r = mid - 1;
}
d[pos + 1] = nums[i];
}
}
return len;
}
}
这里会产生几个问题 ,只是想到用一个判断和条件不符合时用二分法找位置,确实不难 ,但是有两个问题
1.为什么当条件不符合时就要去d数组里找到大于等于该数的最小值并将其替换?
2.为什么最后得到的d数组的长度就是最长上升子序列的长度?
解答:1、比如 3 5 6 2 4 这个序列我们可以把所有的上升序列写出来 (上升序列 单个字符也是上升序列)
单个字符的序列有:“3” “5” “6” “2” “4” 结尾最小的序列是”2“ 结尾数为2
双个字符的序列有:“3,5” “5,6” “2,4” 结尾最小的序列是”2,4“ 结尾数为4
三个字符的序列有:“3,5,6” 结尾最小的序列是”3,5,6“ 结尾数为6
d数组为【2,4,6】
此时我们发现出现一个比6大的数时所有上升序列的 长度都会加一, 但是出现比6小但是比2大的数时有的序列长度加一有的不加,出现5时,就要把6替换 ,d数组就为【2,4,5】,这时是因为2,4,5 和3,5,6都是三字符上升序列,但是将6换成5是为了之后更容易碰到比该数大的数,即碰到比6大的数的概率永远比碰到比5大的数概率小我们就会更容易得到最长字串
2、我们发现d数组中的每一个字符都是该字符(下标加一)个字符的最长上升子序列的结尾最小值
即2为(0+1)个字符上升序列结尾最小值 4为(1+1)个字符上升序列最小值 6为(2+1)个字符上升序列最小值
所以d数组长度就为最长上升子序列的长度