动态规划
概念
动态规划:是一种解决问题的思想,大规模问题的结果,是由小规模问题的结果运算得来的。动态规划可用递归来实现(Memorization Search).
算法思想
动态规划其实是运筹学的一种最优化方法,求解动态规划的核心问题是穷举。
暴力的递归解法 -> 带备忘录的递归解法(自顶向下) -> 迭代的动态规划解法(自底向上,遍历枚举最小子问题,一般用数组记录已有解)。
- 首先,动态规划的穷举有点特别,因为这类问题存在「重叠子问题」,如果暴力穷举的话效率会极其低下,所以需要「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。
- 而且,动态规划问题一定会具备「最优子结构」,才能通过子问题的最值得到原问题的最值。要符合「最优子结构」,子问题间必须互相独立。
- 另外,虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,只有列出正确的「状态转移方程」才能正确地穷举。
动态规划三要素
- 重叠子问题
- 最优子结构
- 状态转移方程
如何列出正确的状态转移方程?
- 确定最小极限状态 => 起点
- 确定「状态」,也就是原问题和子问题中会变化的变量
- 确定「选择」,也就是导致「状态」产生变化的行为
4、明确 dp 函数/数组的定义。
算法模版
以下以中等-零钱兑换为例:
自顶向下
带备忘录的递归解法, 递归过程中,常用hash表作为记录已有解
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
let len = coins.length;
// 动态规划自上而下
let map = new Map();
function dp (total) {
if (!total) return 0;
if (total < 0) return -1;
if (map.has(total)) return map.get(total);
let res = Infinity;
coins.forEach(item => {
let sub = dp(total - item);
if (sub === -1) return;
res = Math.min(res, sub + 1);
});
res = res === Infinity ? -1 : res;
map.set(total, res);
return res;
}
return dp(amount);
};
自底向上
迭代的动态规划解法,遍历枚举最小子问题,一般用数组记录已有解,新的解是在已有的解基础上建立
/**
* @param {number[]} coins
* @param {number} amount
* @return {number}
*/
var coinChange = function(coins, amount) {
// 动态规划自下而上
let dp = [0];
for (let i = 1; i <= amount; i ++) { // 遍历枚举,最小子问题, 即为输入的amount
dp[i] = Infinity;
coins.forEach(item => {
if (i - item < 0) return;
dp[i] = Math.min(dp[i], dp[i - item] + 1);
});
}
return dp[amount] === Infinity ? -1 : dp[amount];
};
相关算法题
简单-斐波那契数
简单-爬楼梯
中等-零钱兑换
中等-三角形最小路径和
矩阵类型
子序列类型
int n = arr.length;
int[][] dp = new dp[n][n];
for (int i = 0; i < n; i++) {
for (int j = 1; j < n; j++) {
if (arr[i] == arr[j])
dp[i][j] = dp[i][j] + ...
else
dp[i][j] = 最值(...)
}
}

浙公网安备 33010602011771号