You are given a rectangular pizza represented by a list of strings pizza
, where each character is either 'A'
(representing an apple) or '.'
(empty cell). You are also given an integer k
, the number of pieces you must cut the pizza into. Each cut must be either horizontal or vertical, and after each cut, you choose one of the resulting pieces to cut again, until you have exactly k
pieces.
The goal is to find the number of ways to cut the pizza such that each piece contains at least one apple. Two ways are considered different if they involve a different sequence of cuts. The answer can be large, so return it modulo 10^9 + 7
.
Constraints:
pizza.length
, pizza[0].length
<= 50k
<= 10pizza
is either 'A'
or '.'
pizza
At first glance, the problem seems to require generating all possible ways to cut the pizza and counting those where every piece contains at least one apple. A brute-force approach would try all possible cut combinations, but with up to 50 rows and columns and up to 9 cuts, this quickly becomes infeasible.
To optimize, we notice:
We solve this problem using dynamic programming with memoization, combined with a prefix sum array to quickly check if a subrectangle contains apples.
preSum
where preSum[i][j]
is the number of apples in the subrectangle from (i, j)
to the bottom-right corner.dp(i, j, k)
as the number of ways to cut the subrectangle starting at (i, j)
into k
pieces, each with at least one apple.k == 1
, return 1 if the current subrectangle has at least one apple, else 0.i
. If the top part has at least one apple, recursively solve for the bottom part with k-1
cuts.j
. If the left part has at least one apple, recursively solve for the right part with k-1
cuts.(i, j, k)
.10^9 + 7
.
Example Input:
pizza = ["A..","AAA","..."]
, k = 3
Step-by-step:
k-1
pieces.k-1
cuts, and count a valid way if all pieces have at least one apple.For this input, there are 3 valid ways to cut the pizza into 3 pieces, each containing at least one apple.
Brute-force: The naive solution tries all possible cut sequences, leading to exponential time complexity, which is infeasible for large grids.
Optimized DP Approach:
rows * cols * k
unique DP states.rows + cols
possible cuts.O(k * rows^2 * cols^2)
in the worst case.O(rows * cols * k)
for the memoization table and O(rows * cols)
for the prefix sum array.This problem is elegantly solved by combining dynamic programming with memoization and prefix sum preprocessing. The prefix sum allows fast apple checks in any subrectangle, while DP ensures we only compute each state once. By structuring the problem as a recursive process of making cuts and counting valid ways, we avoid brute-force enumeration and achieve an efficient solution even for large pizzas and multiple cuts.
from functools import lru_cache
class Solution:
def ways(self, pizza, k):
MOD = 10 ** 9 + 7
rows, cols = len(pizza), len(pizza[0])
# Precompute prefix sums
preSum = [[0] * (cols + 1) for _ in range(rows + 1)]
for i in range(rows - 1, -1, -1):
for j in range(cols - 1, -1, -1):
preSum[i][j] = (1 if pizza[i][j] == 'A' else 0) + preSum[i+1][j] + preSum[i][j+1] - preSum[i+1][j+1]
@lru_cache(maxsize=None)
def dp(i, j, cuts):
if preSum[i][j] == 0:
return 0
if cuts == 1:
return 1
res = 0
# Horizontal cuts
for x in range(i+1, rows):
if preSum[i][j] - preSum[x][j] > 0:
res = (res + dp(x, j, cuts-1)) % MOD
# Vertical cuts
for y in range(j+1, cols):
if preSum[i][j] - preSum[i][y] > 0:
res = (res + dp(i, y, cuts-1)) % MOD
return res
return dp(0, 0, k)
class Solution {
public:
int ways(vector<string>& pizza, int k) {
const int MOD = 1e9 + 7;
int rows = pizza.size(), cols = pizza[0].size();
vector<vector<int>> preSum(rows+1, vector<int>(cols+1, 0));
// Precompute prefix sums
for (int i = rows - 1; i >= 0; --i) {
for (int j = cols - 1; j >= 0; --j) {
preSum[i][j] = (pizza[i][j] == 'A') + preSum[i+1][j] + preSum[i][j+1] - preSum[i+1][j+1];
}
}
vector<vector<vector<int>>> memo(rows, vector<vector<int>>(cols, vector<int>(k+1, -1)));
function<int(int,int,int)> dp = [&](int i, int j, int cuts) {
if (preSum[i][j] == 0) return 0;
if (cuts == 1) return 1;
if (memo[i][j][cuts] != -1) return memo[i][j][cuts];
int res = 0;
// Horizontal cuts
for (int x = i+1; x < rows; ++x) {
if (preSum[i][j] - preSum[x][j] > 0)
res = (res + dp(x, j, cuts-1)) % MOD;
}
// Vertical cuts
for (int y = j+1; y < cols; ++y) {
if (preSum[i][j] - preSum[i][y] > 0)
res = (res + dp(i, y, cuts-1)) % MOD;
}
return memo[i][j][cuts] = res;
};
return dp(0, 0, k);
}
};
class Solution {
public int ways(String[] pizza, int k) {
int MOD = 1_000_000_007;
int rows = pizza.length, cols = pizza[0].length();
int[][] preSum = new int[rows+1][cols+1];
// Precompute prefix sums
for (int i = rows - 1; i >= 0; --i) {
for (int j = cols - 1; j >= 0; --j) {
preSum[i][j] = (pizza[i].charAt(j) == 'A' ? 1 : 0)
+ preSum[i+1][j] + preSum[i][j+1] - preSum[i+1][j+1];
}
}
Integer[][][] memo = new Integer[rows][cols][k+1];
return dp(0, 0, k, preSum, memo, rows, cols, MOD);
}
private int dp(int i, int j, int cuts, int[][] preSum, Integer[][][] memo, int rows, int cols, int MOD) {
if (preSum[i][j] == 0) return 0;
if (cuts == 1) return 1;
if (memo[i][j][cuts] != null) return memo[i][j][cuts];
int res = 0;
// Horizontal cuts
for (int x = i+1; x < rows; ++x) {
if (preSum[i][j] - preSum[x][j] > 0)
res = (res + dp(x, j, cuts-1, preSum, memo, rows, cols, MOD)) % MOD;
}
// Vertical cuts
for (int y = j+1; y < cols; ++y) {
if (preSum[i][j] - preSum[i][y] > 0)
res = (res + dp(i, y, cuts-1, preSum, memo, rows, cols, MOD)) % MOD;
}
memo[i][j][cuts] = res;
return res;
}
}
/**
* @param {string[]} pizza
* @param {number} k
* @return {number}
*/
var ways = function(pizza, k) {
const MOD = 1e9 + 7;
const rows = pizza.length, cols = pizza[0].length;
// Precompute prefix sums
const preSum = Array.from({length: rows+1}, () => Array(cols+1).fill(0));
for (let i = rows - 1; i >= 0; --i) {
for (let j = cols - 1; j >= 0; --j) {
preSum[i][j] = (pizza[i][j] === 'A' ? 1 : 0)
+ preSum[i+1][j] + preSum[i][j+1] - preSum[i+1][j+1];
}
}
const memo = new Map();
function dp(i, j, cuts) {
const key = i + ',' + j + ',' + cuts;
if (preSum[i][j] === 0) return 0;
if (cuts === 1) return 1;
if (memo.has(key)) return memo.get(key);
let res = 0;
// Horizontal cuts
for (let x = i+1; x < rows; ++x) {
if (preSum[i][j] - preSum[x][j] > 0)
res = (res + dp(x, j, cuts-1)) % MOD;
}
// Vertical cuts
for (let y = j+1; y < cols; ++y) {
if (preSum[i][j] - preSum[i][y] > 0)
res = (res + dp(i, y, cuts-1)) % MOD;
}
memo.set(key, res);
return res;
}
return dp(0, 0, k);
};