300. 最长上升子序列

251 阅读3分钟

leetcode原题链接

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。 说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。 你算法的时间复杂度应该为 O(n²) 。 进阶: 你能将算法的时间复杂度降低到 O(n * log n) 吗?

思路

蛮力法找出所有子序列,共有2^n个子序列;如果每个子序列都进行检查,则总的时间复杂度为 O(n * n²)。复杂度太高,下面考虑动态规划的解法。

1. 动态规划O(n²)

1. 定义状态

dp[i]表示以i元素结尾的最长上升子序列的长度。即在 [0, ..., i] 的范围内,选择以数字nums[i]结尾可以获得的最长上升子序列的长度。

注意:以第 i 个数字为结尾,即要求 nums[i] 必须被选取。反正一个子序列一定会以一个数字结尾,那我就将状态这么定义,这一点是常见的。

2. 状态转移方程

遍历索引是i的元素时,需要检查 [0,i-1]的所有dp,如果nums[i]严格大于之前的某个nums[j],则将nums[i]接到这个数后必然能够形成一个更长的上升子序列;

此时dp[i]为他们的最大值+1。

状态转移方程:dp(i) = max( 1 + dp(j) if j < i and num[i] > num[j])

///动态规划
///时间复杂度O(n²)
///空间复杂度:O(n)
func lengthOfLIS(_ nums: [Int]) -> Int {
    if nums.count <= 1 {
        return nums.count
    }
    // 表示以当前元素结尾的最长上升子序列长度;当前元素必须使用
    var dp = [Int](repeating: 1, count: nums.count)
    
    var maxLen = 0
    for i in 1..<nums.count {
        for j in 0..<i {
            if nums[i] > nums[j] {
                dp[i] = max(dp[i], dp[j] + 1)
            }
        }
        maxLen = max(dp[i], maxLen)
    }
    return maxLen
}

2 动态规划+贪心+二分查找

维护一个数组dp,当出现的数大于这个数组直接append,否则替换掉数组中大于等于这个数的最小值。最后dp的长度就是最长上升子序列的长度 而dp数组是一个有序数组,使用二分查找复杂度为O(log n)

每一次来一个新的数 num,就找 dp 数组中第一个大于等于 num 的那个数,试图让它变小,以致于新来的数有更多的可能性接在它后面,成为一个更长的“上升子序列”,这是“贪心算法”的思想。

/*
* 最长上升子序列:动态规划+贪心+二分查找
* 时间复杂度O(NlogN) 空间复杂度O(N)
*/
///时间复杂度:O(nlogn)
///空间复杂度:O(n)
func lengthOfLIS2(_ nums: [Int]) -> Int {
    if nums.count <= 1 {
        return nums.count
    }
    /**
    dp[i]: 所有长度为i+1的递增子序列中, 最小的那个序列尾数.
    由定义知dp数组必然是一个递增数组, 可以用 maxL 来表示最长递增子序列的长度.
    对数组进行迭代, 依次判断每个数num将其插入dp数组相应的位置:
    1. num > dp[maxL], 表示num比所有已知递增序列的尾数都大, 将num添加入dp
       数组尾部, 并将最长递增序列长度maxL加1
    2. dp[i-1] < num <= dp[i], 只更新相应的dp[i]
    **/
    var dp = [Int](repeating: 1, count: nums.count)
    
    var maxLen = 0
    for num in nums {
        var left = 0
        var right = maxLen
        while left < right {
            let mid = left + (right - left) / 2
            if dp[mid] < num {
                left = mid + 1
            } else {
                right = mid
            }
        }
        dp[left] = num;
        if left == maxLen {
            maxLen += 1
        }
    }
    return maxLen
}