LC-1444. 切披萨的方案数(记忆化搜索==>动态规划)

1444. 切披萨的方案数

困难118

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

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

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

示例 1:

img

输入: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''.'

记忆化搜索==>动态规划

https://leetcode.cn/problems/number-of-ways-of-cutting-a-pizza/solutions/2392051/ji-bai-100cong-di-gui-dao-di-tui-dao-you-dxz5/

class Solution {
    public static final int MOD = (int)1e9+7;
    public int ways(String[] pizza, int k) {
        MatrixSum ms = new MatrixSum(pizza);
        int m = pizza.length, n = pizza[0].length();
        int[][][] memo = new int[k][m][n];
        for(int i = 0; i < k; i++){
            for(int j = 0; j < m; j++){
                Arrays.fill(memo[i][j], -1);
            }
        }
        return dfs(k-1, 0, 0, memo, ms, m, n);
    }

    // 定义dfs(c, i, j)表示左上角在(i,j)右下角在(m-1, n-1)的子矩形切c刀,每块都包含至少一个苹果的方案数
    // 转移
    //  如果垂直的切,枚举j2(宽度j2~j) 剩下的子矩形左上角在(i, j2) 还需切c-1刀:dfs(c-1, i, j2)
    //  如果水平的切,枚举i2(高度i2~i) 剩下的子矩形所上角在(i2, j) 还需切c-1刀:dfs(c-1, i2, j)
    // 递归边界 dfs(0, m-1, n-1) = 1
    // 递归入口 dfs(k-1, 0, 0)
    public int dfs(int c, int i, int j, int[][][] memo, MatrixSum ms, int m, int n){
        if (c == 0) // 递归边界:无法再切了
            return ms.query(i, j, m, n) > 0 ? 1 : 0;
        if (memo[c][i][j] != -1) // 之前计算过
            return memo[c][i][j];
        int res = 0;
        for (int j2 = j; j2 < n; j2++) // 垂直切
            if (ms.query(i, j, m, j2) > 0) // 有苹果
                res = (res + dfs(c - 1, i, j2, memo, ms, m, n)) % MOD;
        for (int i2 = i; i2 < m; i2++) // 水平切
            if (ms.query(i, j, i2, n) > 0) // 有苹果
                res = (res + dfs(c - 1, i2, j, memo, ms, m, n)) % MOD;
        return memo[c][i][j] = res; // 记忆化
    }
}

// 二维前缀和模板('A' 的 ASCII 码最低位为 1,'.' 的 ASCII 码最低位为 0)
class MatrixSum {
    private final int[][] sum;

    public MatrixSum(String[] matrix) {
        int m = matrix.length, n = matrix[0].length();
        sum = new int[m + 1][n + 1];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + (matrix[i].charAt(j) & 1);
            }
        }
    }

    // 返回左上角在 (r1,c1) 右下角在 (r2-1,c2-1) 的子矩阵元素和(类似前缀和的左闭右开)
    public int query(int r1, int c1, int r2, int c2) {
        return sum[r2][c2] - sum[r2][c1] - sum[r1][c2] + sum[r1][c1];
    }
}

转递推

class Solution {
    public static final int MOD = (int)1e9+7;
    public int ways(String[] pizza, int k) {
        MatrixSum ms = new MatrixSum(pizza);
        int m = pizza.length, n = pizza[0].length();
        int[][][] f = new int[k][m][n];
        for(int c = 0; c < k; c++){
            for(int i = 0; i < m; i++){
                for(int j = 0; j < n; j++){
                    if(c == 0){
                        f[c][i][j] = ms.query(i, j, m, n) > 0 ? 1 : 0;
                        continue;
                    }
                    int res = 0;
                    for (int j2 = j + 1; j2 < n; j2++) // 垂直切
                        if (ms.query(i, j, m, j2) > 0) // 有苹果
                            res = (res + f[c - 1][i][j2]) % MOD;
                    for (int i2 = i + 1; i2 < m; i2++) // 水平切
                        if (ms.query(i, j, i2, n) > 0) // 有苹果
                            res = (res + f[c - 1][i2][j]) % MOD;
                    f[c][i][j] = res;

                }
            }
        }
        return f[k - 1][0][0];
    }
}

// 二维前缀和模板('A' 的 ASCII 码最低位为 1,'.' 的 ASCII 码最低位为 0)
class MatrixSum {
    private final int[][] sum;

    public MatrixSum(String[] matrix) {
        int m = matrix.length, n = matrix[0].length();
        sum = new int[m + 1][n + 1];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + (matrix[i].charAt(j) & 1);
            }
        }
    }

    // 返回左上角在 (r1,c1) 右下角在 (r2-1,c2-1) 的子矩阵元素和(类似前缀和的左闭右开)
    public int query(int r1, int c1, int r2, int c2) {
        return sum[r2][c2] - sum[r2][c1] - sum[r1][c2] + sum[r1][c1];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值