算法笔记/USACO Guide GOLD金组DP 4. Longest Increasing Subsequence

news/2024/10/28 4:32:17/

Has Not Appeared. *

理解最长递增子序列(LIS)问题是一个经典的DP问题。在给定的数组中,目标是找到最长的严格递增的子序列

子序列

子序列是通过删除一些或不删除元素,从原数组中得到的序列,且保留原顺序。例如,在数组 `[3, 10, 2, 1, 20]` 中,`[3, 10, 20]` 就是一个子序列。

例题:

问题概述

给定一个数组 `A`,找到其中最长的子序列,且子序列中的每个元素都比前一个元素大。这就是LIS问题。

朴素动态规划解决方案 (时间复杂度 O(N^2))

朴素方法使用动态规划(DP),时间复杂度为 O(N²)。该方法逻辑简单,但在处理较大数组时效率低。

朴素动态规划方法解释:

1. 创建一个 DP 数组 `dp[]`,其中每个元素 `dp[i]` 表示以索引 `i` 结尾的最长递增子序列的长度。

2. 对于每个元素 `A[i]`,遍历之前的所有元素 `A[j]`(`j < i`)。如果 `A[j] < A[i]`,则 `A[i]` 可以扩展以 `A[j]` 结尾的子序列。

3. 更新 `dp[i]`,使其为 `dp[i]` 和 `dp[j] + 1` 的较大值。

4. 最后的结果是 `dp[]` 数组中的最大值。

#include <vector>using namespace std;int find_lis(const vector<int>& a) {int lis = 0;vector<int> dp(a.size(), 1);for (int i = 0; i < a.size(); i++) {for (int j = 0; j < i; j++) {if (a[j] < a[i]) {dp[i] = max(dp[i], dp[j] + 1);}}lis = max(lis, dp[i]);}return lis;}

时间复杂度:O(N²),因为对于每个元素,我们都要遍历之前的所有元素以计算最长子序列。

空间复杂度:O(N) 用于存储 DP 数组。

效率不高。

二分查找方法:

通过维护一个递增序列,并使用**二分查找**进行高效更新,优化后的方法将时间复杂度降低到 O(N log N)。

关键思路:

与其维护一个完整的 DP 数组,我们可以维护一个代表不同长度递增子序列的最小可能尾元素的列表。通过二分查找,我们可以在对数时间内高效地更新该列表。

1. 维护一个列表 `dp[]`,其中 `dp[i]` 保存长度为 `i+1` 的递增子序列的最小尾值。

2. 对于每个元素 `A[i]`,使用**二分查找**找到 `A[i]` 可以扩展子序列的位置。

3. 如果 `A[i]` 大于 `dp[]` 中的所有元素,将其添加到列表末尾。

4. 如果 `A[i]` 可以替换 `dp[]` 中的某个元素(通过二分查找找到),则替换该元素,以确保 `dp[]` 始终保持最小可能尾元素。

5. 最终,`dp[]` 数组的长度就是最长递增子序列的长度。

优化代码

#include <vector>#include <algorithm>using namespace std;int find_lis(const vector<int>& a) {vector<int> dp;for (int i : a) {// 使用二分查找找到要替换的位置int pos = lower_bound(dp.begin(), dp.end(), i) - dp.begin();if (pos == dp.size()) {dp.push_back(i);  // 可以扩展子序列} else {dp[pos] = i;  // 替换元素以保持最小可能值}}return dp.size();}

- **时间复杂度**:由于每个元素的二分查找,时间复杂度为 O(N log N)。

- **空间复杂度**:O(N) 用于存储 `dp[]` 列表。

常见的LIS应用

Non-intersecting Segments

一个相关问题是找到最大数量的不相交线段。这个问题可以简化为 LIS 问题,将线段视为区间,并确保没有两个区间重叠。

Minimum Number of Increasing Sequences

在另一个应用中,目标是将一个数组划分为最少数量的递增子序列。这个问题可以通过计算最长非递增子序列的长度来解决,并使用该长度来对数组进行划分。

Summary

问题是动态规划和算法中的基础问题,但是在金赛中还未出现过。尽管 O(N²) 的朴素解决方案适用于较小数组,但使用二分查找优化的 O(N log N) 方法对于处理大型数据集至关重要。理解这个问题不仅能增强算法技能,还能为解决各种与序列相关的问题铺平道路。


http://www.ppmy.cn/news/1542520.html

相关文章

基于SpringBoot的旅店管理系统的设计与实现源码+Vue前端(酒店、民宿、功能较多)

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

重学SpringBoot3-Spring WebFlux之Reactor事件感知 API

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ Spring WebFlux之Reactor事件感知 API 1. 什么是 doOnXxx 系列 API&#xff1f;2. doOnXxx API 的常用方法2.1 doOnNext()示例&#xff1a;输出&#xff1a; 2.2 doOnErr…

Ansible 批量部署

anseble role [rootubuntu24 ansible]$ tree . ├── ansible.cfg ├── dns_master.yaml ├── dns_slave.yaml ├── hosts ├── LVS.yaml ├── mysql-discuz.yaml ├── mysql-jpress.yaml ├── nginx_php.yaml ├── roles │ ├── LVS │ │ ├── …

代码随想录算法训练营Day43 | 322. 零钱兑换、279. 完全平方数、139.单词拆分、多重背包理论基础

目录 322. 零钱兑换 279. 完全平方数 139.单词拆分 多重背包理论基础 322. 零钱兑换 题解 322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 …

JS补原型链

在JavaScript中&#xff0c;补足一个对象的原型链通常是指确保一个对象继承自另一个对象。这涉及到原型和构造函数。 对于构造函数&#xff08;通常首字母大写&#xff0c;如 Animal&#xff09;&#xff0c;它们有一个 prototype 属性&#xff0c;这个属性是一个对象&#xf…

Spring Boot:植物健康监测的智能时代

3系统分析 3.1可行性分析 通过对本植物健康系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本植物健康系统采用SSM框架&#xff0c;JAVA作为开发语言&#…

Python 自动化运维:Python基础知识

Python 自动化运维&#xff1a;Python基础知识 目录 &#x1f4ca; Python 基础复习 数据类型、控制结构与常用函数面向对象编程&#xff08;OOP&#xff09;与类的使用函数式编程概念与 lambda 表达式异常处理与日志记录的基本实践 1. &#x1f4ca; Python 基础复习 数据…

kubernetes中的ingress-nginx

华子目录 ingress-nginxingress-nginx功能 部署ingress及使用注意 ingress的高级用法1.基于路径的访问2.基于域名的访问3.建立tls加密4.建立auth认证5.rewrite重定向 ingress-nginx 官网&#xff1a;https://kubernetes.github.io/ingress-nginx/deploy/#bare-metal-clusters …