返回

剑指动态规划:算法优化,化繁为简!

前端

众所周知,动态规划是算法设计中的一种重要技术,其原理是在解决问题的过程中保存中间结果,避免重复计算,从而提高效率。在 leetcode 刷题过程中,动态规划题目更是层出不穷。本文将分享一系列动态规划题目,并附上详细题解,帮助你轻松掌握动态规划的奥妙,化繁为简。

入门题

斐波那契数列

斐波那契数列的定义是:F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)(n >= 2)。请编写一个函数,给定一个整数 n,返回第 n 个斐波那契数。

官方题目: https://leetcode.cn/problems/fibonacci-number/

题解:

使用动态规划,建立一个 dp 数组,存储前面 n 个斐波那契数。当 n 小于等于 1 时,直接返回 n。否则,返回 dp[n-1] + dp[n-2]。

const fib = (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];
};

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 阶。有多少种不同的方法可以爬到楼顶呢?

官方题目: https://leetcode.cn/problems/climbing-stairs/

题解:

定义 dp[i] 为爬到第 i 阶楼梯的方法数。那么,爬到第 i 阶楼梯的方法数要么是从第 i-1 阶爬上来,要么是从第 i-2 阶爬上来。因此,dp[i] = dp[i-1] + dp[i-2]。

const climbStairs = (n) => {
  if (n <= 2) {
    return n;
  }
  const dp = [0, 1, 2];
  for (let i = 3; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }
  return dp[n];
};

硬币找零

给你一个整数数组 coins,表示不同面额的硬币;以及一个整数 amount,表示总金额。请计算有多少种组合方式可以凑成总金额。

官方题目: https://leetcode.cn/problems/coin-change-2/

题解:

定义 dp[i][j] 为使用前 i 个硬币凑成金额 j 的组合数。那么,如果第 i 个硬币的面额为 coins[i-1],则凑成金额 j 的组合数要么是使用前 i-1 个硬币凑成金额 j,要么是使用前 i 个硬币凑成金额 j-coins[i-1]。因此,dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]]。

const change = (coins, amount) => {
  const dp = new Array(coins.length + 1).fill(0).map(() => new Array(amount + 1).fill(0));
  dp[0][0] = 1;
  for (let i = 1; i <= coins.length; i++) {
    for (let j = 0; j <= amount; j++) {
      dp[i][j] = dp[i - 1][j];
      if (j - coins[i - 1] >= 0) {
        dp[i][j] += dp[i][j - coins[i - 1]];
      }
    }
  }
  return dp[coins.length][amount];
};

进阶题

最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

官方题目: https://leetcode.cn/problems/longest-common-subsequence/

题解:

定义 dp[i][j] 为字符串 text1 的前 i 个字符和字符串 text2 的前 j 个字符的最长公共子序列的长度。那么,如果 text1 的第 i 个字符与 text2 的第 j 个字符相等,则 dp[i][j] = dp[i-1][j-1] + 1。否则,dp[i][j] = max(dp[i-1][j], dp[i][j-1])。

const longestCommonSubsequence = (text1, text2) => {
  const m = text1.length;
  const n = text2.length;
  const dp = new Array(m + 1).fill(0).map(() => new 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];
};

最大子数组和

给定一个整数数组 nums,找到一个连续子数组,其和最大。返回最大的和。

官方题目: https://leetcode.cn/problems/maximum-subarray/

题解:

定义 dp[i] 为以第 i 个元素结尾的连续子数组的最大和。那么,dp[i] 要么是 dp[i-1] 加上第 i 个元素,要么是第 i 个元素。因此,dp[i] = max(dp[i-1] + nums[i], nums[i])。

const maxSubArray = (nums) => {
  const dp = new Array(nums.length).fill(0);
  dp[0] = nums[0];
  for (let i = 1; i < nums.length; i++) {
    dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
  }
  return Math.max(...dp);
};

总结

通过以上题目的讲解,相信你已经对动态规划有了更深入的理解。动态规划是一种强大的算法设计技术,可以有效解决许多原本复杂的算法问题。在 leetcode 刷题的过程中,掌握动态规划的精髓至关重要。希望本文能为你的动态规划学习之旅增添一份助力。