
个人主页:
<zephyr05>
个人专栏:
座右铭:
等风来不如追风去,追寻的过程就是人生的意义。
目录
[一. 问题定义](#一. 问题定义)
[二. 实现算法](#二. 实现算法)
[三. 例题解析](#三. 例题解析)
一. 问题定义
最长上升子序列(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之前的所有元素j(0 <= 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,转载请注明出处。