leetcode
leetcode 2551 ~ 2600
砍竹子 I

砍竹子 I

难度:

标签:

题目描述

English description is not available for the problem. Please switch to Chinese.

代码结果

运行时间: 52 ms, 内存: 14.8 MB


/*
 * 题目思路:
 * 使用 Java Stream API 来实现动态规划。
 * 由于 Java Stream 并不适合直接处理带有副作用的动态规划问题,
 * 因此,我们还是需要依赖数组存储中间结果。
 */
import java.util.stream.IntStream;
public class Solution {
    public int integerBreak(int bamboo_len) {
        int[] dp = new int[bamboo_len + 1];
        dp[1] = 1;
        IntStream.rangeClosed(2, bamboo_len).forEach(i -> 
            IntStream.range(1, i).forEach(j -> 
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]))
            )
        );
        return dp[bamboo_len];
    }
}

解释

方法:

该题解采用了动态规划的思路来解决问题。函数 `dp(i)` 表示长度为 `i` 的竹子能得到的最大乘积。基本情况是当竹子长度为 2 时,只能分为两段,每段长度为 1,乘积为 1。对于长度大于 2 的竹子,我们尝试每种可能的分割方式(即从长度 2 到 i-1),并计算两种情况:1) 将竹子分割为 j 和 i-j,继续分割 i-j;2) 将竹子分割为 j 和 i-j,但不再分割 i-j。这两种情况中的最大值会被存储在 memo 字典中,以避免重复计算。最后返回长度为 n 的竹子的最大乘积。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么动态规划适用于这类问题,而不是贪心或其他算法?
动态规划适用于这类问题因为它可以通过解决子问题来构建对整个问题的解答,并且这些子问题往往重叠,即多次出现在问题的不同部分。在砍竹子的问题中,最优切割方式可能需要考虑所有可能的分割组合,动态规划通过存储这些子问题的解(memoization)避免重复工作,从而提高效率。相比之下,贪心算法在每一步只采取局部最优解,可能无法得到全局最优解,特别是在分割决策互相影响时。其他算法如回溯或分治可能效率较低,因为它们没有利用子问题解的重复性,可能导致计算量大增。
🦆
在动态规划的实现中,函数 `dp(i)` 反复计算 `max(j * dp(i - j), j * (i - j))`,请问这里的两种情况具体代表什么意义?
在函数 `dp(i)` 中,`max(j * dp(i - j), j * (i - j))` 的两种情况分别代表:1) 将竹子首先切割为长度为 `j` 和 `i-j` 的两段,其中 `i-j` 部分继续进行最优分割,即 `dp(i-j)` 表示对长度为 `i-j` 的竹子继续进行分割所能得到的最大乘积。2) 将竹子切割为长度为 `j` 和 `i-j` 的两段,但是 `i-j` 部分不再进行分割,直接考虑这两段的乘积 `j * (i - j)`。这样的分割策略确保了所有可能的分割方式都被考虑,从而找到最大的乘积值。
🦆
在实现中,为什么选择从 `2` 到 `i-1` 范围内进行迭代?对于 `j = 1` 的情况是如何处理的,为什么它被省略了?
在实现中选择从 `2` 到 `i-1` 进行迭代是因为:当 `j=1` 时,剩余部分 `i-1` 与 `1` 相乘,实际上没有改变值,即 `1 * (i-1) = i-1`。这意味着这种分割并没有带来任何增值,仅仅是将竹子分成了长度为 1 和 `i-1` 的两部分,与不分割相比没有任何乘积上的增益。因此,从 `j=2` 开始迭代既可以避免无效的计算,也保证了每次分割都可能带来乘积的增加。

相关问题