LIS算法(最长上升子序列)算法+例题(LeetCode300)

LIS定义

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.动态规划法

最长上升子序列具有最优子结构,这个问题可以分解为更小更简单的子问题,每个子问题的最优解可以得到总的最优解。例如对于序列(1,7,3,5,9,4,8)来说, dp[i]的值表示长度为i的序列的最长子序列。那么对于dp[i+1]来说,如果list[i+1] > list[i],可以的得到转移方程:

  • if(list[i] > list[j]) dp[i] = Math.max(dp[i],dp[j]+1);

核心代码如下:

	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)

2.贪心+二分查找

还是之前的例题,对于数组{1,7,3,5,9,4,8}来说,使用一个dp序列,存放当前的最长子序列

  • {1}的最长子序列为1,此时dp={1}
  • {1,7}此时,7>1,大于dp{}序列的最大值,构成非递减序列,可以把7加入数组中,此时dp= {1,7}
  • {1,7,3}此时,对于3来说,和之前的序列无法构成上升序列,根据贪心的思想,我们要想让序列的长度尽可能的大,那么就应该让加进去的数字尽量小,这样才能保证后面加入的数字尽量多,所以当3 小于此时的边界值7的时候,我们应该在dp{}序列中,找到第一个大于3的数字,并用3替换它,这样可以保证当前的序列尽量小,可以加入的数字尽量多。
  • {1,7,3,5}此时dp={1,3}, 5 > 3,将5加入到dp序列中,dp={1,3,5}
  • 以此类推
  • 。。。

最后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),这个方法的返回值如下:

  • 如果target存在在dp中,那么返回索引的下标
  • 如果不存在,则返回应当存在的位置索引,即第一个大于target的位置索引,返回形式为 -index-1

补充:可以自己写一个二分查找的函数 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;
		}
	}

你可能感兴趣的:(算法,算法,数据结构,leetcode,java,动态规划)