动态规划算法的核心思想和经典题目
动态规划(Dynamic Programming,简称 DP)是一种用于解决复杂问题的算法思想,通过将问题分解为更小的子问题,并存储这些子问题的结果以避免重复计算,从而提高算法效率。动态规划的核心思想是利用子问题的最优解来构建原问题的最优解。
核心思想
- 分解问题:
- 将原问题分解为更小的子问题。
- 存储子问题的结果:
- 使用数组或其他数据结构存储子问题的结果,以避免重复计算。
- 构建最优解:
- 利用子问题的最优解来构建原问题的最优解。
经典题目
以下是一些经典的动态规划题目及其解答:
1. 斐波那契数列
问题描述
计算斐波那契数列的第 n 个数。斐波那契数列定义如下:
F(0) = 0
F(1) = 1
F(n) = F(n-1) + F(n-2),其中 n >= 2
解答
function fibonacci(n) {
if (n <= 1) return n;
const dp = [0, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
// 示例
console.log(fibonacci(10)); // 输出: 55
2. 最长公共子序列(LCS)
问题描述
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
解答
function longestCommonSubsequence(text1, text2) {
const m = text1.length;
const n = text2.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (text1[i - 1] === text2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
// 示例
console.log(longestCommonSubsequence('abcde', 'ace')); // 输出: 3
3. 背包问题
问题描述
给定一个容量为 W 的背包和 N 个物品,每个物品有重量 weights[i] 和价值 values[i]。求在不超过背包容量的情况下,能够获得的最大价值。
解答
function knapsack(W, weights, values) {
const N = weights.length;
const dp = Array.from({ length: N + 1 }, () => Array(W + 1).fill(0));
for (let i = 1; i <= N; i++) {
for (let w = 1; w <= W; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[N][W];
}
// 示例
const weights = [2, 3, 4, 5];
const values = [3, 4, 5, 6];
const W = 5;
console.log(knapsack(W, weights, values)); // 输出: 7
4. 最长递增子序列(LIS)
问题描述
给定一个整数数组 nums,找到其中最长严格递增子序列的长度。
解答
function lengthOfLIS(nums) {
if (nums.length === 0) return 0;
const dp = Array(nums.length).fill(1);
for (let i = 1; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return Math.max(...dp);
}
// 示例
console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); // 输出: 4
5. 编辑距离(Edit Distance)
问题描述
给定两个单词 word1 和 word2,计算将 word1 转换为 word2 所使用的最少操作数。你可以对一个单词进行以下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
解答
function minDistance(word1, word2) {
const m = word1.length;
const n = word2.length;
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) {
dp[i][0] = i;
}
for (let j = 0; j <= n; j++) {
dp[0][j] = j;
}
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
}
}
}
return dp[m][n];
}
// 示例
console.log(minDistance('horse', 'ros')); // 输出: 3
总结
动态规划是一种强大的算法思想,通过将问题分解为更小的子问题,并存储这些子问题的结果以避免重复计算,从而提高算法效率。经典的动态规划题目包括斐波那契数列、最长公共子序列、背包问题、最长递增子序列和编辑距离等。通过理解和练习这些经典题目,你可以掌握动态规划的核心思想,并应用于解决复杂的问题。