题目:

  输出最长递增子序列的长度,如输入 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;
}
}

  

  

最新文章

  1. 安装cocoapods
  2. maven内部运行原理解析
  3. 无参数实例化Configuration对象以及addResource无法加载core-site.xml中的内容
  4. Java 对象拷贝方式
  5. jQuery语法
  6. Oracle的表空间和数据文件
  7. CUBRID学习笔记 22 插入数据
  8. tableView中的“点击加载更多”点击不到
  9. oracle 配置 oem
  10. mencoder mencoder 安装使用及常用参数
  11. android Service开机启动及debug
  12. JAVA大数类
  13. ES6新特性-------数组、Math和扩展操作符(续)
  14. s nrmtyu,yi.sfn rt
  15. CentOS6+MySQL5.6二进制安装
  16. 菜鸟笔记:node.js+mysql中将JSON数据构建为树(递归制作树状菜单数据接口)
  17. [LeetCode] 二叉树相关题目(不完全)
  18. TeamViewer 服务队列网页怎么打开?有什么用?
  19. 校园网下对VMware网络的配置
  20. C#单例和Unity单例

热门文章

  1. 怎样解决if __name__ == &quot;__main__&quot;:下面的代码没有执行的问题
  2. 协议形式化分析Scyther 资料整理
  3. Nginx配置详解(转)
  4. C++—模板(1)模板与函数模板
  5. EF core的模型映射
  6. JAVA -数据类型与表达式---变量与赋值
  7. JDBC url连接字符串错误1
  8. js中将字符串作为函数名来调用的方法
  9. yarn一直在跑一个用户为dr.who的application
  10. JS的常用属性