切披萨的方案数
难度:
标签:
题目描述
给你一个 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`来预处理披萨中的苹果数量?
▷🦆
当更新DP数组`f`时,你是如何确保每一块披萨至少包含一个苹果的?
▷🦆
在题解中,初始化DP数组`f`使用了`if presums[r][c]`条件,这个条件具体是基于什么逻辑?
▷🦆
题解提到对DP数组`f`进行了`k-1`轮更新,每一轮更新如何影响`f[r][c]`值,并能否详细解释每轮的更新过程?
▷