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 分钟前
宠物行为识别:将日常行为转化为可量化的健康指标
人工智能·算法·计算机视觉·宠物
稚南城才子,乌衣巷风流20 分钟前
长链剖分(Long Chain Decomposition)算法详解
算法·深度优先·图论
小保CPP27 分钟前
OpenCV C++基于模糊数学的图像滤波
c++·人工智能·opencv·计算机视觉
变量未定义~33 分钟前
环境治理、染色时间、小蓝组网
算法
Ulyanov1 小时前
雷达导引头仿真中的坐标系——从惯性系到量测角
开发语言·python·算法·系统仿真·雷达信号处理·雷达导引头
玖玥拾1 小时前
LeetCode 13 罗马数字转整数
笔记·算法·leetcode
m沐沐1 小时前
【计算机视觉】OpenCV 物体跟踪——原理、算法与CSRT跟踪器实战
人工智能·python·深度学习·opencv·算法·计算机视觉·人脸识别
明日清晨1 小时前
VS2022禁用 NRVO/返回值优化(不调用拷贝构造函数处理方式)
c++
起个破名想半天了2 小时前
算法与数据结构之Kruskal算法
数据结构
阿米亚波2 小时前
【C++ STL】std::array
java·开发语言·c++