LintCode领扣 题解丨字节跳动高频题:最长上升子序列

简介: LintCode领扣 题解丨字节跳动高频题:最长上升子序列

给定一个整数序列,找到最长上升子序列(LIS),返回LIS的长度。

在线评测地址:
https://www.lintcode.com/problem/longest-increasing-subsequence/description?utm_source=sc-tianchi-sz0828

说明

最长上升子序列的定义:

最长上升子序列问题是在一个无序的给定序列中找到一个尽可能长的由低到高排列的子序列,这种子序列不一定是连续的或者唯一的。


样例 1:

输入:  [5,4,1,2,3]
输出:  3

解释:
LIS 是 [1,2,3]

样例 2:

输入: [4,2,4,5,3,7]
输出:  4

解释: 
LIS 是 [2,4,5,7]

算法:动态规划(dp)
算法思路

因为所求为子序列,很容易想到一种线性动态规划。
对于求最长上升子序列,上升我们肯定需要知道当前阶段最后一个元素为多少,最长我们还要知道当前我们的序列有多长。
那么我们就可以这样定义状态:设 dp[i] 表示以 nums[i] 为结尾的最长上升子序列的长度,为了保证元素单调递增肯定只能从 i 前面且末尾元素比 nums[i] 小的状态转移过来
代码思路

状态转移方程为

每个位置初始值为 dp[i]=1(将自己作为一个序列)
答案可以是任何阶段中只要长度最长的那一个,所以我们边转移边统计答案
复杂度分析

N表示为nums 的长度

空间复杂度:O(N)
时间复杂度:O(N^2)
public class Solution {

/**
 * @param nums: The integer array
 * @return: The length of LIS (longest increasing subsequence)
 */
public int longestIncreasingSubsequence(int[] nums) {
    // dp[i]表示以nums[i]为结尾的最长上升子序列的长度
    int []dp = new int[nums.length];
    int ans = 0;
    for (int i = 0; i < nums.length; i++) {
        // 初始值为1
        dp[i] = 1;
        for (int j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                // 若nums[j] < nums[i]那么可以接在该序列后,更新状态
                dp[i] = dp[i] > dp[j] + 1 ? dp[i] : dp[j] + 1;
            }
        }
        // 记录所有状态中的最大值
        if (dp[i] > ans) {
            ans = dp[i];
        }
    }
    return ans;
}

}
优化算法:动态规划(dp)+二分优化
上面说过:我们肯定要知道当前阶段最后一个元素为多少,还有当前的序列有多长。上面的方法是用前者做状态,即元素是多少,那么我们可不可以用后者,即序列长度做状态呢?

算法思路

设 dp[i] 表示长度为 i 的最长上升子序列的末尾元素的最小值,显然这个数组的权值一定单调不降。
于是我们按顺序枚举数组nums,每一次对dp数组二分查找,找到小于nums[i]的最大的 dp[j],并更新 dp[j+1]。
复杂度分析

空间复杂度:O(N)
时间复杂度:O(NlogN)
public class Solution {

/**
 * @param nums: An integer array
 * @return: The length of LIS (longest increasing subsequence)
 */
public int longestIncreasingSubsequence(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    
    int[] lis = new int[nums.length + 1];
    lis[0] = Integer.MIN_VALUE;
    for (int i = 1; i <= nums.length; i++) {
        lis[i] = Integer.MAX_VALUE;
    }
    
    int longest = 0;
    for (int i = 0; i < nums.length; i++) {
        int index = firstGTE(lis, nums[i]);
        lis[index] = nums[i];
        longest = Math.max(longest, index);
    }
    
    return longest;
}

// GTE = Greater Than or Equal to
private int firstGTE(int[] nums, int target) {
    int start = 0, end = nums.length - 1;
    while (start + 1 < end) {
        int mid = start + (end - start) / 2;
        if (nums[mid] >= target) {
            end = mid;
        } else {
            start = mid;
        }
    }
    if (nums[start] >= target) {
        return start;
    }
    return end;
}

}
更多题解参见九章算法官网:
https://www.jiuzhang.com/solution/longest-increasing-subsequence/?utm_source=sc-tianchi-sz0828

相关文章
LeetCode每日一题——522. 最长特殊序列 II
给定字符串列表 strs ,返回其中 最长的特殊序列 。如果最长特殊序列不存在,返回 -1 。
97 0
【leetcode】力扣 --- 日积月累,每日一题——8 长度最小的子数组
随笔录:如果忙碌了一天突然想到自己没有打卡,你会不会去刷个题来打卡,说实话以前我是没这种想法的,我宁愿去躺着也不想去学习。但是发现抱团学习,有很大的奇效,而且还有奖励,那就是更美滋滋了,如果你想和其他人一起抱团学习,一起学习,那就来加入社区吧。每周都会有书籍、周边等奖励发放。今天拿到了上周我得到的diy手机壳奖励,觉得自己学到东西还有奖励拿就美滋滋。哈哈哈 社区链接 长度最小的子数组 一、题目 二、代码及思路
【leetcode】力扣 --- 日积月累,每日一题——8 长度最小的子数组
[leetcode/lintcode 题解] 阿里面试高频题:岛屿的个数
[leetcode/lintcode 题解] 阿里面试高频题:岛屿的个数
[leetcode/lintcode 题解]  阿里面试高频题:岛屿的个数
[leetcode/lintcode 题解]国内大厂面试真题详解:摊平二维向量
[leetcode/lintcode 题解]国内大厂面试真题详解:摊平二维向量
[leetcode/lintcode 题解]国内大厂面试真题详解:摊平二维向量
[leetcode/lintcode 题解] 算法面试真题详解:最频繁出现的子串
[leetcode/lintcode 题解] 算法面试真题详解:最频繁出现的子串
[leetcode/lintcode 题解] 算法面试真题详解:最频繁出现的子串
[leetcode/lintcode 题解]算法面试高频题详解: 对称树
[leetcode/lintcode 题解]算法面试高频题详解: 对称树
[leetcode/lintcode 题解]算法面试高频题详解: 对称树
[leetcode/lintcode 题解] 算法面试高频题详解:最大时刻
[leetcode/lintcode 题解] 算法面试高频题详解:最大时刻
[leetcode/lintcode 题解] 算法面试高频题详解:最大时刻
[leetcode/lintcode 题解] 阿里算法面试题:切割剩余金属
[leetcode/lintcode 题解] 阿里算法面试题:切割剩余金属
[leetcode/lintcode 题解] 阿里算法面试题:切割剩余金属
|
机器学习/深度学习 算法
[leetcode/lintcode 题解] 算法面试高频题详解:亮起时间最长的灯
[leetcode/lintcode 题解] 算法面试高频题详解:亮起时间最长的灯
[leetcode/lintcode 题解] 算法面试高频题详解:亮起时间最长的灯