动态规划初步--最长上升子序列(LIS)
一、问题
有一个长为n的数列 a0,a1,a2...,an-1a。请求出这个序列中最长的上升子序列的长度和对应的子序列。上升子序列指的是对任意的i < j都满足ai < aj的子序列。
二、思路
如果i < j且ai < aj则认为ai到aj存在有向边,由于一个数不可能直接或间接的指向自己,所以是一个有向无环图。但是,在这里我们并不需要真正的建立图。我们可以用动态规划来做,对于状态的设定有多种方式。
三、代码实现
1、将dp[i]表示以ai结束的最长上升子序列,当j < i且a[j] < a[i],dp[i] = max(dp[i],dp[j] + 1),初始化dp[i] = 1。由于定义的是结束位置,所以记录的是逆序,用栈倒过来再输出就行。
代码一:
#include<stdio.h>
#include<iostream>
#include<stack>
#include<algorithm>
using namespace std; const int INF = 0x3f3f3f3f;
const int maxn = + ;
int n;
int a[maxn], dp[maxn]; //dp[i]表示以i结束的最长上升子序列
int nextp[maxn]; //记录路径
int res = ;
int first = ; void print_ans()
{
stack<int>s;
for (int i = ; i < res; i++)
{
s.push(first + );
first = nextp[first];
}
for (int i = ; i < res; i++)
{
int tmp = s.top(); s.pop();
printf("%d ", tmp);
}
printf("\n%d\n", res);
} void slove()
{
memset(dp, , sizeof(dp));
memset(nextp, , sizeof(nextp));
res = ; first = ; //记得清零
for (int i = ; i < n; i++)
{
dp[i] = ;
for (int j = ; j < i; j++)
{
if (a[j] < a[i])
{
if (dp[j] + > dp[i])
{
dp[i] = dp[j] + ;
nextp[i] = j;
}
}
}
if (dp[i] > res)
{
res = dp[i];
first = i;
}
}
print_ans();
} int main()
{
while (scanf("%d", &n) == && n)
{
for (int i = ; i < n; i++)
scanf("%d", &a[i]);
slove();
}
return ;
}
代码二:两者只是输出的处理不同,用逆向寻路,不需要额外的空间,但要消耗一些时间。
#include<stdio.h>
#include<iostream>
#include<stack>
#include<vector>
#include<algorithm>
using namespace std; const int INF = 0x3f3f3f3f;
const int maxn = + ;
int n;
int a[maxn], dp[maxn]; //dp[i]表示以i结束的最长上升子序列
stack<int>sta; void print_ans(int s)
{
sta.push(s + );
if (dp[s] == )
{
while (!sta.empty())
{
int tmp = sta.top(); sta.pop();
printf("%d ", tmp);
}
return;
}
for (int i = ; i < n; i++)
{
if (a[i] < a[s] && dp[s] == dp[i] + )
{
print_ans(i);
break;
}
}
}
void slove()
{
memset(dp, , sizeof(dp));
int res = ;
int first = ;
stack<int>s;
for (int i = ; i < n; i++)
{
dp[i] = ;
for (int j = ; j < i; j++)
{
if (a[j] < a[i])
{
dp[i] = max(dp[i],dp[j] + );
}
}
if (dp[i] > res)
{
res = dp[i];
first = i; //first记录最长上升子序列的最后元素的位置
}
} print_ans(first);
printf("\n%d\n", res);
} int main()
{
while (scanf("%d", &n) == && n)
{
for (int i = ; i < n; i++)
scanf("%d", &a[i]);
slove();
}
return ;
}
2、用dp[i]表示以i开始的最长上升子序列,初始化同样是dp[i] = 0。同时由于定义的是开始位置,可以直接输出最长上升子序列。
void print_ans()
{
for (int i = ; i < res; i++)
{
printf("%d ", first + );
first = nextp[first];
}
printf("\n%d\n", res);
} void slove()
{
res = ; //定义成全局变量时,注意清零
for (int i = n - ;i >= ; i--)
{
dp[i] = ;
for (int j = i + ; j < n; j++)
{
if (a[i] < a[j])
{
if (dp[j] + > dp[i])
{
dp[i] = dp[j] + ;
nextp[i] = j;
}
}
}
if (dp[i] > res)
{
res = dp[i];
first = i;
}
}
print_ans();
}
3、把dp[i]表示长度为i + 1的上升子序列中末尾元素的最小值(不存在的话就是INF)。
贪心 + 二分查找,利用贪心思想,对于一个一个上升子序列,显然最后一个元素越小,越有利于添加新的元素,这样序列就越长。
类似的我们也可以定义对称的状态,即把dp[i]表示长度为i + 1的上升子序列中开始元素的最大值
相比前面的状态定义,有两个好处:如果子序列长度相同,可以使最末尾元素的值最小;时间复杂度由O(n^2)降至O(nlogn)。
开始全部初始化为INF,如果i == 0或者dp[i - 1] < aj,dp[i] = min(dp[i],aj),最终dp[i] < INF,最大的i + 1就是结果。如何找到aj呢,由于dp[i]记录的长度为i + 1的最末尾元素的最小值,而最末尾元素是这个长度为i+1中的最大值,所以aj大于dp[i]就能更新。我们还能发现,dp单增,每个aj只需用来更来一次。
n个元素,每次查找logn,总的时间复杂度为O(nlogn).
void slove()
{
fill(dp, dp + n, INF);
for (int i = ; i < n; i++)
{
*upper_bound(dp, dp + n, a[i]) = a[i];
}
int first = lower_bound(dp, dp + n, INF) - dp;
printf("%d\n", dp[first - ]);
printf("%d\n", first);
}
打印结果路径似乎不太方便,以后再补上吧。
动态规划初步--最长上升子序列(LIS)的更多相关文章
- 动态规划之一最长上升子序列LIS
//最长上升子序列 #include<iostream> #include<cstring> using namespace std; const int maxn = 101 ...
- 动态规划(DP),最长递增子序列(LIS)
题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(d ...
- 2.16 最长递增子序列 LIS
[本文链接] http://www.cnblogs.com/hellogiser/p/dp-of-LIS.html [分析] 思路一:设序列为A,对序列进行排序后得到B,那么A的最长递增子序列LIS就 ...
- 最长回文子序列LCS,最长递增子序列LIS及相互联系
最长公共子序列LCS Lintcode 77. 最长公共子序列 LCS问题是求两个字符串的最长公共子序列 \[ dp[i][j] = \left\{\begin{matrix} & max(d ...
- 最长上升子序列LIS(51nod1134)
1134 最长递增子序列 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 给出长度为N的数组,找出这个数组的最长递增子序列.(递增子序列是指,子序列的元素是递 ...
- 【部分转载】:【lower_bound、upperbound讲解、二分查找、最长上升子序列(LIS)、最长下降子序列模版】
二分 lower_bound lower_bound()在一个区间内进行二分查找,返回第一个大于等于目标值的位置(地址) upper_bound upper_bound()与lower_bound() ...
- 题解 最长上升子序列 LIS
最长上升子序列 LIS Description 给出一个 1 ∼ n (n ≤ 10^5) 的排列 P 求其最长上升子序列长度 Input 第一行一个正整数n,表示序列中整数个数: 第二行是空格隔开的 ...
- 一个数组求其最长递增子序列(LIS)
一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...
- 1. 线性DP 300. 最长上升子序列 (LIS)
最经典单串: 300. 最长上升子序列 (LIS) https://leetcode-cn.com/problems/longest-increasing-subsequence/submission ...
随机推荐
- TypeScript完全解读(26课时)_9.TypeScript完全解读-TS中的类
9.TypeScript完全解读-TS中的类 创建class.ts文件,并在index.ts内引用 创建一个类,这个类在创建好后有好几个地方都标红了 这是tslint的一些验证规则 一保存就会自动修复 ...
- Android布局中的layout_weight和weightSum属性的详解及使用
由于Android设备的尺寸大小不一,种类繁多,当我们在开发应用的时候就要考虑屏幕的适配型了,尽可能让我们的应用适用于主流机型的尺寸,这样我们的应用不会因为尺寸不同而不美观,解决屏幕适配问题的方法有很 ...
- 解决MySql报错:1130 - Host 'xxx' is not allowed to connect to this MySQL server的方法
发现问题 使用Navicat连接MySql数据库时,未能成功,提示信息如下图: 这个错误提示已经很明确了,"不允许主机'desktop-teat9ob'连接到此mysql服务器", ...
- vm安装mac
需要 vm虚拟机:vm10 mac系统: mac10.9 vm安装mac补丁 : unlokc-all-v120 vm tools for mac10.9: darwin6.0.3.iso ...
- Codevs 1569 最佳绿草
1569 最佳绿草 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题解 题目描述 Description 贝茜正计划着这一天如何美美地咀嚼春天的绿 ...
- LeetCode.905-按奇偶排序数组(Sort Array By Parity)
这是悦乐书的第347次更新,第371篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第212题(顺位题号是905).给定一个非负整数数组A,返回一个由A的所有偶数元素组成的 ...
- load View 流程 程序启动流程
基本流程: loadView / nib文件 来加载view到内存 -> viewDidLoad 函数进一步初始化这些view -> 内存不足时, 调用viewDidUnload 函数释 ...
- 第二篇 HTML5打包发布IOS APP之苹果开发者账号申请流程
打包技术转移到了公众号
- kafka剖析(转)
Kafka是由LinkedIn开发的一个分布式的消息系统,使用Scala编写,它以可水平扩展和高吞吐率而被广泛使用.目前越来越多的开源分布式处理系统如Cloudera.Apache Storm.Spa ...
- bzoj1125:[POI2008]Poc
传送门 这个题好难卡啊. 看到这种题自然会想到字符串hash是不是,但是对于每次操作造成的影响需要\(O(n)\)的时间去更新,自然是不优的 可以发现这个更新可以用数据结构来维护,对于每个hash值开 ...