题目:

  输出最长递增子序列的长度,如输入 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. Zygote及System进程启动

    1.  init 根据init.rc 运行 app_process, 并携带‘--zygote' 和 ’--startSystemServer' 参数. 2.  AndroidRuntime.cpp: ...

  2. 第一个 Python 程序

    ## 目标 * 第一个 `HelloPython` 程序* `Python 2.x` 与 `3​​.x` 版本简介* 执行 `Python` 程序的三种方式 * 解释器 —— `python` / ` ...

  3. 微信小程序开发01 --- 微信小程序项目结构介绍

    一.微信小程序简单介绍: 微信官方介绍微信小程序是一个不需要下载安装就可使用(呵呵,JS代码不用下载吗?展示的UI不用下载吗?)的应用,它实现了应用“触手可及”的梦想,用户扫一扫或搜一下即可打开应用. ...

  4. 《剑指offer》两个链表中的第一个公共节点

    本题来自<剑指offer> 反转链表 题目: 思路: C++ Code: Python Code: 总结:

  5. 公设基础Generic

    1# 与泛型相关的一些术语 1.类型参数(type parameter) : EX: List<E> 这里的E就属于List接口的单个类型参数E 2.参数化的类型(parameterize ...

  6. mysql5.7初始化密码报错 ERROR 1820 (HY000): You must reset your password using ALTER USER statement before

    mysql初始化密码常见报错问题1,mysql5.6是密码为空直接进入数据库的,但是mysql5.7就需要初始密码 cat /var/log/mysqld.log | grep password1 2 ...

  7. 关于Python 解包,你需要知道的一切

    解包在英文里叫做 Unpacking,就是将容器里面的元素逐个取出来(防杠精:此处描述并不严谨,因为容器中的元素并没有发生改变)放在其它地方,好比你老婆去菜市场买了一袋苹果回来分别发给家里的每个成员, ...

  8. CSS(Cascading Style Sheet)简述

    CSS(Cascading Style Sheet)简述 什么是CSS? css是指层叠样式表 css定义了如何显示HTML元素 css的诞生是为了解决内容与表现分离的问题,可以极大地提高工作效率 样 ...

  9. unity一个按钮实现开和关

    using System.Collections; using System.Collections.Generic; using UnityEngine; public class ButtonCl ...

  10. 学习使人快乐7--Mail收发原理+计划

    本篇了解邮件收发的原理与机制(smtp,pop3协议).不打算作重点学习辣~~~~~~~~~~~~~~~~ 日常感谢大佬gacl 另:打算把每周计划学的东西发在blog上面,勉励一下本咸鱼本周计划:1 ...