预计阅读时间:9分钟

很多读者反应,就算看了前文 动态规划详解,了解了动态规划的套路,也不会写状态转移方程,没有思路,怎么办?本文就借助「最长递增子序列」来讲一种设计动态规划的通用技巧:数学归纳思想。

最长递增子序列(Longest Increasing Subsequence,简写 LIS)是比较经典的一个问题,比较容易想到的是动态规划解法,时间复杂度 O(N^2),我们借这个问题来由浅入深讲解如何写动态规划。

比较难想到的是利用二分查找,时间复杂度是 O(NlogN),我们通过一种简单的纸牌游戏来辅助理解这种巧妙的解法。

先看一下题目,很容易理解:

注意「子序列」和「子串」这两个名词的区别,子串一定是连续的,而子序列不一定是连续的。下面先来一步一步设计动态规划算法解决这个问题。

一、动态规划解法

动态规划的核心设计思想是数学归纳法。

相信大家对数学归纳法都不陌生,高中就学过,而且思路很简单。比如我们想证明一个数学结论,那么我们先假设这个结论在 k

类似的,我们设计动态规划算法,不是需要一个 dp 数组吗?我们可以假设 dp[0...i−1] 都已经被算出来了,然后问自己:怎么通过这些结果算出dp[i]?

直接拿最长递增子序列这个问题举例你就明白了。不过,首先要定义清楚 dp 数组的含义,即 dp[i] 的值到底代表着什么?

我们的定义是这样的:dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。

举个例子:

算法演进的过程是这样的:

根据这个定义,我们的最终结果(子序列的最大长度)应该是 dp 数组中的最大值。

int res = 0;for (int i = 0; i

读者也许会问,刚才这个过程中每个 dp[i] 的结果是我们肉眼看出来的,我们应该怎么设计算法逻辑来正确计算每个 dp[i] 呢?

这就是动态规划的重头戏了,要思考如何进行状态转移,这里就可以使用数学归纳的思想:

我们已经知道了 dp[0...4] 的所有结果,我们如何通过这些已知结果推出 dp[5] 呢?

根据刚才我们对 dp 数组的定义,现在想求 dp[5] 的值,也就是想求以 nums[5] 为结尾的最长递增子序列。

nums[5] = 3,既然是递增子序列,我们只要找到前面那些结尾比 3 小的子序列,然后把 3 接到最后,就可以形成一个新的递增子序列,而且这个新的子序列长度加一。

当然,可能形成很多种新的子序列,但是我们只要最长的,把最长子序列的长度作为 dp[5] 的值即可。

这段代码的逻辑就可以算出 dp[5]。到这里,这道算法题我们就基本做完了。读者也许会问,我们刚才只是算了 dp[5] 呀,dp[4], dp[3] 这些怎么算呢?

类似数学归纳法,你已经可以通过 dp[0...4] 算出 dp[5] 了,那么任意 dp[i] 你肯定都可以算出来:

还有一个细节问题,就是 base case。dp 数组应该全部初始化为 1,因为子序列最少也要包含自己,所以长度最小为 1。下面我们看一下完整代码:

至此,这道题就解决了,时间复杂度 O(N^2)。总结一下动态规划的设计流程:

首先明确 dp 数组所存数据的含义。这步很重要,如果不得当或者不够清晰,会阻碍之后的步骤。

然后根据 dp 数组的定义,运用数学归纳法的思想,假设 dp[0...i−1] 都已知,想办法求出 dp[i],一旦这一步完成,整个题目基本就解决了。

但如果无法完成这一步,很可能就是 dp 数组的定义不够恰当,需要重新定义 dp 数组的含义;或者可能是 dp 数组存储的信息还不够,不足以推出下一步的答案,需要把 dp 数组扩大成二维数组甚至三维数组。

二、二分查找解法

这个解法的时间复杂度会将为 O(NlogN),但是说实话,正常人基本想不到这种解法(也许玩过某些纸牌游戏的人可以想出来)。所以如果大家了解一下就好,正常情况下能够给出动态规划解法就已经很不错了。

根据题目的意思,我都很难想象这个问题竟然能和二分查找扯上关系。其实最长递增子序列和一种叫做 patience game 的纸牌游戏有关,甚至有一种排序方法就叫做 patience sorting(耐心排序)。

为了简单起见,后文跳过所有数学证明,通过一个简化的例子来理解一下思路。

首先,给你一排扑克牌,我们像遍历数组那样从左到右一张一张处理这些扑克牌,最终要把这些牌分成若干堆。

处理这些扑克牌要遵循以下规则:

只能把点数小的牌压到点数比它大的牌上。如果当前牌点数较大没有可以放置的堆,则新建一个堆,把这张牌放进去。如果当前牌有多个堆可供选择,则选择最左边的堆放置。

比如说上述的扑克牌最终会被分成这样 5 堆(我们认为 A 的值是最大的,而不是 1)。

为什么遇到多个可选择堆的时候要放到最左边的堆上呢?因为这样可以保证牌堆顶的牌有序(2, 4, 7, 8, Q),证明略。

按照上述规则执行,可以算出最长递增子序列,牌的堆数就是我们想求的最长递增子序列的长度,证明略。

我们只要把处理扑克牌的过程编程写出来即可。每次处理一张扑克牌不是要找一个合适的牌堆顶来放吗,牌堆顶的牌不是有序吗,这就能用到二分查找了:用二分查找来搜索当前牌应放置的位置。

PS:旧文 二分查找算法详解 详细介绍了二分查找的细节及变体,这里就完美应用上了。如果没读过强烈建议阅读。

至此,二分查找的解法也讲解完毕。

这个解法确实很难想到。首先涉及数学证明,谁能想到按照这些规则执行,就能得到最长递增子序列呢?其次还有二分查找的运用,要是对二分查找的细节不清楚,给了思路也很难写对。

所以,这个方法作为思维拓展好了。但动态规划的设计方法应该完全理解:假设之前的答案已知,利用数学归纳的思想正确进行状态的推演转移,最终得到答案。

推荐阅读:

二维数组最长递增java_动态规划设计之最长递增子序列相关推荐

  1. 二维数组的查找(每一行每一列都是递增顺序)?

    问题描述: 在一个二维数组中( 每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有 ...

  2. html二维数组查找车票,用C语言设计出的火车票系统

    要求: 火车订票系统 要求: 1.用户注册功能 注册新用户,输入用户名密码. 2.用户名检测,用户名重复,重新注册. 3.用户注册成功. 用户登录 检测用户名和密码是否正确,正确,登录成功,否则失败. ...

  3. C语言求二维数组平均数,一道JavaScript的二维数组求平均数的题

    JavaScript中只支持一维数组,但是可以在数组中嵌套数组来创建二维以至于多维的数组.今天下午在看书时候,发现一道感觉比较有意思的题,就是js中如何求二维数组的列之和和行之和,现在就给大家分享下, ...

  4. c语言二维vector大小,vector作为二维数组

    vector本来就是可以用来代替一维数组的,vector提供了operator[]函数,可以像数组一样的操作,而且还有边界检查,动态改变大小. 这里只介绍用它来代替二维的数组,二维以上的可以依此类推. ...

  5. BM18 二维数组中的查找

    描述 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有 ...

  6. 《剑指Offer》——二维数组中的查找(JZ1)C++

    文章目录 前言 题目:JZ1 二维数组中的查找 一.暴力解法 二.优化解法 总结 前言 题目:JZ1 二维数组中的查找 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序, ...

  7. 在数组中查找指定元素_剑指 offer 第一题: 二维数组中的查找

    题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数 ...

  8. 二维数组的对角搜索C++

    1.需求与分析 在一个二维数组中,每一行按照从左到右递增的顺序进行排序,每一列按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否存在这个整数. 2.代码 #i ...

  9. 剑指offer刷题(java)|二维数组中的查找|替换空格|leetcode刷题

    文章目录 前言 一.二维数组中的查找 题目 题解一 题解二 题解三 二.替换空格 题目 题解一 题解二 题解三 前言 本文主要是写了我做算法题的思路以及对其他优秀题解的自我理解. 一.二维数组中的查找 ...

  10. python给矩阵赋值_解决Python二维数组赋值问题

    解决Python二维数组赋值问题 当我们采用s=[[0]*3]*2初始化一个数组,然后对s[0][0]进行赋值,改变的是第一列所有的值.因为用s = [[0]*3]*2 初始化数组,他表示的是指向这个 ...

最新文章

  1. LabVIEW彩色图像分割(基础篇—14)
  2. 你不知道的z-index
  3. IOS开发-地图 (mapkit)实验
  4. 良品铺子,互联网经济下的“两元店”
  5. 圣殿骑士-移民结婚了,而且很年轻~
  6. springboot socket服务端_SpringBoot2.x系列教程81--SpringBoot中整合WebSockets
  7. 6月8日任务(12.10 Nginx访问日志 12.11 Nginx日志切割 12.12 静态文件)
  8. 一个优秀的公司如何才能成为一个卓越的公司?
  9. Jmeter系列之接口断言
  10. Web前端开发需要掌握的技术有哪些?
  11. php 手机号 去掉86,手机号前面的+86是什么意思
  12. 中国鲷鱼养殖产量和捕捞产分析,养殖产业区域集中度高「图」
  13. python批量修改文件内容(修改文件指定行)
  14. Ubuntu18.04安装英伟达显卡驱动
  15. 海思 Hi3559A Sample_vdec视频解码及编译
  16. java日志脱敏_java 日志脱敏框架 sensitive,优雅的打印脱敏日志
  17. 快进来看程序员风格的修真小说!
  18. MA Chapter 3 Presenting information(SRCharlotte)
  19. 梅姨眼中最爱读英国书籍的人竟然是TA?
  20. 二维码生成工具微信小程序源码下载

热门文章

  1. F.Studio 远程备份系统
  2. unity byte[]的压缩和解压
  3. Atitit img hot click link 图像背景拉伸100%Map area trouble..So body backgrd img mode...is easy...No
  4. Atitit. 衡量项目规模 ----包含的类的数量 .net java类库包含多少类 多少个api方法??
  5. 解决SAXParseException: Premature end of file
  6. 一位区域销售经理百条经验手记
  7. (转)高盛将“穷人”理财交给机器人,那智能投顾在中国怎么玩?
  8. SQLite: 关于日期的标准化问题
  9. 蚂蚁可信原生负责人入选“2021年度全球青年领袖”名单
  10. 文荣:7月24日阿里云上海峰会网络大神