leetcode
leetcode 2551 ~ 2600
砍竹子 II

砍竹子 II

难度:

标签:

题目描述

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

代码结果

运行时间: 1312 ms, 内存: 16 MB


/*
 * 题目思路:
 * 将一根长度为 bamboo_len 的竹子分成若干段,每段的长度都是正整数。
 * 我们需要找到分割方式,使得这些段的长度乘积最大。
 * 可以利用动态规划来解决这个问题,dp[i] 表示将长度为 i 的竹子分割成若干段后的最大乘积。
 * 递推公式为:dp[i] = max(j * (i - j), j * dp[i - j]),其中 j 从 1 遍历到 i-1。
 * 使用 Java Stream 处理循环。
 */

import java.util.stream.IntStream;

public class BambooCuttingStream {
    public static int maxProduct(int bamboo_len) {
        int MOD = 1000000007;
        int[] dp = new int[bamboo_len + 1];
        dp[0] = 0;
        dp[1] = 0;
        for (int i = 2; i <= bamboo_len; i++) {
            final int finalI = i;
            dp[i] = IntStream.range(1, i).map(j -> Math.max(j * (finalI - j), j * dp[finalI - j])).max().orElse(0) % MOD;
        }
        return dp[bamboo_len];
    }

    public static void main(String[] args) {
        System.out.println(maxProduct(12)); // 输出 81
    }
}

解释

方法:

题解使用了自顶向下的动态规划方法,并利用记忆化来存储已经计算过的结果,避免重复计算,提高效率。函数dp(i)的作用是找到长度为i的竹子能得到的最大乘积。基本情况是当i等于2时,最大乘积为1。对于i大于2的情况,我们通过尝试每一种可能的第一段长度j(从2到i-1),计算两种情况的最大值:一是将竹子剩余部分继续切割得到的最大乘积(j * dp(i - j)),二是直接将竹子切成j和i-j两段(j * (i - j))。这两种情况的最大值,即为当前i的最大乘积。这样的递归确保我们可以从已解决的子问题构建出更大问题的解答。

时间复杂度:

O(n^2)

空间复杂度:

O(n)

代码细节讲解

🦆
在动态规划的实现中,为何选择自顶向下的方式而非自底向上?各有什么优缺点?
自顶向下的动态规划方法,也称为记忆化递归,从最终目标开始解决问题,逐步分解为较小的子问题。该方法的优点在于只解决实际需要的子问题,节省计算资源,特别是当部分子问题不必解决时。缺点是递归调用可能导致较大的栈开销,且递归结构可能在理解和调试上更复杂。相对地,自底向上的方法,即迭代方法,从最小的子问题开始,逐步构建解决方案直至达到最终目标,通常在空间效率上更优,易于理解和实施,但可能会计算一些不必要的状态。
🦆
函数dp(i)中,为什么要比较`j * dp(i - j)`和`j * (i - j)`这两种情况?实际上这两种情况有什么区别?
在函数`dp(i)`中, `j * dp(i - j)`考虑的是将长度为i的竹子切成长度为j和i-j的两段,其中长度为i-j的段继续进行切割以获取最大乘积。而`j * (i - j)`则是将竹子直接切成两段,不对长度为i-j的段进一步切割。这两种情况的区别在于是否继续对剩余部分进行优化切割。比较这两种情况是为了确保在每一步都能获得可能的最大乘积,无论是继续切割或是停止切割。
🦆
在计算最大乘积时,为什么从2开始循环到i-1,将i本身作为一段的情况不被考虑?
在计算最大乘积时,从2开始循环到i-1是因为至少需要切割出两段,每段最小长度为1。若考虑将i本身作为一段,则不会有任何切割发生,这与题目要求找到切割后的最大乘积相违背。因此,我们不考虑将整个i作为一段的情况,而是寻找至少一次切割的最优解。
🦆
在取模操作(% 1000000007)中,为什么要加这一步,直接返回dp(n)的结果会有什么问题?
在取模操作(% 1000000007)中,加入这一步是为了防止结果数值过大而超出整数表示范围,特别是在涉及大数的程序设计语言中。1000000007是一个大质数,常用于避免溢出同时减少计算结果的碰撞概率。直接返回dp(n)的结果可能导致整数溢出,从而得到错误的输出。取模操作确保结果保持在安全的数值范围内。

相关问题