返回

最后一块石头的重量:动态规划求解,实现一步步碾压

前端

征服算法巨石:攻克 LeetCode 1046 最后一块石头的重量

踏入算法世界的征途,LeetCode 俨然矗立于面前的一座巍峨高峰。其中的 1046. 最后一块石头的重量就像一块坚固的磐石,考验着算法爱好者的毅力与智慧。今天,让我们换个视角,踏上征服这块巨石的征途。

理解题目的本质

这场算法较量是一场石头的重量较量。每回合中,两块最重的石头被选中,粉碎殆尽。我们的目标,就是要找出这堆石头中最后一块石头的重量。

动态规划:算法的明灯

面对浩瀚的算法海洋,动态规划就像一艘指引我们航行的明灯。它是一种自顶向下的求解方式,将大问题拆解成一系列小问题,逐步解决。

动态规划的奥秘:

  1. 状态定义: dp[i] 表示前 i 块石头的最后一块石头的重量。
  2. 状态转移方程: 对于第 i 块石头,有两种可能:
    • 与第 j 块石头(j < i)一起粉碎,得到重量 dp[i] = dp[j] - dp[i - j - 1]
    • 保留第 i 块石头,得到重量 dp[i] = dp[i - 1]
    • 我们选择重量最大的那个作为 dp[i]
  3. 初始化: dp[1] = stones[0]
  4. 答案: 最后一块石头的重量就是 dp[n],其中 n 是石头的总数。

代码实现

Java 代码:

public class LastStoneWeight {
    public int lastStoneWeight(int[] stones) {
        int n = stones.length;
        int[] dp = new int[n + 1];
        dp[1] = stones[0];

        for (int i = 2; i <= n; i++) {
            for (int j = 1; j < i; j++) {
                dp[i] = Math.max(dp[i], dp[j] - dp[i - j - 1]);
            }
            dp[i] = Math.max(dp[i], dp[i - 1]);
        }

        return dp[n];
    }
}

Python 代码:

def lastStoneWeight(stones):
    dp = [0] * len(stones)
    dp[1] = stones[0]

    for i in range(2, len(stones)):
        for j in range(1, i):
            dp[i] = max(dp[i], dp[j] - dp[i - j - 1])
        dp[i] = max(dp[i], dp[i - 1])

    return dp[len(stones) - 1]

复杂度分析

  • 时间复杂度:O(n^2),其中 n 是石头的数量。
  • 空间复杂度:O(n),用于存储 dp 数组。

总结

征服最后一块石头的重量,我们领略了动态规划的强大。它将看似复杂的问题化繁为简,一步步引领我们走向胜利。当我们用算法思维审视世界,原本晦涩难懂的问题也会变得清晰明了。

常见问题解答

1. 动态规划和贪心算法有什么区别?

动态规划自顶向下,将大问题分解成小问题逐步解决,而贪心算法从上至下,每次做出看似最优的选择。

2. 最后一块石头的重量中是否存在最优子结构?

是的,两个最重的石头粉碎后,其重量之差为其后的最优子结构。

3. 动态规划的初始化条件为何重要?

初始化条件为算法提供起始点,确保算法的正确性。

4. 如何优化动态规划算法的空间复杂度?

使用滚动数组或空间优化技术,将空间复杂度降低到 O(1) 或 O(n)。

5. 最后一块石头的重量的动态规划解法为何比暴力解法更有效率?

动态规划利用存储的中间结果,避免重复计算,显著提升效率。