题目:

  输出最长递增子序列的长度,如输入 4 2 3 1 5 6,输出 4 (因为 2 3 5 6组成了最长递增子序列)。

  暴力破解法:这种方法很简单,两层for循环搞定,时间复杂度是O(N2)。

  动态规划:之前我们使用动态规划去解决一般是创建一维数组或者二维数组来构建出dp表,利用之前的历史上dp表中的值进行相关的处理求解出这个过程中的几个最大值,最小值,然后相加减来得出dp表的当前元素的值,所以我们会想,先创建一个一维数组,因为数组中选择的元素的范围在进行变化,所以dp表表示的值为截取到当前范围内最长的递增子序列是多少,但是在填表的过程中发现这种一般方法是不行的。但是这作为一道经典的题目,思路本身是很难的,有人想出来了它的dp解法,我们可以通过借鉴它的思路来帮助我们扩展理解dp算法。

  那么它的思路是:dp[i]表示的意思是必须包含以dp[i]结尾的最长递增子序列,那么它的值就是以dp[i]结尾的最长递增子序列长度。依次去扫描dp[i]这个字符之前的字符,假如发现当前字符大于之前的字符那么比较当前的最长子序列的长度与当前dp[i] + 1的值的大小来更新当前最长递增子序列的长度,当扫描完当前字符i之前的字符那么这个时候说明找到了以当前字符结尾的最长递增子序列的长度,把这个值赋值给dp[i]。

  需要注意的是最后还需要扫描一下dp数组,看一下哪一个dp[i]最大,因为我们不知道以哪个字符结尾的字符序列为最长的递增子序列,所以需要扫描一下,最后找到dp[i]的最大值返回,这是与之前的dp数组不同的一个点,以前是返回dp数组的最后一个元素就可以了,因为最后一个元素就是我们需要求解的最优解。这个解法时间复杂度也是O(N2)。
  那解法三的思路呢:dp[i]表示的是长度为i的最长递增子序列的末尾的那个数。先初始化dp[0]为数组中第一个元素的值,即dp[0]  =  4,定义一个指针p用来记录当前递增子序列的位置,从数组的第二个元素开始比较当前arr[i]与当前指针指向的dp数组的位置的值的大小,假如arr[i] > dp[p]那么说明当前的字符可以构成更长的递增子序列那么我们应该更新dp数组,指针往下移动,然后把当前的arr[i]的值赋值给dp[++p],假如arr[i] < dp[p]说明当前字符不能够构成更长的递增子序列,此时需要进行元素的替换,为什么进行替换呢?因为当前更小的元素更有利于最长递增子序列的贡献,所以扫描dp数组指向位置以及之前的位置假如arr[i] 大于dp[p]那么我们将其dp[p] 替换为arr[i]

  最后返回的是指针p指向的位置,注意返回最长递增子序列的长度是p指向的下标,这也是与之前动态规划的一般解法不同的点。更进一步的优化就是这种解法在扫描dp数组的过程中可以通过二分查找来降低时间复杂度,整体从O(N2)降低到O(NlgN)。
  代码:

package chapter_08_贪心策略与动态规划;

public class 最长递增子序列 {
static int[] arr = { 4, 2, 3, 1, 5, 6, 4, 8, 5, 9 }; public static void main(String[] args) {
System.out.println(f(arr)); // 输出 6
System.out.println(dp(arr)); // 输出 6
System.out.println(dp1(arr)); // 输出 6
} // 暴力破解法
private static int f(int[] arr) {
int maxCnt = 0;
for (int i = 0; i < arr.length; i++) {
int p = i;
int cnt = 1;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] > arr[p]) {
cnt++;
p = j;
}
}
// if (cnt>maxCnt){
// maxCnt=cnt;
// }
maxCnt = Math.max(maxCnt, cnt);
}
return maxCnt; } static int[] dp = new int[arr.length]; // 解法二
private static int dp(int[] arr) {
dp[0] = 1; for (int i = 1; i < arr.length; i++) {
int cnt = 1;
for (int j = i - 1; j >= 0; j--) {
if (arr[i] > arr[j]) {
cnt = Math.max(cnt, dp[j] + 1);
}
}
dp[i] = cnt; }
int ans = -1;
for (int i = 0; i < dp.length; i++) {
ans = Math.max(ans, dp[i]);
}
return ans;
} // 解法三
/* 在优化之后,可以达到O(NlgN) */
private static int dp1(int[] arr) {
dp = new int[arr.length + 1];
dp[1] = arr[0];// 长度为1的最长递增子序列,初始化为第一个元素
int p = 1;// 记录dp更新的最后位置
for (int i = 1; i < arr.length; i++) {
if (arr[i] > dp[p]) {
dp[p + 1] = arr[i];
p++;
} else {
//扫描dp数组,替换第一个比arr[i]大的dp
//for (int j = 0; j <= p; j++) {
// if (dp[j] > arr[i]) {
// dp[j] = arr[i];
// }
//}
// 二分查找,比上面扫描dp数组耗时少
int indexOfFirstBigger = indexOfFirstBigger(dp, arr[i], 0, p);
if (indexOfFirstBigger != -1)
dp[indexOfFirstBigger] = arr[i];
}
} return p;
} /**
* 在递增数组中,从左查找第一个比v大的元素的下标
*/
public static int indexOfFirstBigger(int[] dp, int v, int l, int r) {
while (l <= r) {
int mid = (l + r) >> 1;
if (dp[mid] > v) {
r = mid; // 保留大于v的下标以防这是第一个
} else {
l = mid + 1;
}
if (l == r && dp[l] > v)
return l;
}
return -1;
}
}

  

  

动态规划----最长递增子序列问题(LIS)的更多相关文章

  1. 最长公共子序列(LCS)、最长递增子序列(LIS)、最长递增公共子序列(LICS)

    最长公共子序列(LCS) [问题] 求两字符序列的最长公共字符子序列 问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字 ...

  2. 最长递增子序列(LIS)(转)

    最长递增子序列(LIS)   本博文转自作者:Yx.Ac   文章来源:勇幸|Thinking (http://www.ahathinking.com)   --- 最长递增子序列又叫做最长上升子序列 ...

  3. [51Nod 1218] 最长递增子序列 V2 (LIS)

    传送门 Description 数组A包含N个整数.设S为A的子序列且S中的元素是递增的,则S为A的递增子序列.如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS).A的LIS可 ...

  4. 算法之动态规划(最长递增子序列——LIS)

    最长递增子序列是动态规划中最经典的问题之一,我们从讨论这个问题开始,循序渐进的了解动态规划的相关知识要点. 在一个已知的序列 {a1, a 2,...an}中,取出若干数组成新的序列{ai1, ai ...

  5. 求解最长递增子序列(LIS) | 动态规划(DP)+ 二分法

    1.题目描述     给定数组arr,返回arr的最长递增子序列. 2.举例     arr={2,1,5,3,6,4,8,9,7},返回的最长递增子序列为{1,3,4,8,9}. 3.解答      ...

  6. 动态规划 - 最长递增子序列(LIS)

    最长递增子序列是动态规划中经典的问题,详细如下: 在一个已知的序列{a1,a2,...,an}中,取出若干数组组成新的序列{ai1,ai2,...,aim},其中下标i1,i2,...,im保持递增, ...

  7. Python动态规划求解最长递增子序列(LIS)

    原始代码错误,移步博客查看O(N^2)及优化的O(N*logN)的实现:每天一道编程题--最长递增子序列

  8. 动态规划之最长递增子序列(LIS)

           在一个已知的序列{ a1,a2,……am}中,取出若干数组成新的序列{ ai1, ai2,…… aim},其中下标 i1,i2, ……im保持递增,即新数列中的各个数之间依旧保持原数列中 ...

  9. hdu1257最少拦截系统 动态规划(最长递增子序列(LIS))

    Problem Description 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能超过前一发的高 ...

随机推荐

  1. 详解MariaDB数据库的索引

    1.什么是索引 索引是一种特殊的文件(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针. 更通俗的说,数据库索引好比是一本书前面的目录,在查找内容之前可以先在 ...

  2. 直流滤波器 verilog

    // dc filter- y(n) = c*x(n) + (1-c)*y(n-1) `timescale 1ps/1ps module ad_dcfilter #( // data path dis ...

  3. Ubuntu 18.04 安装MySQL

    最近在写东西的时候,需要用到MySQL,在网上查了一下,都说Ubuntu18.04不能安装MySQL5.7.22, 总觉的不可能,所以自己就研究了一下,然后分享给大家 工具/原料   VMware W ...

  4. java中文和unicode编码相互转换(转)

    工具类代码如下: package aa.com; import java.io.UnsupportedEncodingException; public class UnicodeUtil { pub ...

  5. android新窗口以及传值

    1,新建一个activity,如Activity2,在清单文件AndroidManifest.xml 中 application节点中 增加一个新窗体: ................. </ ...

  6. net core 依懒注入 中间件

    依懒注入 依懒 当一个类需要另一个类协作来完成工作的时候就产生了依赖.比如我们在AccountController这个控制器需要完成和用户相关的注册.登录 等事情.其中的登录我们由EF结合Idneti ...

  7. 028 kafka面试小节

    1.大纲 Kafka控制节点用的是什么? 消费者.生产者是如何理解的? 2.Kafka控制节点用的是什么? 基于zookeeper协调的分布式消息系统 3.消费者.生产者是如何理解的? 消息系统通常都 ...

  8. TensorFlow卷积网络常用函数参数详细总结

    卷积操作 tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None) 除去name参数用以指定该操作 ...

  9. vue中 v-show和v-if的区别?

    v-show的操作元素的属性是display v-if的操作元素的移除和新建 还有一个就是权限的时候,v-show普通用户能看到,用v-if的时候普通用户看不到.

  10. UI分层中使用PageFactory

    基于原PO设计模式,需要改变原有的从文件中读取文件,更改为PageFactory模式.做出如下改动: 1 2 public MsysPage(DriverBase driver) { super(dr ...