Leetcode每日一题:1444. 切披萨的方案数(2023.8.17 C++)

目录

1444. 切披萨的方案数

题目描述:

实现代码与解析:

二维后缀和  + 动态规划

原理思路:


1444. 切披萨的方案数

题目描述:

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

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

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

示例 1:

Leetcode每日一题:1444. 切披萨的方案数(2023.8.17 C++)_第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' 和 '.' 。

实现代码与解析:

二维后缀和  + 动态规划

class Solution {
public:
    int ways(vector& pizza, int k) {

        int m = pizza.size(), n = pizza[0].size(), mod = 1e9 + 7;
        vector>> f(k, vector>(m, vector(n)));

        vector> apples(m + 1, vector(n + 1)); // 后缀和

        // 后缀和 与 初始化dp数组
        for (int i = m - 1; i >= 0; i--)
        {
            for (int j = n - 1; j >= 0; j--)
            {
                apples[i][j] = apples[i + 1][j] + apples[i][j + 1] - apples[i + 1][j + 1] + (pizza[i][j] == 'A');
                f[0][i][j] = apples[i][j] > 0;
            }
        } 

        for (int kk = 1; kk < k; kk++)
        {
            for (int i = 0; i < m; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    // 选择此刀的切割位置

                    // 水平切, 遍历切的位置
                    for (int a = i + 1; a < m; a++)
                    {
                        // 上面的一块中至少要有一个苹果
                        if (apples[i][j] > apples[a][j])
                        {
                            f[kk][i][j] = (f[kk][i][j] + f[kk - 1][a][j]) % mod;
                        }
                    }
                
                    // 垂直切
                    for (int b = j + 1; b < n; b++)
                    {
                        // 左侧块中至少有一个苹果
                        if (apples[i][j] > apples[i][b])
                        {
                            f[kk][i][j] = (f[kk][i][j] + f[kk - 1][i][b]) % mod;
                        }
                    }
                }
            }
        }
        
        return f[k - 1][0][0];
    }
};

原理思路:

        apples 数组,后缀和用于记录一块披萨中的苹果数量,用一块中的左上角来代替此块含有的苹果数。

        此题的关键是,dp[ k ][ i ][ j ] 的含义代表还剩下 k 刀没切,剩下的是左上角为 i ,j 的披萨状态时的切割方案总数。这是我自己的理解,力扣上dp数组定义的含义感觉不如我这样写和解释更直观,不过原理肯定是一样的。

        知道dp数组的含义,就很好写了。

        首先计算 apples 数组,这个就不用解释了,不会的话,建议去学习一下前缀和,二维前缀和的基础算法就行,同时初始化一下dp。

        初始化dp数组:显然在还需要切0刀,剩下最后一块披萨中有苹果时,表示切好了,是一种情况,赋值为1,否则不成立赋值为0;

f[0][i][j] = apples[i][j] > 0;

        遍历顺序:一定是先遍历切割刀数,因为就比如一个形状披萨状态下,切两刀肯定需要切一刀的状态递推而来,后面根据递推式也能看出来。

        递推方程:两种切法分类讨论:

        水平切:肯定是从第一行下边开始切,总不能切空气吧,所以是 i + 1 开始遍历,然后切完后上面的那块中一定要有苹果,所以需要判断一下,切完此刀后,剩下的大块需要再切 kk - 1刀,我们就不用再去遍历了,dp数组含义就是这个,根据这个写出递推式。

                递推式:f[ kk ][ i ][ j ] = (f[ kk ][ i ][ j ] + f[ kk - 1 ][ a ][ j ]) % mod;

        垂直切:与水平切同理,直接给出递推式:

                递推式:f[ kk ][ i ][ j ] = (f[ kk ][ i ][ j ] + f[ kk - 1 ][ i ][ b ]) % mod;

       最后,返回结果,显然,在初始状态还剩切k - 1刀时是我们需要的结果状态。

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

       结束。

你可能感兴趣的:(Leetcode,leetcode,代理模式,算法)