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)

面试中更常考。

相关推荐
郝学胜-神的一滴31 分钟前
Qt 入门 01-01:从零基础到商业级客户端实战
开发语言·c++·qt·程序人生·软件构建
宏笋40 分钟前
C++ thread的detach()方法详解
c++
旖-旎1 小时前
深搜练习(单词搜索)(12)
c++·算法·深度优先·力扣
企客宝CRM2 小时前
2026年中小企业CRM选型指南:企客宝CRM处于什么位置?
android·算法·企业微信·rxjava·crm
橙淮2 小时前
二叉树核心概念与Java实现详解
数据结构·算法
大卡片2 小时前
C++的基础知识点
开发语言·c++
米罗篮2 小时前
DSU并查集 & 拓展欧几里得-逆元
c++·经验分享·笔记·算法·青少年编程
橙淮2 小时前
双指针法:高效算法解题的利器
算法
初心未改HD2 小时前
深度学习之MLP与反向传播算法详解
人工智能·深度学习·算法