题目是LeetCode第188场周赛的第三题,链接:1444. 切披萨的方案数。具体描述见原题。
这道题是一道比较麻烦的动态规划题目,因为是三维的动态规划。先定义dp[i][j][p]
:切了p-1
刀之后(这时得到p
块披萨)剩下以(i,j)
为左上角的披萨的方案数。则对于第p-2
刀应该切在哪有了限制,因为需要保证切了之后剩下的那块披萨得可以再切一刀得到两块披萨(都得有苹果)。然后就是这第p-2
刀切了之后剩下什么同样有限制,这剩下的必然是以(ii,j)
或(i,jj)
为左上角的披萨,其中ii
、jj
分别是比i
、j
小的坐标,也就是不可能剩下一块以(ii,jj)
为左上角的披萨,否则没法通过只切一刀就得到以(i,j)
为左上角的披萨。那么现在就可以写出递推公式了
d p [ i ] [ j ] [ p ] = ∑ i i = 0 i − 1 d p [ i i ] [ j ] [ p − 1 ] + ∑ j j = 0 j − 1 d p [ i ] [ j j ] [ p − 1 ] dp[i][j][p]=\sum_{ii=0}^{i-1}dp[ii][j][p-1]+\sum_{jj=0}^{j-1}dp[i][jj][p-1] dp[i][j][p]=ii=0∑i−1dp[ii][j][p−1]+jj=0∑j−1dp[i][jj][p−1]
但是上面的ii
和jj
都需要满足特定条件,比如ii
是cumSum[i][j] > 0 && cumSum[ii][j] - cumSum[i][j] > 0
,即切了第p-2
刀之后剩下的第p-1
块披萨可以被再切一刀形成两块有苹果的披萨,同理对j
有cumSum[i][j] > 0 && cumSum[i][jj] - cumSum[i][j] > 0
。这里的cumSum[i][j]
的意思是以(i,j)
为左上角的披萨里有多少苹果。
最后需要返回的结果其实就是将所有dp[i][j][k-1]
给加起来。
时间复杂度为 O ( ( m + n ) m n k ) O((m+n)mnk) O((m+n)mnk),空间复杂度为 O ( m n k ) O(mnk) O(mnk)(可以继续优化空间复杂度,懒得做了)。
JAVA版代码如下:
class Solution {
public int ways(String[] pizza, int k) {
int mod = 1000_000_007;
int row = pizza.length;
int col = pizza[0].length();
// cumSum[i][j]:以(i,j)为左上角的披萨里有多少个苹果
int[][] cumSum = new int[row][col];
cumSum[row - 1][col - 1] = pizza[row - 1].charAt(col - 1) == 'A' ? 1 : 0;
for (int j = col - 2; j >= 0; --j) {
if (pizza[row - 1].charAt(j) == 'A') {
cumSum[row - 1][j] = cumSum[row - 1][j + 1] + 1;
}
else {
cumSum[row - 1][j] = cumSum[row - 1][j + 1];
}
}
for (int i = row - 2; i >= 0; --i) {
int curcum = 0;
for (int j = col - 1; j >= 0; --j) {
curcum += pizza[i].charAt(j) == 'A' ? 1 : 0;
cumSum[i][j] = cumSum[i + 1][j] + curcum;
}
}
// dp[i][j][p]:切了p-1刀后剩下第p块且其以(i,j)为左上角的披萨的方案数
int[][][] dp = new int[row][col][k];
dp[0][0][0] = 1;
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
// 只需要切k-1次就得到k块
for (int p = 1; p < k; ++p) {
if (cumSum[i][j] == 0) {
break;
}
// 第p-2刀切了之后剩下的是(i, jj)
for (int jj = 0; jj < j; ++jj) {
// 需要确保切了第p-2刀之后还可以再竖切一刀剩下(i,j)
if (cumSum[i][j] > 0 && cumSum[i][jj] - cumSum[i][j] > 0) {
dp[i][j][p] = (dp[i][j][p] + dp[i][jj][p - 1]) % mod;
}
}
// 第p-2刀切了之后剩下的是(ii, j)
for (int ii = 0; ii < i; ++ii) {
if (cumSum[i][j] > 0 && cumSum[ii][j] - cumSum[i][j] > 0) {
// 需要确保切了第p-2刀之后还可以再切一刀剩下(i,j)
dp[i][j][p] = (dp[i][j][p] + dp[ii][j][p - 1]) % mod;
}
}
}
}
}
int result = 0;
for (int i = 0; i < row; ++i) {
for (int j = 0; j < col; ++j) {
result = (result + dp[i][j][k - 1]) % mod;
}
}
return result;
}
}
提交结果如下:
Python版代码如下:
class Solution:
def ways(self, pizza: List[str], k: int) -> int:
row, col = len(pizza), len(pizza[0])
cumSum = [[0 for _ in range(col)] for _ in range(row)]
cumSum[row - 1][col - 1] = 1 if pizza[row - 1][col - 1] == 'A' else 0
for j in range(col - 2, -1, -1):
cumSum[row - 1][j] += cumSum[row - 1][j + 1] + (1 if pizza[row - 1][j] == 'A' else 0)
for i in range(row - 2, -1, -1):
cur = 0
for j in range(col - 1, -1, -1):
cur += 1 if pizza[i][j] == 'A' else 0
cumSum[i][j] += cur + cumSum[i + 1][j]
dp = [[[0 for _ in range(col)] for _ in range(row)] for _ in range(k)]
dp[0][0][0] = 1 if cumSum[0][0] > 0 else 0
for p in range(1, k):
for i in range(row):
for j in range(col):
for ii in range(i):
if cumSum[i][j] > 0 and cumSum[ii][j] > cumSum[i][j]:
dp[p][i][j] += dp[p - 1][ii][j]
for jj in range(j):
if cumSum[i][j] > 0 and cumSum[i][jj] > cumSum[i][j]:
dp[p][i][j] += dp[p - 1][i][jj]
return sum(dp[k - 1][i][j] for i in range(row) for j in range(col)) % 1000000007
提交结果如下: