【每日一题Day297】LC1444切披萨的方案数 | 动态规划+二维前缀和

切披萨的方案数【LC1444】

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

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

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

能独立写出2100+的题啦,保持手感!

dfs+记忆化
  • 思路

    • 寻找子问题:每次切披萨可以按行切割或者按列切割,剩下的披萨区域为右边区域或者下方区域,我们需要求出在剩下区域切 k − 1 k-1 k1刀的方案数,该问题和原问题是重复子问题,因此可以使用dp解决

    • **二维前缀和:**而每次切割时需要保证分割后披萨的苹果数目大于0,因此可以使用二维前缀和数组快速求解某个区域的苹果树

    • 定义dfs函数:定义 d f s ( x , y , k ) dfs(x,y,k) dfs(x,y,k)为在以 ( x , y ) (x,y) (x,y)为左上角、以 ( n − 1 , m − 1 ) (n-1, m-1) (n1,m1)为右下角的区域,切 k k k刀确保每一块披萨包含至少一个苹果的切披萨方案数

    • 递归入口:那么 d f s ( 0 , 0 , k − 1 ) dfs(0,0,k-1) dfs(0,0,k1)即为答案

    • 递归逻辑

      • 枚举按行切割,需保证上方披萨苹果数目大于0
      • 枚举按列切割,需保证左方披萨苹果数目大于0

      d f s ( x , y , k ) = ∑ i = x + 1 n − 1 d f s ( i , y , k − 1 ) + ∑ i = y + 1 m − 1 d f s ( x , i , k − 1 ) dfs(x,y,k) = \sum _{i=x+1} ^{n-1}dfs(i, y, k- 1) + \sum _{i=y+1} ^{m-1}dfs(x, i, k- 1) dfs(x,y,k)=i=x+1n1dfs(i,y,k1)+i=y+1m1dfs(x,i,k1)

    • 递归边界

      k k k为0无需分割时,检验这块披萨的苹果数目,如果大于0,则为有效分割,返回1;否则返回0

  • 实现

    class Solution {
        int n, m;
        int[][] sum;
        int[][][] memo;
        public static final int MOD = (int)(1e9 + 7);
        public int ways(String[] pizza, int k) {
            // 二维前缀和 + 记忆化
            n = pizza.length;
            m = pizza[0].length();
            sum = new int[n + 1][m + 1];// 以(x,y)为左上角、以(n-1, m-1)为右下角的区域包含的苹果数目
            memo = new int[n][m][k];
            for (int i = n - 1; i >= 0; i--){
                for (int j = m - 1; j >= 0; j--){
                    sum[i][j] = sum[i + 1][j] + sum[i][j + 1] - sum[i + 1][j + 1];
                    if ('A' == pizza[i].charAt(j)){
                        sum[i][j] += 1;
                    }
                    Arrays.fill(memo[i][j], -1);
                }
            }
            return dfs(0, 0, k - 1);
        }
        // 在以(x,y)为左上角、以(n-1, m-1)为右下角的区域,切k刀确保每一块披萨包含至少一个苹果的切披萨方案数
        public int dfs(int x, int y, int k){
            if (k == 0){
                return sum[x][y] > 0 ? 1 : 0;
            }
            if (memo[x][y][k] != -1) return memo[x][y][k];
            int res = 0;
            // 枚举行
            for (int i = x; i < n - 1; i++){
                if (sum[x][y] - sum[i + 1][y] > 0){// 这块披萨为x行至i行
                    res = (res + dfs(i + 1, y, k- 1)) % MOD;
                }
            }
            // 枚举列
            for (int i = y; i < m - 1; i++){
                if (sum[x][y] - sum[x][i + 1] > 0){// 这块披萨为y列至i列
                    res = (res + dfs(x, i + 1, k- 1)) % MOD;
                }
            }
            memo[x][y][k] = res;
            return res;
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n m + ( m + n ) k ) \mathcal{O}(nm +(m+n)^k) O(nm+(m+n)k),求取前缀和的时间复杂度为 O ( n m ) \mathcal{O}(nm) O(nm),记忆化搜索的过程可以近似为高度为 k k k m + n m+n m+n叉树,因此需要时间复杂度 O ( ( m + n ) k ) \mathcal{O}((m+n)^k) O((m+n)k)
      • 空间复杂度: O ( n m k + k ) \mathcal{O}(nmk+k) O(nmk+k)
dp
  • 实现

    从小到大枚举k

    class Solution {
        public static final int MOD = (int)(1e9 + 7);
        public int ways(String[] pizza, int k) {
            // 二维前缀和 + 记忆化
            int n = pizza.length;
            int m = pizza[0].length();
            int[][] sum = new int[n + 1][m + 1];// 以(x,y)为左上角、以(n-1, m-1)为右下角的区域包含的苹果数目
            int[][][] dp = new int[n][m][k];
            for (int i = n - 1; i >= 0; i--){
                for (int j = m - 1; j >= 0; j--){
                    sum[i][j] = sum[i + 1][j] + sum[i][j + 1] - sum[i + 1][j + 1];
                    if ('A' == pizza[i].charAt(j)){
                        sum[i][j] += 1;
                    }
                }
            }
            for (int c = 0; c < k; c++){
                for (int x = 0; x < n; x++){
                    for (int y = 0; y < m; y++){
                        if (c == 0){
                            dp[x][y][0] = sum[x][y] > 0 ? 1 : 0;
                            continue;
                        }
                        for (int i = x; i < n - 1; i++){
                            if (sum[x][y] - sum[i + 1][y] > 0){
                                dp[x][y][c] = (dp[x][y][c] + dp[i + 1][y][c - 1]) % MOD;
                            }
                        }
                        for (int i = y; i < m - 1; i++){
                            if (sum[x][y] - sum[x][i + 1] > 0){
                                dp[x][y][c] = (dp[x][y][c] + dp[x][i + 1][c - 1]) % MOD;
                            }
                        }
                    }
                }
            }
            return dp[0][0][k - 1];
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n m k ∗ ( m + n ) ) \mathcal{O}(nmk*(m+n)) O(nmk(m+n))
      • 空间复杂度: O ( m n k ) \mathcal{O}(mnk) O(mnk)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值