动态规划-最长上升子序列问题

个人主页:

<zephyr05>

个人专栏:

<Linux> <C/C++> <竞赛专栏>

座右铭:

等风来不如追风去,追寻的过程就是人生的意义。


目录

[一. 问题定义](#一. 问题定义)

[二. 实现算法](#二. 实现算法)

动态规划(O(n²))

[三. 例题解析](#三. 例题解析)

题目描述:题目链接

思路分析:

代码:


一. 问题定义

最长上升子序列(Longest Increasing Subsequence,简称 LIS)是动态规划中最经典、最基础的问题之一。

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

  • 子序列:不要求连续,但保持原数组的相对顺序。

  • 严格递增 :后一个元素必须 大于 前一个(nums[i] < nums[j])。

nums = [10, 9, 2, 5, 3, 7, 101, 18]

最长上升子序列是 [2, 3, 7, 101][2, 5, 7, 101],长度为 4

二. 实现算法

动态规划(O(n²))

  • 状态定义 :令 dp[i] 表示 nums[i] 结尾 的最长上升子序列的长度。

  • 状态转移 :遍历 i 之前的所有元素 j0 <= j < i),如果 nums[j] < nums[i],说明可以把 nums[i] 接在以 nums[j] 结尾的序列后面,形成更长的序列。

  • 初始化 :每个元素自身至少构成长度为 1 的子序列,所以 dp[i] = 1

  • 最终答案 :取 dp 数组中的最大值。

三. 例题解析

题目描述:题目链接

N位同学站成一排,音乐老师要请其中的 (N-K) 位同学出列,使得剩下的K位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为 1,2...,K,他们的身高分别为 T1,T2,...,TK, 则他们的身高满足 t1<t2...<ti>ti+1>...>tk−1>tk(1≤i≤k) t1​<t2​...<ti​>ti+1​>...>tk−1​>tk​(1≤i≤k)

你的任务是,已知所有 n 位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

思路分析:

题目问"最少出列几人",如果直接想删谁,情况极其复杂,但我们可以转换一下思路,总数 n 固定,最少出列 = n - 最多留下, 因此**,** 我们不再关注踢掉谁,而是全力计算 "最多能留下多少人,组成合唱队形"

合唱队形的数学定义是:t1 < t2 < ... < ti > ... > tk

这个形状像一座山,有且只有一个峰顶 (假设峰顶是第 i 个人),站在峰顶往左看 ,身高严格递增(即最长上升 );站在峰顶往右看 ,身高严格递减(即最长下降)。

现在,把目光锁定在任意一个同学 i 作为峰顶:

  • 左侧(0 到 i) :要求以 arr[i] 结尾,身高严格递增。

    这完美对应了 "以 i 结尾的最长上升子序列"

  • 右侧(i 到 n-1) :要求以 arr[i] 开头,身高严格递减。

    如果我们把右侧从右往左看 ,递减就变成了递增。

    所以它等价于 "以 i 结尾的,从右往左看的最长上升子序列"

为了把上述思路落地,我们定义两个数组 ,分别记录左右两侧的**"最优解"**:

  • fi :表示从左到右,以第 i 个同学作为终点的最长上升子序列长度。

  • gi :表示从右到左,以第 i 个同学作为终点的最长上升子序列长度(等价于原序列中以 i 为起点的最长下降子序列)。

这样一来,如果让第 i 个人当峰顶

  • 左侧最多留下 fi 人(包括峰顶)。

  • 右侧最多留下 gi 人(包括峰顶)。

  • 但峰顶被左边和右边重复计算了两次 ,所以以 i 为峰顶的最大保留人数 = fi+gi−1。

我们并不知道谁最适合当峰顶,但没关系,遍历所有位置 i(0 到 n-1),把每个人假设成峰顶算一遍。

  • 最大保留人数 =

  • 最终答案(最少出列)= n− 最大保留人数

代码:

cpp 复制代码
#include <iostream>
using namespace std;

int n;                 // 同学总数
int arr[1010];        // 存储每位同学的身高,下标从 0 开始
int f[1010];          // f[i]:以 i 结尾的【从左到右】最长上升子序列长度
int g[1010];          // g[i]:以 i 开头的【从右到左】最长上升子序列长度
                      //      等价于以 i 结尾的【从左到右】最长递减子序列长度

int main() {
    // ---------- 1. 读入数据 ----------
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
    }

    // ---------- 2. 计算 f[i]:从左到右的最长上升子序列 ----------
    // 状态转移:f[i] = max( f[j] + 1 ),其中 j < i 且 arr[j] < arr[i]
    for (int i = 0; i < n; i++) {
        f[i] = 1;                     // 至少包含自己,长度为 1
        for (int j = 0; j < i; j++) { // 枚举所有可能的左边元素
            if (arr[i] > arr[j]) {    // 只有严格递增才能接在后面
                // 尝试用 f[j] + 1 更新 f[i]
                f[i] = max(f[i], f[j] + 1);
            }
        }
    }

    // ---------- 3. 计算 g[i]:从右到左的最长上升子序列 ----------
    // 也就是从 i 到末尾的最长递减子序列(因为反过来看是上升)
    // 状态转移:g[i] = max( g[j] + 1 ),其中 j > i 且 arr[i] > arr[j]
    for (int i = n - 1; i >= 0; i--) {
        g[i] = 1;                     // 至少包含自己
        for (int j = i + 1; j < n; j++) { // 枚举所有可能的右边元素
            if (arr[i] > arr[j]) {    // 注意这里是 arr[i] > arr[j],构成递减
                g[i] = max(g[i], g[j] + 1);
            }
        }
    }

    // ---------- 4. 枚举每个位置作为"峰顶",求最大保留人数 ----------
    // 以 i 为峰顶的合唱队形长度 = f[i] + g[i] - 1(i 被算了两次)
    // 需要出列的人数 = n - (最大合唱队形长度)
    int ans = 0x3f3f3f3f;   // 初始化为一个很大的数,用于取最小值
    for (int i = 0; i < n; i++) {
        ans=min(ans,n-(f[i]+g[i]-1));
        // 更新最少出列人数
    }

    // ---------- 5. 输出结果 ----------
    cout << ans << endl;
    return 0;
}

如果本文对你有帮助,欢迎点赞、收藏、分享。如有疑问,请在评论区留言,我会尽力解答。

博客首发于 CSDN,转载请注明出处。

相关推荐
深圳市快瞳科技有限公司1 小时前
个体识别、行为解读、健康管理:多模态宠物AI大模型的场景化落地
人工智能·算法·计算机视觉·大模型·多模态·宠物·宠物ai识别
liulilittle1 小时前
归一化:激活函数
人工智能·算法·机器学习·llm
Mem0rin3 小时前
前缀和:一维与二维
数据结构·算法
小趴蔡ha3 小时前
12 K-Means 聚类入门:没有标签如何发现数据分组
算法·kmeans·聚类
-dzk-3 小时前
【二叉树】LC 101.对称二叉树
算法·二叉树
白狐_7983 小时前
408数据结构第6章:图——核心考点、算法对比与AOE网真题
数据结构·算法
变量未定义~3 小时前
DFS之剪枝与优化-小猫爬山、 数独、 木棒
算法
Navigator_Z4 小时前
LeetCode //C - 1187. Make Array Strictly Increasing
c语言·算法·leetcode
龍德明宇4 小时前
如何理解大语言模型的负主体性-龍德明宇
人工智能·算法·大语言模型llm·负主体性·ai存在论
白狐_7985 小时前
408数据结构第6章:迪杰斯特拉(Dijkstra)算法——真题精讲
数据结构·算法