动态规划概述

动态规划,是一种解决最优化问题的方法,在我看来就是一种穷举算法。只不过,这种穷举算法具有“特殊性质”。一般的穷举法,就是列出问题所有的可行解,然后通过比较找到最优解,比如最大、最小值问题。但穷举所有可行解,使得算法的复杂度特别高,让人难以忍受。动态规划将原问题分解为多个重叠的子问题,原问题的最优解由子问题的最优解推导而来。正因为动态规划能将原问题分解为多个重叠的子问题,所以我们可以储存子问题的最优解,当再次遇到重叠子问题的时候直接使用已储存的结果,减少计算量。此外,原问题的最优解由子问题的最优解推导产生,使得我们在计算原问题的时候,能够部分使用已储存的结果,进一步减少计算量。

接下来,我将用一个例子展示动态规划和递归穷举的异同,并说明什么是重叠子问题

第一题(重叠子问题)

509. 斐波那契数

题目描述:斐波那契数(通常用 F(n) 表示)形成的序列称为斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

示例1:

输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

思路

在这里,我沿用了动态规划解题套路框架的思路,首先考虑用递归穷举去解决问题。

递归法

int fib(int n){return dfs(n);
}int dfs(int n){if(n == 0){return 0;}if(n == 1){return 1;}return dfs(n - 1) + dfs(n - 2);
}

如果我们把递归树画出来,可以看到有重叠子问题

这是动态规划和递归穷举的不同,也是动态规划的第一个要素。递归穷举,子问题之间互不重叠,所以画出来递归树也没有相同的节点;动态规划,子问题之间有重叠,所以画出来的递归树有相同节点。既然递归树有相同节点,那我们就可以通过剪枝来优化算法。我们可以用一个数组来记录节点,如果某个节点已经被记录在数组中,那就直接返回数组中的值,而不用进行递归求解。

递归法+dp数组

int fib(int n){int[] dp = new int[n + 1];return dfs(n, dp);
}int dfs(int n, int[] dp){if(n == 0){return 0;}if(n == 1){return 1;}if(dp[n] != 0){return dp[n];}dp[n] = dfs(n - 1, dp) + dfs(n - 2, dp);return dp[n];
}

这个时候我们再把递归树画出来,可以看到我们将递归树剪枝成了递归序列。

不难看出,现在问题转换成如何填充dp数组,我们可以对此继续进行优化。

迭代法(动态规划)

为了节省空间,我们可以放弃递归函数,用迭代循环的方式填充dp数组。

int fib(int n){if(n < 2){return n;}int[] dp = new int[n + 1];dp[0] = 0;dp[1] = 1;for(int i = 2; i <= n; i++){dp[i] = dp[i - 1] + dp[i - 2];}return dp[n];
}

总结

动态规划与递归穷举的联系与区别:

动态规划与递归穷举的联系在于,两者都是通过搜索全部的可行解来解决问题。动态规划与递归穷举的区别在于,递归穷举的子问题之间互不重叠,动态规划的子问题存在相互重叠。

动态规划两要素:

  1. 原问题可以拆解成重叠子问题

  2. 原问题的最优解由子问题的最优解构成(最优子结构

只有满足这两个要素的问题,才能够用动态规划法来求解。第一条已经说明过了,接下来我再用一个例子说明什么是最优子结构

最优子结构

在这里,我再次引用最优子结构原理和 DP 数组遍历方向的例子,供大家参考。

我先举个很容易理解的例子:假设你们学校有 10 个班,你已经计算出了每个班的最高考试成绩。那么现在我要求你计算全校最高的成绩,你会不会算?当然会,而且你不用重新遍历全校学生的分数进行比较,而是只要在这 10 个最高成绩中取最大的就是全校的最高成绩。
我给你提出的这个问题就符合最优子结构:可以从子问题的最优结果推出更大规模问题的最优结果。让你算每个班的最优成绩就是子问题,你知道所有子问题的答案后,就可以借此推出全校学生的最优成绩这个规模更大的问题的答案。
你看,这么简单的问题都有最优子结构性质,只是因为显然没有重叠子问题,所以我们简单地求最值肯定用不出动态规划。
再举个例子:假设你们学校有 10 个班,你已知每个班的最大分数差(最高分和最低分的差值)。那么现在我让你计算全校学生中的最大分数差,你会不会算?可以想办法算,但是肯定不能通过已知的这 10 个班的最大分数差推到出来。因为这 10 个班的最大分数差不一定就包含全校学生的最大分数差,比如全校的最大分数差可能是 3 班的最高分和 6 班的最低分之差。
这次我给你提出的问题就不符合最优子结构,因为你没办通过每个班的最优值推出全校的最优值,没办法通过子问题的最优值推出规模更大的问题的最优值。想满足最优子结构,子问题之间必须互相独立。全校的最大分数差可能出现在两个班之间,显然子问题不独立,所以这个问题本身不符合最优子结构。
那么遇到这种最优子结构失效情况,怎么办?策略是:改造问题。改造问题,也就是把问题等价转化:最大分数差,不就等价于最高分数和最低分数的差么,那不就是要求最高和最低分数么,不就是我们讨论的第一个问题么,不就具有最优子结构了么?那现在改变思路,借助最优子结构解决最值问题,再回过头解决最大分数差问题,是不是就高效多了?

总结

通过以上两个例子,我解释了动态规划解题的两要素:重叠子问题和最优子结构。只有一个问题满足这两个要素,才能使用动态规划法解题。接下来,我将通过一道题,说明动态规划的解题步骤

第二题(动态规划的解题步骤)

动态规划的解题步骤可以分为三步:确定状态,确定状态转移方程,确定边界条件。接下来,我将详细解释这三个步骤的含义。

  • 确定状态:可以简单地将状态理解为问题的规模。在前文已经提到了,动态规划将原问题分解为子问题,从原问题状态转移到子问题状态,同时问题的规模也随之减小。所以,确定状态的一个简单办法,就是找到从原问题到子问题中会变化的量。
  • 确定状态转移方程:当我们从子问题的最优解推导原问题的最优解时,状态转移方程建立起子问题与原问题之间的关系。用一个我们毕竟熟悉的概念来解释,状态转移方程就是数学归纳法中的递推公式
  • 确定边界条件:既然状态转移方程就是递推公式,那么我们还需要给出递推公式的初始值,也就是边界条件。

接下来,通过一道题进一步说明这三个步骤。

198. 打家劫舍

题目描述:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。

思路

首先,确定状态。很显然,状态(问题的规模)就是房子的数量,因为房子数量越多,你可以偷到更多的钱。以示例1来说,如果只有一间房,那你只能偷1号房屋,偷到的金额为1。如果有四间房,那结果如上所示。
其次,确定状态转移方程。对于示例1,当一共有四间房可供你选择偷窃的时候,你有两种选择。第一种,选择偷窃3号房屋,那么为了不触动警报装置,你就不能偷4号房屋;选择不偷窃3号房屋,那你可以偷4号房屋。那你会怎么选择呢?作为一个专业的小偷,你肯定是选择两者间金额最大的方法作案。所以,当四间房屋都可以偷窃的时候,能获得的最大金额=max(只偷前三间房屋的金额, 只偷前两间房屋的金额(略过3号房屋)+4号房屋的金额)。这个公式就是这道题的状态转移方程。
最后,确定边界条件。从偷4号房屋的例子中,我们可以看出其依赖“只偷前两间房屋的金额”和“只偷前三间房屋的金额”,也就是其前两个状态的结果。所以,我们应该给出最开始两个状态的边界条件,才能进行递推。当没有房子的时候,可以偷到的金额为0;当只有一间房子的时候,可以偷到的金额为1号房屋的金额。

代码

java版本

class Solution {public int rob(int[] nums) {// 确定状态,与房屋的数量有关int n = nums.length;int[] dp = new int[n + 1];// 边界条件dp[1] = nums[0];for(int i = 2; i <= n; i++){// 确定状态转移方程dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);}return dp[n];}
}

总结

至此,我已经介绍了动态规划的两要素:重叠子问题和最优子结构,而且进一步说明了动态规划的解题步骤。
接下来,我把leetcode上做过的动态规划问题,按照不同的分类总结在一起来讲解。

参考:

动态规划解题套路框架
算法设计与分析基础(第3版) (豆瓣)
基础线性dp

Leetcode动态规划题解1——两要素和解题步骤相关推荐

  1. 动态规划类问题解题步骤 --附例题(小偷问题)

    动态规划类问题解题步骤 --附例题(小偷问题) 动态规划 基本思想 适用情况 优点 解题步骤 实例分析 问题 解题步骤 动态规划 基本思想 动态规划背后的基本思想非常简单.大致上,若要解一个给定问题, ...

  2. LeetCode/LintCode 题解丨一周爆刷双指针: 两数之和

    描述 给一个整数数组,找到两个数使得他们的和等于一个给定的数 target. 你需要实现的函数twoSum需要返回这两个数的下标, 并且第一个下标小于第二个下标.注意这里下标的范围是 0 到 n-1. ...

  3. LeetCode/LintCode 题解丨一周爆刷分治法:合并两棵二叉树

    描述 给出两棵二叉树,当你用其中一棵覆盖另一棵时,两棵树的一些节点会发生重叠,而其他节点则不会重叠. 您需要将它们合并到一棵新的二叉树中. 合并的规则是如果两个节点重叠,则将节点值加起来作为合并节点的 ...

  4. leetcode初级算法4.两个数组的交集 II

    leetcode初级算法4.两个数组的交集 II 仅为个人刷题记录,不提供解题思路 题解与收获 我的解法:(总结在代码中) public int[] intersect(int[] nums1, in ...

  5. leetcode动态规划(python与c++)

    1 . 斐波那契数 class Solution:def fib(self, n: int) -> int:# if n==0:# return 0# elif n==1:# return 1# ...

  6. 什么是 “动态规划” , 用两个经典问题举例。

    1.什么是动态规划? 看了很多题解,一般解决者开始就说用DP来解,然后写了嵌套的for循环,不是很容易看懂,但是确实解出来了,我们这次来看下到底什么是动态规划?它有什么特点呢?容我抄一段话: 动态规划 ...

  7. LeetCode/LintCode 题解丨一周爆刷字符串:URL 编码

    描述 给出一个代表网址 host 的字符串 base_url,和代表查询参数的列表 query_params_list,你需要返回带查询参数的完整 URL. 查询参数列表由一些包含两个元素的数组组成, ...

  8. LeetCode/LintCode 题解丨一周爆刷双指针:最小范围

    描述 有k个升序排列的数组,寻找一个最小范围,使每个数组中至少有一个元素被包含. 范围[a,b]比范围[c,d]小,当且仅当b-a < d-c,或是a < c且b-a == d-c. 给定 ...

  9. LeetCode/LintCode 题解丨一周爆刷字符串:简化路径

    描述 给定一个文件的绝对路径(Unix-style),请进行路径简化. Unix中, . 表示当前目录, - 表示父目录. 结果必须以 / 开头,并且两个目录名之间有且只有一个 /.最后一个目录名(如 ...

最新文章

  1. C++11可变模版参数的妙用+ 认真分析mmap:是什么 为什么 怎么用
  2. wpf 绘制rectangle 代码
  3. Leetoce--572. 另一个树的子树(java)
  4. android textview 必填,在android中如何使用Html渲染的方式实现必填项前面的*号
  5. 中职计算机网络技术教学大纲,计算机网络技术课程教学大纲
  6. Ssh+Mysql实现的Java Web图书商城
  7. comsol 裂隙 耦合_使用COMSOL建立多重连续介质渗流模型
  8. Android中Context的详细介绍
  9. 看故事也能长知识,CPU的工作原理原来这么简单!
  10. dnf电脑服务器不稳定怎么办,Win10玩DNF间歇性卡顿怎么办?Win10系统玩DNF卡顿解决方法(2)...
  11. 扫清盲点,如何正确的从HttpClient 3.x系统升级到HttpClient 4.x
  12. win10 python安装以及编辑器pycharm安装
  13. qq三国华容道算法(拼图问题,8数码问题?)
  14. java 将网页表格导出_Java导出网页表格Excel过程详解
  15. CGAN(条件生成-对抗网络)简述教程
  16. M-LAG—跨设备链路聚合组
  17. 采用轻型MiWi协议,Microchip发起进军WPAN首轮
  18. 推理规则/经典规则(排中律/反证法双重否定消除)
  19. 编译goldfish2.6.9遇见的问题
  20. 动物系列3D虚拟解剖软件助力畜牧兽医专业学习

热门文章

  1. 通过修改mateMask的nonce值修复replacement transaction underpriced的问题
  2. 【GreenPlum】使用gprecoverseg报gprecoverseg failed
  3. 怎么入门Java编程?
  4. 怎么设置能在IIS6内设置显示错误信息?
  5. 存储基础知识 - 传统存储 NAS SAN 和 分布式存储对比
  6. 【web】React-hooks
  7. 计算机初级培训教学大纲,计算机初级培训教学大纲.doc
  8. 统计之 - 离均差平方和
  9. 时间都去哪儿了? 番茄钟告诉你答案
  10. 火狐(FireFox)黑客常用插件