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)

面试中更常考。

相关推荐
黄敬峰2 小时前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术3 小时前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六7 小时前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术7 小时前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize8 小时前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考1 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
CSharp精选营1 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
美团技术团队1 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
用户805533698031 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
To_OC2 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode