目录
最优子结构:通过求子问题的最优解,可以获得原问题的最优解
343.整数拆分
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
示例 1:
输入: 2 输出: 1 解释: 2 = 1 + 1, 1 × 1 = 1。
示例 2:
输入: 10 输出: 36 解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。
说明: 你可以假设 n 不小于 2 且不大于 58。
分析:如下图,以4为分割数为例
(1)想获得分割4的乘积可以有3种方案,可以分为 1+3,2+2,3+1,所以该问题可以化成求分割3/2/1所获得的最大乘积
(2)同理,分割3的方案可以分为,分割2和分割1,分割2又可以分割 1的方案,1无法再继续进行分割
(3)以此类推,可以求得所有情况可以获得的最大乘积,但如下图可以知道,有很多重复计算的分支,如分割2获得最大乘积就重复计算了两次,因此这里就可以采用记忆化搜索的方式去解答
直接相出动态规划的解法可能相对有些困难,但是如果通过自顶向下的记忆化搜索,就可以比较方便的推算出自底向上的动态规划算法
/********* 普通的递归:超出时间限制 *********/
class Solution {
/**
* @param Integer $n
* @return Integer
*/
function integerBreak($n) {
return $this->breakInterger($n);
}
/**
* [将n进行分割(至少分割两部分),可以获得的最大乘积]
* @param $n [当前的数字]
*/
private function breakInterger($n){
if($n == 1) return 1;
$res = -1;
for($i = 1;$i<=$n-1;++$i){
//这样可以获得,i + ( n - i)
$res = max($res, $i * ($n-$i), $i * $this->breakInterger($n - $i));
}
return $res;
}
}
/********* 记忆化搜索:16ms *********/
class Solution {
private $memory = []; //初始化记忆数组
/**
* @param Integer $n
* @return Integer
*/
function integerBreak($n) {
return $this->breakInterger($n);
}
/**
* [将n进行分割(至少分割两部分),可以获得的最大乘积]
* @param $n [当前的数字]
*/
private function breakInterger($n){
if($n == 1) return 1; //当数=1时,已经无法继续分割,直接返回
//当之前已经找到过该数的分割最大乘积时,可以直接返回
if(isset($this->memory[$n])) return $this->memory[$n];
//否则则继续下面的流程
$res = -1;
for($i = 1;$i<=$n-1;++$i){
//这样可以获得,i + ( n - i)
$res = max($res, $i * ($n-$i), $i * $this->breakInterger($n - $i));
}
$this->memory[$n] = $res; //将该数取得的最大结果写入记忆数组中
return $res;
}
}
/********* 动态规划:16ms *********/
class Solution {
/**
* @param Integer $n
* @return Integer
*/
function integerBreak($n) {
$memory = []; //初始化记忆数组
$memory[1] = 1; //$memory[$i]表示将$i进行分割(至少分割两部分)后获得的最大乘积
for($i = 2;$i<=$n;++$i){
//求解$memory[$i]
for($j = 1;$j<=$i-1;++$j){
//$j + ($i - $j)
//当前数的乘积取
$memory[$i] = max(empty($memory[$i])?0:$memory[$i], $j * ($i - $j),$j * $memory[$i-$j]);
}
}
return $memory[$n];
}
}
91.解码方法
一条包含字母 A-Z
的消息通过以下方式进行了编码:
'A' -> 1
'B' -> 2
...
'Z' -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
示例 1:
输入: "12" 输出: 2 解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。
示例 2:
输入: "226" 输出: 3 解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。
动态规划:化为每一个子问题的最优解==>化为每一个位置的最多种解码可能,直到最后一个位置
注意:
(1)前两个数字,比较关键,需要进行特殊处理
(2)如果当前数字为0,则不进行单独考虑,当前dp数组为0
(3)如果当前数字不为0,则可配合前1个dp进行编码,当前$dp[$i] = $dp[$i-1]
(4)如果当前数字和前一个数字搭配小于26,则这两个数字可配合前2个dp进行编码,当前$dp[$i] += $dp[$i-2]
class Solution {
/**
* @param String $s
* @return Integer
*/
function numDecodings($s) {
$dp = [];
$len = strlen($s); //初始化动态规划数组和长度
if($len == 0) return 0; //若长度为0,不能编码,直接返回0
//初始化前两个数组的编码种类
$dp[0] = $s[0] == '0' ? 0:1;
if($len == 1) return $dp[0];//当长度为 1 时,返回第一个字母的动态规划解
$flag = ($s[0] > 0 && $s[1] > 0) ? 1:0; //判断前两位有 0 的特殊情况
$dp[1] = $flag + (($s[0] == 1 || ($s[0] == 2 && $s[1] <= 6)) ? 1:0); //小于26时,再+1种可能
for($i = 2;$i<$len;++$i){ //从第3位继续计算
//如果当前的数字为 0 ,需要与前一位一起考虑,不单独考虑
$dp[$i] = $s[$i] == 0 ? 0:$dp[$i-1];
//当前位置跟前一位是否<=26,如果符合,则在 $i - 2 位上解码
$dp[$i] += ($s[$i-1] == 1 || ($s[$i-1] == 2 && $s[$i] <= 6)) ? $dp[$i-2] : 0;
}
return $dp[$len-1];
}
}
62.不同路径
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?
例如,上图是一个7 x 3 的网格。有多少可能的路径?
说明:m 和 n 的值均不超过 100。
示例 1:
输入: m = 3, n = 2 输出: 3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向右 -> 向下 2. 向右 -> 向下 -> 向右 3. 向下 -> 向右 -> 向右
示例 2:
输入: m = 7, n = 3 输出: 28
动态规划:化为每一个子问题的最优解==>每一个点的坐标值化为左上角到该点的路径总数
class Solution {
/**
* @param Integer $m
* @param Integer $n
* @return Integer
*/
function uniquePaths($m, $n) {
$dp = [];
for($i = 0;$i<$m;++$i){
for($j = 0;$j<$n;++$j){
if($i == 0 || $j == 0) { //边界都只有一条路径
$dp[$i][$j] = 1;
}else{
$dp[$i][$j] = $dp[$i-1][$j] + $dp[$i][$j-1]; //其他方向的路径为该点的左边和上边的路径总和
}
}
}
return $dp[$m-1][$n-1]; //右下角的点的值为最终的结果
}
}
63.不同路径II
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
说明:m 和 n 的值均不超过 100。
示例 1:
输入: [ [0,0,0], [0,1,0], [0,0,0] ] 输出: 2 解释: 3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有2条不同的路径: 1. 向右 -> 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 -> 向右
与62区别:输入参数改为数组;路径途中存在障碍物
解法区别:如果该点为阻碍点,则该点的dp为0
class Solution {
/**
* @param Integer[][] $obstacleGrid
* @return Integer
*/
function uniquePathsWithObstacles($obstacleGrid) {
if(empty($obstacleGrid)) return 0; //初始化判断,数组为空,则没有路径
$dp = [];
$m = count($obstacleGrid); //初始化列
$n = count($obstacleGrid[0]); //初始化行
for($i = 0;$i<$m;++$i){
for($j = 0;$j<$n;++$j){
if($obstacleGrid[$i][$j] == 0){ //当没有阻碍时
if($i == 0 && $j == 0){ //初始化左上角节点
$dp[$i][$j] = 1;
}else{
$dp[$i][$j] = $dp[$i-1][$j] + $dp[$i][$j-1];//其他方向的路径为该点的左边和上边的路径总和
}
}else{
$dp[$i][$j] = 0; //当没有阻碍时,该点不可通过,dp为0
}
}
}
return $dp[$m-1][$n-1];
}
}