leetcode
leetcode 1301 ~ 1350
切披萨的方案数

切披萨的方案数

难度:

标签:

题目描述

给你一个 rows x cols 大小的矩形披萨和一个整数 k ,矩形包含两种字符: 'A' (表示苹果)和 '.' (表示空白格子)。你需要切披萨 k-1 次,得到 k 块披萨并送给别人。

切披萨的每一刀,先要选择是向垂直还是水平方向切,再在矩形的边界上选一个切的位置,将披萨一分为二。如果垂直地切披萨,那么需要把左边的部分送给一个人,如果水平地切,那么需要把上面的部分送给一个人。在切完最后一刀后,需要把剩下来的一块送给最后一个人。

请你返回确保每一块披萨包含 至少 一个苹果的切披萨方案数。由于答案可能是个很大的数字,请你返回它对 10^9 + 7 取余的结果。

 

示例 1:

输入:pizza = ["A..","AAA","..."], k = 3
输出:3 
解释:上图展示了三种切披萨的方案。注意每一块披萨都至少包含一个苹果。

示例 2:

输入:pizza = ["A..","AA.","..."], k = 3
输出:1

示例 3:

输入:pizza = ["A..","A..","..."], k = 1
输出:1

 

提示:

  • 1 <= rows, cols <= 50
  • rows == pizza.length
  • cols == pizza[i].length
  • 1 <= k <= 10
  • pizza 只包含字符 'A' 和 '.' 。

代码结果

运行时间: 31 ms, 内存: 16.2 MB


/*
 * Problem: You are given a rectangular pizza represented as a matrix of characters 'A' (apple) and '.' (empty cell). You need to cut the pizza k-1 times into k pieces such that each piece contains at least one apple.
 * Approach using Java Streams:
 * 1. Similar to the standard approach but leverages Java Streams for concise code.
 * 2. Use a prefix sum array to calculate the number of apples in each sub-pizza.
 * 3. Implement dynamic programming using streams to accumulate the number of ways to cut the pizza.
 * 4. The dp array will store the number of ways to cut the sub-pizza starting from a given cell with a specific number of cuts left.
 */
import java.util.*;
import java.util.stream.IntStream;

public class WaysToCutPizzaStream {
    private static final int MOD = 1_000_000_007;

    public int waysToCutPizza(String[] pizza, int k) {
        int rows = pizza.length, cols = pizza[0].length();
        int[][][] dp = new int[rows + 1][cols + 1][k + 1];
        int[][] prefixSum = new int[rows + 1][cols + 1];

        for (int i = rows - 1; i >= 0; i--) {
            for (int j = cols - 1; j >= 0; j--) {
                prefixSum[i][j] = (pizza[i].charAt(j) == 'A' ? 1 : 0) + prefixSum[i + 1][j] + prefixSum[i][j + 1] - prefixSum[i + 1][j + 1];
                dp[i][j][1] = prefixSum[i][j] > 0 ? 1 : 0;
            }
        }

        for (int cuts = 2; cuts <= k; cuts++) {
            for (int i = 0; i < rows; i++) {
                for (int j = 0; j < cols; j++) {
                    int finalI = i;
                    int finalJ = j;
                    dp[i][j][cuts] = (IntStream.range(i + 1, rows).filter(row -> prefixSum[finalI][finalJ] - prefixSum[row][finalJ] > 0).map(row -> dp[row][finalJ][cuts - 1]).sum() % MOD
                            + IntStream.range(j + 1, cols).filter(col -> prefixSum[finalI][finalJ] - prefixSum[finalI][col] > 0).map(col -> dp[finalI][col][cuts - 1]).sum() % MOD) % MOD;
                }
            }
        }
        return dp[0][0][k];
    }
}

解释

方法:

这个题解使用动态规划的方法来解决切披萨问题。主要思路如下: 1. 首先预处理得到一个前缀和数组presums,其中presums[r][c]表示披萨从(r,c)位置到披萨右下角的苹果数量。 2. 然后定义一个二维DP数组f,其中f[r][c]表示从(r,c)位置到披萨右下角,切k-1次能够得到的合法方案数。 3. 初始化f数组,对于每个位置(r,c),如果从该位置到披萨右下角还有苹果,则f[r][c]=1,表示还可以进行一次合法切割。 4. 接下来进行k-1轮切割,每一轮都更新f数组。对于当前轮的每个位置(r,c),若该位置下方或右方已经没有苹果,则f[r][c]直接等于下方或右方的f值;否则,f[r][c]等于下方和右方所有合法切割方案数的总和。 5. 最后返回f[0][0]即为最终答案,表示从披萨左上角开始切k-1次能够得到的合法方案总数。

时间复杂度:

O(kRC)

空间复杂度:

O(RC)

代码细节讲解

🦆
在动态规划解法中,为什么需要使用前缀和数组`presums`来预处理披萨中的苹果数量?
前缀和数组`presums`的主要作用是快速计算任何子矩形区域内的苹果数量,以便在动态规划过程中判断是否可以在某个区域进行切割。使用`presums`可以在常数时间内获取任何从(r, c)到披萨右下角的区域内的苹果总数,这是因为通过前缀和,可以避免重复计算同一区域多次,从而有效地优化了算法的时间复杂度。
🦆
当更新DP数组`f`时,你是如何确保每一块披萨至少包含一个苹果的?
在更新DP数组`f`时,确保每块披萨至少含有一个苹果的关键在于使用`presums`数组。在每次切割更新时,通过检查`presums[r][c]`与`presums[r+1][c]`以及`presums[r][c+1]`的比较,确定切割后的每一部分至少包含一个苹果。此外,只有当`presums[r][c]`大于0时,位置`(r, c)`才会初始化为1,表示从该位置开始的区域内至少有一个苹果。这样的初始化和条件检查确保了每次更新时切割的有效性。
🦆
在题解中,初始化DP数组`f`使用了`if presums[r][c]`条件,这个条件具体是基于什么逻辑?
`if presums[r][c]`条件用于检查从坐标`(r, c)`到披萨的右下角是否存在至少一个苹果。如果`presums[r][c]`的值大于0,说明这个区域内有苹果,因此在动态规划数组`f`中,该位置`(r, c)`可以被初始化为1,表示从这个位置至少可以进行一次有效的切割。这是确保每次切割都至少包含一个苹果的基本前提。
🦆
题解提到对DP数组`f`进行了`k-1`轮更新,每一轮更新如何影响`f[r][c]`值,并能否详细解释每轮的更新过程?
在每一轮更新中,`f[r][c]`的值被更新为其下方`f[r+1][c]`和右方`f[r][c+1]`的值的和,但这只发生在苹果存在的情况下。具体来说,如果`(r, c)`位置到其下方没有苹果(`presums[r][c]`等于`presums[r+1][c]`),则`f[r][c]`更新为`f[r+1][c]`;如果到右方没有苹果(`presums[r][c]`等于`presums[r][c+1]`),则更新为`f[r][c+1]`。如果两边都有苹果,则`f[r][c]`更新为下方和右方的方案总和,即`f[r][c] = (f[r+1][c] + f[r][c+1]) % Mode`。这种更新保证了每一次切割都尽可能地增加方案数,同时确保每块披萨至少有一个苹果。

相关问题