目录
大多数的动态规划,本质都是递归问题,只是在递归的过程中会有很多的重叠子问题
对于重叠子问题,可以用记忆化搜索:自顶向下的解决问题,而动态规划是自底向上的解决问题。
动态规划:将原问题拆解成若干子问题,同时保存子问题的答案,使得每个子问题只求解一次,最终获得原问题的答案
因此我们在做动态规划问题时,可以先思考他的记忆化搜索模型,在去推算出他的动态规划模型,比较好想到
70.爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1:
输入: 2 输出: 2 解释: 有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入: 3 输出: 3 解释: 有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
普通的递归查询,需要查询所有的情况,在LeetCode上是无法通过的
但是会有很多次重复的查询,如下图n-2
因此可以采用记忆化搜索对这些重复计算的分支减去,再者,可以通过记忆化搜索的思想,转变成递推
也就是动态规划的解决方法
/********* 普通的递归过程:LeetCode无法通过 *********/
class Solution {
/**
* @param Integer $n
* @return Integer
*/
function climbStairs($n) {
return $this->calway($n);
}
private function calway($n){
if($n == 0 || $n ==1) return 1; //层层递归过程
return $this->calway($n-1) + $this->calway($n-2);
}
}
/********* 记忆化搜索:LeetCode 16ms *********/
class Solution {
private $memory = [];
/**
* @param Integer $n
* @return Integer
*/
function climbStairs($n) {
return $this->calway($n);
}
private function calway($n){
if($n == 0 || $n ==1) return 1;
if(empty($this->memory[$n]))
$this->memory[$n] = $this->calway($n-1) + $this->calway($n-2);
return $this->memory[$n];
}
}
/********* 动态规划:LeetCode 12ms *********/
class Solution {
/**
* @param Integer $n
* @return Integer
*/
function climbStairs($n) {
$memory = []; //自下而上
$memory[0] = 1; //递推,爬上0阶楼梯有1种方法,往上推
$memory[1] = 1;
for($i = 2;$i<=$n;++$i){
$memory[$i] = $memory[$i-1] + $memory[$i-2];
}
return $memory[$n];
}
}
120.三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径和为 11 (即,2 + 3 + 5 + 1 = 11)。
说明:
如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。
普通的递归:内部的节点都要重复计算两次,因此肯定会超出时间限制
所以可以采用记忆化搜索和动态规划的解法
记忆化搜索:自顶向下,会记住节点对应的最小路径和
动态规划:自底向上,计算每一层的节点往下的最小路径和,最后结果就是顶点
/********** 普通递归:LeetCode 超出时间限制 **********/
class Solution {
private $triangle,$len;
/**
* @param Integer[][] $triangle
* @return Integer
*/
function minimumTotal($triangle) {
$this->len = count($triangle) - 1;
$this->triangle = $triangle;
$res = $this->minPath(0,0); //从第0个开始往下层递归
return $res;
}
function minPath($row,$col){
$res = $this->triangle[$row][$col];
if($row == $this->len) return $res;
$add = min($this->minPath($row+1,$col),$this->minPath($row+1,$col+1)); //去下方两条路中和比较小的一条
return $res + $add; //与上一层元素相加返回
}
}
/********** 记忆化搜索递归:56 ms **********/
class Solution {
private $triangle,$len; //初始化三角形表格和数组高度
private $memory; //初始化记忆数组
/**
* @param Integer[][] $triangle
* @return Integer
*/
function minimumTotal($triangle) {
$this->len = count($triangle) - 1;
$this->triangle = $triangle;
$res = $this->minPath(0,0);
return $res;
}
function minPath($row,$col){
$res = $this->triangle[$row][$col];
if($row == $this->len) return $res;
if(empty($this->memory[$row+1][$col])) //记录该元素点往下的最小和
$this->memory[$row+1][$col] = $this->minPath($row+1,$col);
if(empty($this->memory[$row+1][$col+1]))
$this->memory[$row+1][$col+1] = $this->minPath($row+1,$col+1);
$add = min($this->memory[$row+1][$col],$this->memory[$row+1][$col+1]); //两条路径的最小和
return $res + $add;
}
}
/********** 动态规划:32 ms **********/
class Solution {
/**
* @param Integer[][] $triangle
* @return Integer
*/
function minimumTotal($triangle) {
$len = count($triangle);
if($len == 0) return 0;
if($len == 1) return $triangle[0][0]; //从下往上,依次取该节点的最小路径和
for($i = $len-2;$i>=0;--$i){ //第一层保持原样不需要计算
$colen = count($triangle[$i]);
for($j = 0;$j < $colen;++$j){
$triangle[$i][$j] += min($triangle[$i+1][$j],$triangle[$i+1][$j+1]); //加上该节点对应的下方两条路径的最大和
}
}
return $triangle[0][0]; //最后调用最顶部的元素就是最终答案
}
}
64.最小路径和
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入: [ [1,3,1], [1,5,1], [4,2,1] ] 输出: 7 解释: 因为路径 1→3→1→1→1 的总和最小。
该题也可以用递归,记忆化搜索和动态规划来解决,该题是比较简单的动态规划问题
解法:
(1)遍历整张图,从左上到右下,让每一个节点的值都是该点到左上角的最小路径
(2)其中需要注意,左边界和上边界都只有一条路径,不需要进行最小化的判断,其他节点需要加上取左或上元素中偏小的节点
(3)最终的结果就是右下角的节点,返回即可
class Solution {
/**
* @param Integer[][] $grid
* @return Integer
*/
function minPathSum($grid) {
$row = count($grid);
if($row == 0) return 0;
$col = count($grid[0]);
for($i = 0;$i<$row;++$i){
for($j = 0;$j<$col;++$j){ //遍历整个图,构造动态规划
if($i == 0){ //每个节点都是从左上角到该点的最短路径
$grid[$i][$j] += empty($grid[$i][$j-1]) ? 0:$grid[$i][$j-1]; //上边界
}elseif($j == 0){
$grid[$i][$j] += $grid[$i-1][$j]; //下边界都只有一条路径,因此不用进行帕努单
}else{
$grid[$i][$j] += min($grid[$i-1][$j],$grid[$i][$j-1]); //下方路径取左或上较小的节点
}
}
}
return $grid[$row-1][$col-1];
}
}