核心:动态规划(Dynamic Programming)中每一个状态一定是由上一个状态推导出来的
动态规划的解题思路
动态规划步骤
- 举例并定义子问题
- 递推的规律:即通过子问题确定递推公式或推导逻辑
- 初始化
- 确定遍历顺序
通过简单数学公式推导的动态规划
有明确的推导公式,可直接运用公式
数的求和
/**
* 数的求和:求(1+...+n)的和
* 公式:sum(n) = sum(n-1) + n;
* 跳出条件:n =1;
*/
// 递归写法
function sum(n) {
if (n === 1) return 1;
return sum(n - 1) + n;
}
// Dp
function sumDp(n) {
let ret = [];
ret[0] = 0;
for (let i = 1; i < n + 1; i++) {
ret[i] = ret[i - 1] + i;
}
return ret[n];
}
斐波那契数
/**
* 斐波那契数列 :求数列的第n项 (1,1,2,3,5....n)
* 公式:f(n)=f(n-1)+f(n-2)
* 跳出条件:n=0或者n=1
*/
// 递归写法
function fibonacci(n) {
if (n === 0 || n === 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Dp
function fibonacciDp(n) {
let ret = [];
ret[0] = 1;
ret[1] = 1;
for (let i = 2; i < n + 1; i++) {
ret[i] = ret[i - 1] + ret[i - 2];
}
return ret[n];
}
通过简单推导的动态规划
打家却舍
1. 问题
参考 leetcode:打家却舍
/**
* 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,
* 影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
* 给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
*
* 示例:
* 输入:[1,2,3,1]
* 输出:4
* 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。偷窃到的最高金额 = 1 + 3 = 4 。
*
*/
2. 思路
3. 解法
function rob(nums) {
if (nums.length === 0) {
return 0;
}
const n = nums.length;
const dp = new Array(n + 1);
dp[0] = 0;
dp[1] = nums[0];
for (let k = 2; k <= n; k++) {
dp[k] = Math.max(dp[k-1], nums[k-1] + dp[k-2]);
}
return dp[n];
}
复杂公式的动态规划
1. 题目
参考 leetcode:最长回文子串
2. 思路
3. 解法
function longestPalindrome(s) {
const len = s.length;
if (len < 2) {
return s;
}
let maxLen = 1;
let begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
const dp = new Array(len).fill(0).map(() => new Array(len).fill(0));
// 初始化:所有长度为 1 的子串都是回文串
for (let i = 0; i < len; i++) {
dp[i][i] = true;
}
// 递推开始
// 先枚举子串长度
for (let l = 2; l <= len; l++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (let i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
const j = l + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
if (s[i] !== s[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
无法用公式表达的动态规划
1. 题目
城市规划中,房屋需要按一定规划进行修建,规律如下图,根据规律求房子数为n时的排列情况
2. 思路及解法
function orderHouse(n) {
// 初始数组 [2, 1, 2], 数组各数加起来是房子总数 n
let ret = [2, 1, 2];
if (n > 5) {
// 开始循环,每次往数组内塞入一个房子
for (let i = 0; i < n - 5; i++) {
const max = ret[0]; // 当前每行最多可放置的房子数量
const length = ret.length;
// flag1234 表示前面是否没有使用这个房子,使用了后面就不再执行
let flag1 = true;
let flag2 = true;
let flag3 = true;
let flag4 = true;
let lj = Math.round(length / 2); // 奇数行数量
let lk = length - lj; // 偶数行数量
// 1.奇数行补齐房子 = max
for (let j = 0; j < length; j += 2) {
if (ret[j] < max) {
flag1 = false;
ret[j] += 1;
break;
}
}
// 2.奇数行全部补完,偶数补齐房子 = max -1
if (flag1) {
for (let k = 1; k < length; k += 2) {
if (ret[k] < max - 1) {
flag2 = false;
ret[k] += 1;
break;
}
}
// 3.奇偶行全部补完,偶数行需要补齐max或扩充
if (flag2) {
if (lj > lk) {
if (max - 1 > lk) {
// 3.1 偶数行扩充
ret.push(1);
flag3 = false
} else {
// 3.2 偶数行需要补齐max
for (let m = 1; m < length; m += 2) {
if (ret[m] < max) {
flag3 = false;
ret[m] += 1;
break;
}
}
}
} else if(lj === lk) {
// 3.1 偶数行扩充
ret.push(1);
flag4 = false
}
// 4.前面都不满足,开始横向纵向扩充
if (flag3 && flag4) {
ret[0] = ret[0] + 1;
}
}
}
}
}
return ret;
}
总结
寻找规律并一步步得出最后的结果