目录
2. 杨辉三角:js.杨辉三角和分发饼干_js输出杨辉三角-优快云博客
动态规划(Dynamic Programming, DP)是一种解决复杂问题的算法思想,它将大问题分解为小问题,通过存储和重用子问题的解来避免重复计算,从而提高效率。
一、基本概念
1. 核心思想
-
分治思想:将原问题分解为若干子问题
-
记忆化存储:保存已解决的子问题的答案
-
空间换时间:通过存储中间结果减少重复计算
2. 适用场景
动态规划通常适用于具有以下特征的问题:
-
最优子结构:问题的最优解包含子问题的最优解
-
重叠子问题:不同的子问题会重复计算相同的更小子问题
-
无后效性:当前状态只与之前状态有关,与之后状态无关
二、动态规划的实现步骤
-
定义状态:明确dp数组或状态表的含义
-
确定状态转移方程:找出如何从子问题的解得到更大问题的解
-
初始化边界条件:确定最小子问题的解
-
确定计算顺序:通常自底向上或自顶向下
-
返回最终解
三、经典问题示例
1. 爬楼梯:js.爬楼梯-优快云博客
2. 杨辉三角:js.杨辉三角和分发饼干_js输出杨辉三角-优快云博客
3. 打家劫舍:js.打家劫舍-优快云博客
4. 零钱兑换:js.零钱兑换-优快云博客
5. 斐波那契数列
问题描述:计算第n个斐波那契数
function fibonacci(n) {
if (n <= 1) return n;
let dp = new Array(n + 1);
dp[0] = 0; // 初始化
dp[1] = 1;
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程
}
return dp[n];
}
// 空间优化版
function fibonacciOptimized(n) {
if (n <= 1) return n;
let prev = 0, curr = 1;
for (let i = 2; i <= n; i++) {
let next = prev + curr;
prev = curr;
curr = next;
}
return curr;
}
6. 背包问题(0-1 Knapsack)
问题描述:给定物品的重量和价值,在不超过背包容量的情况下,选择物品使总价值最大
function knapsack(weights, values, capacity) {
const n = weights.length;
const dp = Array.from({ length: n + 1 }, () =>
new Array(capacity + 1).fill(0)
);
for (let i = 1; i <= n; i++) {
for (let w = 1; w <= capacity; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(
values[i - 1] + dp[i - 1][w - weights[i - 1]],
dp[i - 1][w]
);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[n][capacity];
}
7. 单词拆分
问题描述:
给你一个字符串
s
和一个字符串列表wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出s
则返回true
。注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
示例 1:
输入: s = "leetcode", wordDict = ["leet", "code"] 输出: true 解释: 返回 true 因为 "leetcode" 可以由 "leet" 和 "code" 拼接成。示例 3:
输入: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"] 输出: false提示:
1 <= s.length <= 300
1 <= wordDict.length <= 1000
1 <= wordDict[i].length <= 20
s
和wordDict[i]
仅由小写英文字母组成,wordDict
中的所有字符串 互不相同
思路:dp[i]表示从前往后s的第i个字符是否能被拼接出来。
代码:
/**
* @param {string} s
* @param {string[]} wordDict
* @return {boolean}
*/
var wordBreak = function(s, wordDict) {
let dp = Array(s.length).fill(0)
for(let i = 0 ; i < s.length ; i++ ){
for(let j = 0 ; j < wordDict.length ; j++ ){
let wordLen = wordDict[j].length
if(i<wordLen-1) continue
let str = s.slice(i-wordLen+1,i+1)
if(str == wordDict[j]){
dp[i] = i-wordLen>=0?dp[i-wordLen]:1
if(dp[i]==1) break
}
}
}
return dp[s.length-1]==0?false:true
};
四、常见动态规划问题分类
-
线性DP:斐波那契、最大子数组和
-
区间DP:矩阵链乘法、石子合并
-
背包DP:0-1背包、完全背包
-
树形DP:二叉树中的最大路径和
-
状态压缩DP:旅行商问题(TSP)
-
数位DP:数字计数问题
五、动态规划与贪心算法的区别
特性 | 动态规划 | 贪心算法 |
---|---|---|
子问题 | 考虑所有子问题 | 只考虑当前最优 |
结果 | 全局最优解 | 局部最优解(可能非全局最优) |
复杂度 | 通常较高 | 通常较低 |
适用性 | 更广泛 | 问题需有贪心选择性质 |