LeetCode 300. 最长递增子序列(LIS)详解(C语言 | DP + 二分优化)

一、题目描述

给定一个整数数组 nums ,找到其中 最长严格递增子序列 的长度。

子序列定义:

子序列是从原数组中删除(或不删除)一些元素后,保持原有顺序得到的序列。

例如:

复制代码
原数组:
[0,3,1,6,2,2,7]

合法子序列:
[3,6,2,7]
[0,1,2,7]

示例 1

复制代码
输入:
nums = [10,9,2,5,3,7,101,18]

输出:
4

解释:
最长递增子序列为:
[2,3,7,101]

示例 2

复制代码
输入:
nums = [0,1,0,3,2,3]

输出:
4

示例 3

复制代码
输入:
nums = [7,7,7,7,7]

输出:
1

二、解法一:动态规划(DP)

1 思路分析

定义状态:

复制代码
dp[i] 表示:
以 nums[i] 结尾的最长递增子序列长度

初始化:

复制代码
dp[i] = 1

因为每个元素本身就是一个长度为1的序列。


状态转移

遍历 j < i

如果:

复制代码
nums[j] < nums[i]

说明可以接在后面:

复制代码
dp[i] = max(dp[i], dp[j] + 1)

最终答案

复制代码
max(dp[i])

2 动态规划图解

示例:

复制代码
nums = [10,9,2,5,3,7,101,18]

DP 变化:

复制代码
10 -> 1
9  -> 1
2  -> 1
5  -> 2
3  -> 2
7  -> 3
101 -> 4
18 -> 4

最长为:

复制代码
4

3 C语言实现

复制代码
#include <stdlib.h>

int max(int a,int b)
{
    return a>b?a:b;
}

int lengthOfLIS(int* nums, int numsSize)
{
    int *dp = (int*)malloc(sizeof(int)*numsSize);
    int ans = 1;

    for(int i=0;i<numsSize;i++)
        dp[i] = 1;

    for(int i=1;i<numsSize;i++)
    {
        for(int j=0;j<i;j++)
        {
            if(nums[j] < nums[i])
            {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }

        ans = max(ans, dp[i]);
    }

    free(dp);
    return ans;
}

时间复杂度

复制代码
时间复杂度:O(n²)
空间复杂度:O(n)

n <= 2500 时可以通过。


三、解法二:贪心 + 二分查找(最优解)

这是 面试最推荐的方法

复杂度:

复制代码
O(n log n)

1 核心思想

维护一个数组:

复制代码
tails[i]

表示:

复制代码
长度为 i+1 的递增子序列的最小结尾元素

关键点:

tails 数组 不是实际的子序列,只是用于维护最优状态。


2 过程演示

示例:

复制代码
nums = [10,9,2,5,3,7,101,18]

过程:

复制代码
10
9
2
2 5
2 3
2 3 7
2 3 7 101
2 3 7 18

最终长度:

复制代码
4

3 二分查找更新

每次处理 nums[i]

tails 中找到:

复制代码
第一个 >= nums[i] 的位置

替换:

复制代码
tails[pos] = nums[i]

如果没找到:

复制代码
追加到末尾

4 C语言实现

复制代码
#include <stdlib.h>

int lengthOfLIS(int* nums, int numsSize)
{
    int *tails = (int*)malloc(sizeof(int)*numsSize);
    int size = 0;

    for(int i=0;i<numsSize;i++)
    {
        int left = 0;
        int right = size;

        while(left < right)
        {
            int mid = (left + right) / 2;

            if(tails[mid] < nums[i])
                left = mid + 1;
            else
                right = mid;
        }

        tails[left] = nums[i];

        if(left == size)
            size++;
    }

    free(tails);
    return size;
}

四、算法过程示例

输入:

复制代码
nums = [0,1,0,3,2,3]

过程:

复制代码
0
0 1
0 1
0 1 3
0 1 2
0 1 2 3

最终:

复制代码
LIS = 4

五、两种方法对比

方法 时间复杂度 空间复杂度 难度
动态规划 O(n²) O(n)
贪心 + 二分 O(n log n) O(n) ⭐⭐⭐

六、面试常见问题

1 什么是 LIS?

复制代码
最长严格递增子序列
不要求连续
但顺序不能改变

2 为什么可以用二分?

因为:

复制代码
tails数组始终保持递增

所以可以:

复制代码
二分查找插入位置

3 tails 数组是不是 LIS?

不是。

复制代码
tails只是维护最优尾值

例如:

复制代码
tails = [2,3,7,18]

真实 LIS 可能是:

复制代码
[2,5,7,101]

七、总结

解决 LIS 的核心方法:

方法一

复制代码
动态规划
O(n²)

思路清晰,容易理解。


方法二(推荐)

复制代码
贪心 + 二分查找
O(n log n)

面试中更常考。

相关推荐
Darkwanderor2 小时前
数据结构——trie(字典)树
数据结构·c++·字典树·trie树
灰色小旋风2 小时前
力扣第11题C++盛最多水的容器
数据结构·算法·leetcode
一匹电信狗2 小时前
【LeetCode面试题17.04】消失的数字
c语言·开发语言·数据结构·c++·算法·leetcode·stl
j_xxx404_2 小时前
从 O(N) 到 O(log N):LCR 173 点名问题的五种解法与最优推导
开发语言·c++·算法
xxxxxxllllllshi2 小时前
【LeetCode Hot100----12-栈(01-06),包含多种方法,详细思路与代码,让你一篇文章看懂所有!】
算法·leetcode·职场和发展
User_芊芊君子2 小时前
【LeetCode经典题解】平衡二叉树高效判断:从O(n²)到O(n)优化
算法·leetcode·职场和发展
五月茶2 小时前
力扣Hot100(Java版本)
java·算法·leetcode
自信150413057592 小时前
数据结构之单链表OJ复盘
c语言·数据结构·算法
仰泳的熊猫2 小时前
题目2265:蓝桥杯2015年第六届真题-移动距离
开发语言·数据结构·c++·算法·蓝桥杯