300 Longest Increasing Subsequence

本文探讨了如何求解最长递增子序列(LIS)问题,通过动态规划方法给出了O(n^2)复杂度的解决方案,并进一步介绍了如何利用二分查找优化至O(n log n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 

Note:

  • There may be more than one LIS combination, it is only necessary for you to return the length.
  • Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?

这是一个多阶段决策问题,求最长,是一个最优化问题,用 BFS, 贪心或DP。 如果用BFS,首先用数组中的所有元素作为根节点,形成n颗树,每棵树开始往下
扩展,出现逆序则终止,最后计算每棵树的高度,取最大,就是最终结果。算法复
杂 度 为 O(n!) 。
本题中,一个节点往下扩展的时候,没有一个确定的准则,让你走哪些边,本题不 具有贪心选择性质,因此不能用贪心法。
我们来尝试用DP来解决这题。最重要的是要定义出状态。首先从状态扩展这方面看,对于数组中的一个元素,它往后走,凡是比它大的元素,都可以作为下一步, 因此这里找不到突破口。
我们换一个角度,从结果来入手,我们要求的最长递增子序列,一个递增子序列, 肯定是有首尾两个端点的,假设我们定义 f[i] 为以第 i 个元素为起点的最长递增子序列,那么 f[i] 和 f[j] 之间没有必然联系,这个状态不好用。
假设定义 f[i] 为以第 i 个元素为终点的最长递增子序列,那么如
果 i<j 且 nums[i]<nums[j] ,则 f[i] 一定是 f[j] 的前缀。这个状态能表示子问题之间的关系,可以接着深入下去。
现在正式开始定义,我们定义状态 f[i] 为第 i 个元素为终点的最长递增子序列的长度,那么状态转移方程是
f[j] = max{f[j], (0 <= i < j && nums[i] < nums[j]) f[i]+ 1}
有了状态和状态转移方程,代码就不难写了。

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums.length == 0 || nums == null)
            return 0;
        int[] f = new int[nums.length];
        Arrays.fill(f, 1);
        int global = 1;
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i])
                    f[i] = Math.max(f[i], f[j] + 1);
            }
            global = Math.max(global, f[i]);
        }
        return global;
    }
}
本题最后有一个进阶问题,能不能 O(n log n) 解决?有。
维护一个单调递增序列,遍历数组,二分查找每一个数在单调序列中的位置,然后 替换之。
class Solution {
    public int lengthOfLIS(int[] nums) {
        List<Integer> lis = new ArrayList<>();
        for (int x : nums) {
            int inPosition = lowerBound(x, lis, 0, lis.size());
            if (inPosition >= lis.size())
                lis.add(x);
            else
                lis.set(inPosition, x);
        }
        return lis.size();
    }
    
    private int lowerBound(int x, List<Integer> lis, int start, int end) {
        while (start < end) {
            int mid = start + (end - start) / 2;
            if (x == lis.get(mid))
                return mid;
            else if (x < lis.get(mid))
                end = mid;
            else
                start = mid + 1;
        }
        return start;
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值