算法:切披萨的方案数,3ms解决方案

这篇博客探讨了如何解决第1444题,关于如何在限制切片次数内,确保每块披萨至少有一个苹果的问题。博主提供了递归、动态规划四种方法,从超时的递归思路到3ms的优化动态规划解决方案,逐步优化算法效率。动态规划的优化重点在于减少重复计算,降低空间复杂度。
摘要由CSDN通过智能技术生成

题目描述

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

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

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

示例

在这里插入图片描述
输入:pizza = [“A…”,“AAA”,"…"], k = 3
输出:3
解释:上图展示了三种切披萨的方案。注意每一块披萨都至少包含一个苹果。

解答

原题解发在了leetcode上,csdn也弄一份吧
本题提供四个方法,逐步优化到比较好的方法。

1.递归(超时)

分析

递归的方法比较容易想得到,但是会超时,不过可以借用这个思想进一步优化。
我们用一个int count(String[] pizza, int row, int col, int k)函数来递归,这个函数解决的是,在以row行,col列为起点的披萨(即这个披萨的左上角为pizza[row][col],右下角为最右下角),在剩余k次切割机会的情况下,有多少种方案,因为上边和左边的切掉会直接分掉,所以,上边和左边至少要有一个苹果,给出一张披萨,怎么得出方案数呢,分两种情况:横着切和竖着切

  • 横着切:我们需要找到从上到下第一个有苹果的行,从这行开始,分为上下两个部分,第一个有苹果的行包括在上面的部分,下面的部分递归用count函数求下面这个部分的披萨在k-1次机会下有多少种切割方案,然后从下面部分划一行给上面部分,再对剩下的下面部分求方案数,一直到下面部分只有一行。在这个过程中,把每次求得的方案数累加起来。
  • 竖着切:与横着切差不多,只是一列一列划到左边来计算右边部分的方案数

递归的结束条件是:k == 0,如果此时这个起点的披萨中没有任何一个苹果,则返回0,否则返回1

代码

public int ways(String[] pizza, int k) {
   
        if (pizza == null || pizza.length == 0 || pizza[0] == null || pizza[0].length() == 0 || k <= 0)
            return 0;
        return count(pizza, 0, 0, k-1);
    }

private int count(String[] pizza, int row, int col, int k) {
   
    if (k == 0) {
   
        for (int r = row; r < pizza.length; r++) {
   
            if (pizza[r].substring(col).indexOf('A') != -1) return 1;
        }
        return 0;
    }
    int ri = row, ci = col;
    //找最上面有苹果的行
    while (ri < pizza.length) {
   
        if (pizza[ri].substring(col).indexOf('A') == -1) ri++;
        else break;
    }
    //找最左边有苹果的列
    while (ci < pizza[0].length()) {
   
        boolean hasA = false;
        //已经求得最上边有苹果的行,就从这里开始,可以省点事
        int r = ri;
        while (r < pizza.length) {
   
            if (pizza[r].charAt(ci) == 'A') {
   
                hasA = true;
                break;
            }
            r++;
        }
        if (hasA) break;
        else ci++;
    }
    int res = 0;
    //横着切
    for (int i = ri + 1; i < pizza.length; i++) {
   
        res = (res + count(pizza, i, ci, k-1)) % 1000000007;
    }
    //竖着切
    for (int i = ci + 1; i < pizza[0].length(); i++) {
   
        res = (res + count(pizza, ri, i, k-1)) % 1000000007;
    }
    return res;
}

动态规划一

分析

在上面的递归函数中,发现row,col,k这三个参数是会变的,而且这三个参数就能决定一个答案,所以采用一张表(三维数组int[row][col][k],表示(row, col)为起点剩余k次切割机会的状态有多少种方案。可以看成一个方体,k是层数。在第三个方法优化成二维数组)记录下来,表示在这个状态下的方案数是多少。
接下来,我们需要求出不需要依赖其他状态的信息就能直接给出答案的状态的结果,以后其他的状态需要求出结果需要依赖这些基础状态
能直接得出答案的是剩余切割数为0的状态,如map[i][j][0],可以直接找(i, j)为起点的披萨还有没有苹果,有就是1,没有就是0。
初始化map[i][j][0]这一步怎么做呢。
下面说明以下怎么得出第0层的表,以下图中,除了苹果A,其他的.格子省略,方案数为1的格子涂成灰色,即以灰色格子的i,j为起点的披萨,一定包含苹果。

  • 首先,一个苹果到左上角起点之间为对角的区域是为1的,涂成灰色,因为这个区域为起点的披萨一定包含这个苹果,如下图map[2][2][0] == 1,所以以(2,2)为起点的披萨(红色区域)包含苹果
    image.png
  • 然后,这个苹果上边,或左边,或者左上出现了别的苹果,也不需要改变灰色区域
    image.png
  • 如果有苹果出现在这个苹果的右上方,则需要更新灰色区域
    image.png

那我们可以这样来求出哪些格子方案数为1:从最后一行从右往左遍历,如果出现了苹果,就记住这个苹果到最左边的距离,并且这行苹果及左边格子涂成灰色,然后看倒数第二行,同样从右往左遍历,有两种情况,一种是出现了比下一行更右边(即离最左边的距离比下一行大)的苹果,那么记住这个距离,然后这里到左边涂成灰色;第二种是没有出现更右边的苹果,那么从右往左遍历直到这个格子到最左边的距离等于记录的距离,那么这里到左边的格子是灰色
image.png
距离为3
image.png
距离还是3
image.png
距离更新为4
image.png
还是4

得出k=0的map之后,令k = 1,此时我们需要借助k为0的状态才能得出答案。思路和递归方法时一样,就是分横着切和竖着切,并且每次向上或向左划以行/列。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值