LIS(Longest Increasing Subsequence)最长上升子序列
一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。
比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
你的任务,就是对于给定的序列,求出最长上升子序列的长度。
最长上升子序列具有最优子结构,这个问题可以分解为更小更简单的子问题,每个子问题的最优解可以得到总的最优解。例如对于序列(1,7,3,5,9,4,8)来说, dp[i]的值表示长度为i的序列的最长子序列。那么对于dp[i+1]来说,如果list[i+1] > list[i],可以的得到转移方程:
核心代码如下:
for(int i = 0;i < list.length;i++)
dp[i] = 1; //初始化dp数组,每个位置的最长子序列最低为1,即本身
for(int i = 1;i < list.length;i++){
for(int j = 0;j < i;j++){
if(list[i] > list[j]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
上面代码中dp[]数组存放的值就是当前位置的最长子序列。
结合一道例题: LeetCode300 最长上升子序列
https://leetcode-cn.com/problems/longest-increasing-subsequence/
题目描述:
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
根据之前的动态规划的思想,两层循环,dp[]数组存放当前位置最长子序列的长度,实现代码如下
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length == 0)
return 0;
// Arrays.sort(nums);
int[] dp = new int[nums.length];
for(int i = 0;i < nums.length;i++)
dp[i] = 1;
for(int i = 1;i < nums.length;i++){
for(int j = 0;j < i;j++){ //内层循环遍历之前的所有位置的最长子串,找到当前位置的最长子串长度
if(nums[i] > nums[j])
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
int ans = dp[0];
for(int i = 1;i < dp.length;i++)
ans = Math.max(ans,dp[i]);
return ans;
}
}
上面的算法时间复杂度为O(n^2),空间复杂度为O(n)
还是之前的例题,对于数组{1,7,3,5,9,4,8}来说,使用一个dp序列,存放当前的最长子序列
最后dp={1,3,4,8}(最长序列不唯一,这里得到的是最小的序列)
LeetCode 300 最长子序列的贪心解法代码
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length == 0)
return 0;
List<Integer> dp = new ArrayList<>();
dp.add(nums[0]);
for(int i = 1;i < nums.length;i++){
if(nums[i] > dp.get(dp.size()-1)){
dp.add(nums[i]);
}
else{
int index = Collections.binarySearch(dp,nums[i]);
if(index < 0){
index = -index-1;
dp.set(index,nums[i]);
}
}
}
return dp.size();
}
}
这里算法的时间复杂度为O(nlogn),因为在dp数组中查找第一个大于当前位置的值时,使用二分查找的方法,降低了一部分时间复杂度,查找的时间复杂度为O(logn),在上面的代码中,java容器提供了一个方法,Collections.binarySearch(dp,target),这个方法的返回值如下:
补充:可以自己写一个二分查找的函数 upperBound(int [] num,int beg,int end ,int target)实现查找,具体代码如下:
public static int upperBound(int[] num,int beg,int end,int target){
while(beg < end){
m = (int)(beg+end)/2;
if(num[m] > target)
end = m;
else
beg = m+1;
}
}