一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。关于学习动态规划的总结,参考了代码随想录和labuladong的算法小抄。
题目描述
给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col) 的下一个元素应当是 (row + 1, col - 1)、(row + 1, col) 或者 (row + 1, col + 1) 。
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/mi… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
输入: matrix = [[2,1,3],[6,5,4],[7,8,9]]
输出: 13
解释: 如图所示,为和最小的两条下降路径
动态规划思想
概念引入
在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线.这种把一个问题看作是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题称为多阶段决策问题。在多阶段决策问题中,各个阶段采取的决策,一般来说是与时间有关的,决策依赖于当前状态,又随即引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化的过程为动态规划方法。
🌟自底向上+dp table
- 看问题的“状态”和我们可以做的“选择”⇒明确dp[i]的含义,是一维还是二维
- 确定状态转移方程:dp[i-1]已知,如何推出dp[i]?
- 确定遍历顺序,先考虑基线情况(eg.可以直接得到dp[0]/dp[1]/可以return的情况/max的设定等)
- 修改一些特殊位置的状态转移方程(数组越界)
自顶向下 带备忘录的dp函数
备忘录memo:初始化一个长度为N+1的数组,来存放f( N ),f(N - 1),...,f( 1 ),已解决重复子问题的冗余计算。
递归算法的时间复杂度?
用子问题个数 × 解决一个子问题需要的时间。
子问题个数===递归树中节点的总数,像斐波那契数列return f(n-1)+f(n-2),子问题个数为O(n^2)。
题解
- DP数组版本 自底向上 比较好理解
var minFallingPathSum = function (matrix) {
//1.dp[i][j]表示走到matrix[i][j]这一格的下降路径最小和
//2.最普通的情况,状态转移方程:dp[i][j] = matrix[i][j]+max(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]);
let len = matrix[0].length;
if (len === 1) return matrix[0][0];
let dp = Array(len).fill().map(() => Array(len));
dp[0] = matrix[0];
let minSum = Number.MAX_VALUE;
for(let i = 1;i < len;i++){
for(let j = 0;j <len;j++){
//处理两个边界
if(j === 0)dp[i][j] = matrix[i][j]+Math.min(dp[i-1][j],dp[i-1][j+1]);
else if(j === len-1)dp[i][j] = matrix[i][j]+Math.min(dp[i-1][j-1],dp[i-1][j]);
else dp[i][j] = matrix[i][j]+Math.min(dp[i-1][j-1],dp[i-1][j],dp[i-1][j+1]);
//选最后一行里最小的
if(dp[len-1][j] !== undefined) minSum = Math.min(minSum,dp[len-1][j]);
}
}
return minSum;
};
- 再贴一个DP函数版本 自顶向下:
let memo = [];
var minFallingPathSum = function (matrix) {
let n = matrix.length;
if (n === 1) return matrix[0][0];
memo = Array(n).fill().map(()=>Array(n).fill(627));
let minSum = Number.MAX_VALUE;
for(let j = 0;j < n;j++){
minSum = Math.min(minSum,dp(matrix,n-1,j));
}
return minSum;
};
//dp函数返回:走到matrix[i][j]位置的最小下降路径
var dp = (matrix, i, j) => {
let n = matrix.length;
//1.越界情况
if (i < 0 || j < 0 || i >= n || j >= n) return Number.MAX_VALUE;
//2.第一行是matrix原位置值
if (i === 0) return matrix[0][j];
//3.如果有存档(不是默认数字),直接读取
if (memo[i][j] !== 627) return memo[i][j];
//状态转移:
memo[i][j] = matrix[i][j] + Math.min(
dp(matrix, i - 1, j - 1),
dp(matrix, i - 1, j),
dp(matrix, i - 1, j + 1)
);
return memo[i][j];
}